【问题标题】:HLS live streaming from static files来自静态文件的 HLS 直播
【发布时间】:2016-05-20 13:24:30
【问题描述】:

我有一个客户端,其中包含数千个在内部流式传输的音频/视频文件,所有文件都被分段 (.ts) 并保存在 S3 存储桶中,并在 SQL 数据库中包含适当的元数据。现在他们要求我创建两个“实时”流,一个用于音频,一个用于视频,他们可以设置并忘记。

不想重新分割所有内容或将我试图破解的所有文件连接到一个“实时”m3u8 中,该 m3u8 可以在现有文件中滑动(它们都以完全相同的方式编码)。

我所做的是生成一个“广播播放列表”,它以 40 秒的间隔(每 m3u8 x3 .ts)保存到数据库中,每个都标有开始和结束时间以及适当的 EXT-X-MEDIA-顺序。然后我在 NOW() 之间进行选择并推送文件。

它可以工作,但有时时机是正确的,它会为第一个和最后一个文件命中相同的分组并缓冲。我可以完全控制播放器 (VideoJS) 和服务器以使其正常工作。

这是我到目前为止的代码......我可以通过什么方式完成这项工作?我还没有尝试在 vJS 上玩缓冲区(不知道如何......)

所有基本文件信息都这样存储在数据库中

INSERT INTO `contenido_audio_hls` (`id`, `audio_s`, `duration`) VALUES ('f2z7dcwc0l7rleig', '["10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","10.000000","4.100000"]', 10);

生成播放列表时,我提取所需的数据

$radio = sql("SELECT lista_contenido.orden,lista_contenido.contenido,contenido_audio_hls.audio_s FROM lista_listas LEFT JOIN lista_contenido ON (lista_listas.id = lista_contenido.lista) LEFT JOIN contenido_audio_hls ON (lista_contenido.contenido = contenido_audio_hls.id) WHERE (lista_listas.tipo = 'radio') ORDER BY lista_contenido.orden ASC");
foreach($radio['data'] as $k=>$v) {
    $arreglo = json_decode($v['audio_s'],TRUE);
    foreach($arreglo as $kk=>$vv) {
        $puro[] = array("extinf"=>'#EXTINF:'.$vv.',',"id"=>$v['contenido'],"segment"=>$kk);
    }
}

我遍历它们以创建组

$segundos = 0;
$grupo = 1;
$contador = 1;
foreach($puro as $k=>$v) {
    if($segundos <= 30) {
        $m3u8[$grupo][] = $puro[$k];
        $contador++;
    } else {
        $m3u8[$grupo][] = $puro[$k];
        $grupo = $grupo + $contador;
        $segundos = 0;
    }
    $segundos = $segundos + 10;
}

然后把它们放到自己的桌子上

$largo = 0;
foreach($m3u8 as $k=>$v) {
    $ini = sprintf('%02d:%02d:%02d',($largo/3600),($largo/60%60),$largo%60);
    $localfin = $largo + 40;
    $fin = sprintf('%02d:%02d:%02d',($localfin/3600),($localfin/60%60),$localfin%60);

    $query = "INSERT INTO lista_m3u8 (ini,fin,tipo,sequence,data) VALUES('".$ini."','".$fin."','radio','".$k."','".json_encode($v)."')";

    sql($query);

    $largo = $largo + 40;
}

这给了我这个

INSERT INTO `lista_m3u8` (`ini`, `fin`, `tipo`, `sequence`, `data`) VALUES ('06:54:00', '06:54:40', 'radio', 580636, '[{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":14},{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":15},{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":16},{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":17}]');
INSERT INTO `lista_m3u8` (`ini`, `fin`, `tipo`, `sequence`, `data`) VALUES ('06:54:40', '06:55:20', 'radio', 582504, '[{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":18},{"extinf":"#EXTINF:10.000000,","id":"f2z7de0quwgehw23","segment":19},{"extinf":"#EXTINF:0.766667,","id":"f2z7de0quwgehw23","segment":20},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":0}]');
INSERT INTO `lista_m3u8` (`ini`, `fin`, `tipo`, `sequence`, `data`) VALUES ('06:55:20', '06:56:00', 'radio', 584375, '[{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":1},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":2},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":3},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":4}]');
INSERT INTO `lista_m3u8` (`ini`, `fin`, `tipo`, `sequence`, `data`) VALUES ('06:56:00', '06:56:40', 'radio', 586249, '[{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":5},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":6},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":7},{"extinf":"#EXTINF:10.000000,","id":"f2z7dft8c217xyp","segment":8}]');

