21

因此,在我的网络服务器上,我想使用 FFMPEG 对媒体文件进行转码,以便与 HTML<audio><video>标签一起使用。够容易吧?

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

这很好,除了在今天的浏览器中,HTML5 音频或视频标签在多个 HTTP 请求中请求带有Range标头的媒体文件。有关详细信息,请参阅此问题

在上面链接的那个问题中,您可以看到 Safari 请求了奇怪的文件块,包括结尾的几个字节。这带来了一个问题,因为 Web 服务器必须等待转换完成,才能交付文件的最终字节以符合Range请求。

所以我的问题是,我的思路对吗?有没有更好的方法可以将转码内容传送到<audio><video>标签,而无需等待整个转换完成?提前致谢!

4

5 回答 5

11

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

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

我们需要一个函数来流式传输内容:

@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找到一个工作示例。

德罗

于 2014-05-08T05:26:41.077 回答
3

感谢卡米洛的回复。我仔细查看了有关 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/*,例如。我会让你知道会发生什么。

于 2010-09-10T05:32:02.463 回答
0

AFAIK,您可以在 ffmpeg 中编码为标准输出。因此,您可以将 HTTP 服务器配置为:

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

我一无所知,但我认为您可以在不知道最终流的长度的情况下逃脱。

另一方面,我认为这很容易受到 DoS 的影响。

于 2010-09-09T05:18:02.667 回答
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;
     });
于 2020-05-13T16:12:08.243 回答
-1

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

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

它能够在 vlc 中转码,并在我的 chrome 浏览器和我的 android 手机上渲染得很好,但我最终采取了不同的解决方案,而不是通过创建自己的 web 应用程序来托管我的媒体集合并创建流的工作请求的文件 - 我看了看,找不到一个免费的,它以我需要/喜欢的方式完成。

于 2011-08-11T20:21:35.993 回答