HTML5 <audio> / <video>以及使用FFMPEG进行实时转码</video> </audio>

时间:2010-09-03 21:11:23

标签: video audio html5 ffmpeg transcoding

因此,从我的网络服务器,我想使用FFMPEG转码媒体文件,以便与HTML <audio><video>标记一起使用。够容易吗?

当HTTP客户端请求转换后的文件时,转换需要实时进行。理想情况下,文件将在转码时流式传输回HTTP客户端(而不是之后的文件,因为在发送回任何数据之前可能需要一段时间)。

这很好,除了在今天的浏览器中,HTML5音频或视频标签使用Range标头在多个HTTP请求中请求媒体文件。 See this question for details

在上面链接的那个问题中,您可以看到Safari请求文件的奇怪块,包括结尾的几个字节。这就产生了一个问题,即Web服务器必须等待转换完成,以便传递文件的最后字节以符合Range请求。

所以我的问题是,我的思路是否正确?有没有更好的方法可以将转码内容提供给<audio><video>标记,而不会等待整个转化完成?提前谢谢!

5 个答案:

答案 0 :(得分:9)

我最近遇到了同样的问题,因为我想将我的库提供给浏览器。令人惊讶的是,通过ffmpeg发送流并在运行中交付的想法非常有效。主要问题是支持寻求......

下面,您将使用Flask在Python中找到代码片段来解决问题:

我们需要一个功能来传输内容:

@app.route('/media/<path:path>.ogv')
def media_content_ogv(path):
    d= os.path.abspath( os.path.join( config.media_folder, path ) )
    if not os.path.isfile( d ): abort(404)
    start= request.args.get("start") or 0
    def generate():
        cmdline= list()
        cmdline.append( config.ffmpeg )
        cmdline.append( "-i" )
        cmdline.append( d );
        cmdline.append( "-ss" )
        cmdline.append( str(start) );
        cmdline.extend( config.ffmpeg_args )
        print cmdline
        FNULL = open(os.devnull, 'w')
        proc= subprocess.Popen( cmdline, stdout=subprocess.PIPE, stderr=FNULL )
        try:
            f= proc.stdout
            byte = f.read(512)
            while byte:
                yield byte
                byte = f.read(512)
        finally:
            proc.kill()

    return Response(response=generate(),status=200,mimetype='video/ogg',headers={'Access-Control-Allow-Origin': '*', "Content-Type":"video/ogg","Content-Disposition":"inline","Content-Transfer-Enconding":"binary"})

然后我们需要一个函数来返回持续时间:

@app.route('/media/<path:path>.js')
def media_content_js(path):
    d= os.path.abspath( os.path.join( config.media_folder, path ) )
    if not os.path.isfile( d ): abort(404)
    cmdline= list()
    cmdline.append( config.ffmpeg )
    cmdline.append( "-i" )
    cmdline.append( d );
    duration= -1
    FNULL = open(os.devnull, 'w')
    proc= subprocess.Popen( cmdline, stderr=subprocess.PIPE, stdout=FNULL )
    try:
        for line in iter(proc.stderr.readline,''):
            line= line.rstrip()
            #Duration: 00:00:45.13, start: 0.000000, bitrate: 302 kb/s
            m = re.search('Duration: (..):(..):(..)\...', line)
            if m is not None: duration= int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + 1
    finally:
        proc.kill()

    return jsonify(duration=duration)

最后,我们使用videojs将其破解为HTML5:

<!DOCTYPE html>
<html>
<head>
    <link href="//vjs.zencdn.net/4.5/video-js.css" rel="stylesheet">
    <script src="//vjs.zencdn.net/4.5/video.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
    <video id="video" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264">
    </video>
    <script>
        var video= videojs('video');
        video.src("media/testavi.avi.ogv");

        // hack duration
        video.duration= function() { return video.theDuration; };
        video.start= 0;
        video.oldCurrentTime= video.currentTime;
        video.currentTime= function(time) 
        { 
            if( time == undefined )
            {
                return video.oldCurrentTime() + video.start;
            }
            console.log(time)
            video.start= time;
            video.oldCurrentTime(0);
            video.src("media/testavi.avi.ogv?start=" + time);
            video.play();
            return this;
        };

        $.getJSON( "media/testavi.avi.js", function( data ) 
        {
            video.theDuration= data.duration;
        });
    </script>
</body>

可以在https://github.com/derolf/transcoder找到一个工作示例。

德罗

答案 1 :(得分:2)

感谢您回复 Camilo 。我仔细研究了关于Range请求的HTTP规范,并找到了:

The header SHOULD indicate the total length of the full entity-body, unless
this length is unknown or difficult to determine. The asterisk "*" character
means that the instance-length is unknown at the time when the response was
generated.

因此,这只是测试浏览器在使用Content-Range: bytes 0-1/*回复时的反应。我会告诉你会发生什么。

答案 2 :(得分:0)

AFAIK你可以在ffmpeg中编码为stdout。因此,您可以将HTTP服务器配置为:

  • 收到GET后开始编码以缓存。
  • 流请求的客户端字节范围。
  • 填充缓冲区并将其用于后续范围。

我很无能,但我认为你可以在不知道最终流的长度的情况下离开。

另一方面,我认为这很容易出现DoS。

答案 3 :(得分:0)

我知道这是一个旧线程,但是如果有人发现它并需要帮助,我还是会发布它。

'user3612643'的答案是正确的,该错误解决了查找问题。但是,这带来了一个新问题。当前时间不再正确。要解决此问题,我们必须复制原始的currentTime函数。

现在,每次video.js调用currentTime(不带参数)时,它都会调用oldCurrentTime,这是原始的currentTime函数。其余与“ user3612643”的答案相同(谢谢!)。这适用于最新的video.js(7.7.6)

    video = videojs("video");
    video.src({
      src: 'http://localhost:4000/api/video/sdf',
      type: 'video/webm'
    });


     // hack duration
     video.duration= function() {return video.theDuration; };
     video.start= 0;

     // The original code for "currentTime"
     video.oldCurrentTime = function currentTime(seconds) {
      if (typeof seconds !== 'undefined') {
        if (seconds < 0) {
          seconds = 0;
        }

        this.techCall_('setCurrentTime', seconds);
        return;
      }
      this.cache_.currentTime = this.techGet_('currentTime') || 0;
      return this.cache_.currentTime;
    }

      // Our modified currentTime
     video.currentTime= function(time) 
     { 
         if( time == undefined )
         {
             return video.oldCurrentTime() + video.start;
         }
         video.start= time;
         video.oldCurrentTime(0);
         video.src({
           src: "http://localhost:4000/api/video/sdf?start=" + time,
           type: 'video/webm'
          });
         video.play();
         return this;
     };

     // Get the dureation of the movie
     $.getJSON( "http://localhost:4000/api/video/sdf/getDuration", function( data ) 
     {
         video.theDuration= data.duration;
     });

答案 4 :(得分:-1)

这应该可以通过VLC来实现,我能够通过设置VLC来托管一个大的avi文件并将其转码为OGG,然后我的html5引用了流来实现它:

<source src="http://localhost:8081/stream.ogg">

它能够在vlc中转码,并在我的Chrome浏览器和我的Android手机上渲染得很好,但我最终选择different solution而不是完成创建我自己的webapp来托管我的工作媒体收集并为所请求的文件创建流 - 我看起来并且找不到一个已经以我需要/喜欢的方式完成它的免费文件。