然后生成m3u8

$audio = sql("SELECT sequence, data FROM lista_m3u8 WHERE tipo = 'radio' AND ini <= DATE_FORMAT(NOW(),'%H:%i:%s') AND fin >= DATE_FORMAT(NOW(),'%H:%i:%s')");

$sale = '#EXTM3U'.PHP_EOL;
$sale .= '#EXT-X-VERSION:3'.PHP_EOL;
$sale .= '#EXT-X-MEDIA-SEQUENCE:'.$audio['data'][0]['sequence'].PHP_EOL;
$sale .= '#EXT-X-TARGETDURATION:10'.PHP_EOL;

$arreglo = json_decode($audio['data'][0]['data'],TRUE);
foreach($arreglo as $k=>$v) {
    $sale .= $v['extinf'].PHP_EOL;
    $sale .= S3URL("bucket-audio",$v['id']."/segment".sprintf('%05d',$v['segment']).".ts",(count($arreglo) * 25)).PHP_EOL;
}

header("Content-type: application/x-mpegURL");
echo $sale.PHP_EOL;

【问题讨论】:

    标签: php amazon-s3 video.js http-live-streaming m3u8


    【解决方案1】:

    我相信我已经解决了。我刚刚离开这个游戏过去 16 个小时才回来,它还在继续,我的 AWS 日志证实了这一点。

    我最初是以错误的方式接近这个,试图生成罐装 m3u8 文件;我真正需要做的是知道两件事:

    1.- 现在应该播放什么片段(无论原始文件)?

    2.- 从“流”开始播放多少段(文件 0,段 0)?

    新方法现在采用原始播放列表并为每个片段创建一行,指示其开始时间、持续时间、片段文件和流中的位置。然后生成 m3u8,后面和后面几个段,从流的开头计算正确的 EXT-X-MEDIA-SEQUENCE。我还在文件之间添加了 EXT-X-DISCONTINUITY,这样它就不会在收到意外的标头时挂起。

    所以现在,我从原始表中获取文件/段列表:

    $ini = 0;
    $conteo = 0;
    $radio = sql("SELECT lista_contenido.orden,lista_contenido.contenido,contenido_audio_hls.audio_s FROM lista_listas LEFT JOIN lista_contenido ON (lista_listas.id = lista_contenido.lista) LEFT JOIN contenido_audio_hls ON (lista_contenido.contenido = contenido_audio_hls.id) WHERE (lista_listas.tipo = 'radio') ORDER BY lista_contenido.orden ASC");
    foreach($radio['data'] as $k=>$v) {
        $arreglo = json_decode($v['audio_s'],TRUE);
        $seg = 0;
        foreach($arreglo as $kk=>$vv) {
            sql("INSERT INTO lista_m3u8 (tipo,orden,contenido,segmento,extinf,ini) VALUES('radio','".$conteo."','".$v['contenido']."','segment".sprintf('%05d',$seg).".ts','".$vv."','".sprintf('%02d:%02d:%02d',($ini/3600),($ini/60%60),$ini%60)."')");
            $ini = $ini + ceil($vv * 1);
            $seg++;
            $conteo++;
        }
    }
    

    这给了我这样一张桌子:

    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 20, 'f2z7ddw7r6bb7gfy', 'segment00018.ts', 10.000000000000, '00:03:11');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 21, 'f2z7ddw7r6bb7gfy', 'segment00019.ts', 10.000000000000, '00:03:21');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 22, 'f2z7ddw7r6bb7gfy', 'segment00020.ts', 6.066667079926, '00:03:31');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 23, 'f2z7df1bb66be7h3', 'segment00000.ts', 10.000000000000, '00:03:38');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 24, 'f2z7df1bb66be7h3', 'segment00001.ts', 10.000000000000, '00:03:48');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 25, 'f2z7df1bb66be7h3', 'segment00002.ts', 10.000000000000, '00:03:58');
    INSERT INTO `lista_m3u8` (`tipo`, `orden`, `contenido`, `segmento`, `extinf`, `ini`) VALUES ('radio', 26, 'f2z7df1bb66be7h3', 'segment00003.ts', 10.000000000000, '00:04:08');
    

    这会创建数千个 db 行(对于 24 小时广播流,大约为 9000 个),但它们是按时间索引的,因此 SELECTing 是即时的。

    最终的 m3u8 脚本会这样做:

    $actual = sql("SELECT orden, extinf, contenido, segmento, ini FROM lista_m3u8 WHERE tipo = 'radio' AND ini >= DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MINUTE),'%H:%i:%s') AND ini <= DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 3 MINUTE),'%H:%i:%s') ORDER BY orden ASC");
    
    $sale = '#EXTM3U'.PHP_EOL;
    $sale .= '#EXT-X-VERSION:3'.PHP_EOL;
    $sale .= '#EXT-X-MEDIA-SEQUENCE:'.($actual['data'][$actual['total']-1]['orden'] - $actual['total']).PHP_EOL;
    $sale .= '#EXT-X-TARGETDURATION:10'.PHP_EOL;
    
    $contenido = $actual['data'][0]['contenido'];
    foreach($actual['data'] as $k=>$v) {
        if($v['contenido'] != $contenido) { $sale .= "#EXT-X-DISCONTINUITY".PHP_EOL; }
        $sale .= "#EXTINF:".$v['extinf'].",".PHP_EOL;
        $sale .= S3URL("audio-bucket",$v['contenido']."/".$v['segmento'],180).PHP_EOL;
        $contenido = $v['contenido'];
    }
    
    header("Content-type: application/x-mpegURL");
    echo $sale.PHP_EOL;
    

    注意这里发生的两件事,EXT-X-MEDIA-SEQUENCE 是通过从整个列表中减去当前段的位置来计算的,并且 XT-X-DISCONTINUITY 放在文件更改之间。

    我将进行更多测试,看看这是否适用于浏览器(目前我只测试了 Chrome 和 IEG);但我相信这是一个可行的解决方案。

    【讨论】:

      【解决方案2】:

      如果你想循环播放一个视频,你也可以试试这个 php 脚本之类的简单方法:

      <?php
      // lets assume that we have stream splitted to parts named testXXXXX.ts
      // and all parts have 2.4 seconds and we want to play in loop part
      // from test0.ts to test29.ts forever in a live stream
      header('Content-Type: application/x-mpegURL');
      $time = intval(time() / 2.40000);
      $s1 = ($time + 1) % 30;
      $s2 = ($time + 2) % 30;
      $s3 = ($time + 3) % 30;
      $s4 = ($time + 4) % 30;
      ?>
      #EXTM3U
      #EXT-X-VERSION:3
      #EXT-X-TARGETDURATION:2
      #EXT-X-MEDIA-SEQUENCE:<?php echo "$time\n"; ?>
      #EXTINF:2.40000,
      test<?php echo $s1; ?>.ts
      <?php if ($s2 < $s1) echo "#EXT-X-DISCONTINUITY\n"; ?>
      #EXTINF:2.40000,
      test<?php echo $s2; ?>.ts
      <?php if ($s3 < $s2) echo "#EXT-X-DISCONTINUITY\n"; ?>
      #EXTINF:2.40000,
      test<?php echo $s3; ?>.ts
      <?php if ($s4 < $s3) echo "#EXT-X-DISCONTINUITY\n"; ?>
      #EXTINF:2.40000,
      test<?php echo $s4; ?>.ts
      

      【讨论】:

        猜你喜欢
        • 2017-10-23
        • 1970-01-01
        • 2020-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-12
        • 2021-10-24
        • 2018-11-17
        相关资源
        最近更新 更多