HTML5 <audio>/ <video>и транскодирование в реальном времени с FFMPEG - PullRequest
20 голосов
/ 04 сентября 2010

Итак, с моего веб-сервера я хотел бы использовать FFMPEG для перекодирования медиа-файла для использования с тегом HTML <audio> или <video>. Достаточно просто, верно?

Преобразование должно происходить в режиме реального времени, когда HTTP-клиент запрашивает преобразованный файл. В идеале файл должен передаваться обратно клиенту HTTP во время его транскодирования (а не после этого в конце, поскольку это может занять некоторое время, прежде чем любые данные начнут отправляться обратно).

Это было бы хорошо, за исключением того, что в современных браузерах тег HTML5 аудио или видео запрашивает мультимедийный файл в нескольких HTTP-запросах с заголовком Range. Подробности см. В этом вопросе .

В этом вопросе, связанном выше, вы можете видеть, что Safari запрашивает странные куски файла, включая последние несколько байтов. Это создает проблему, заключающуюся в том, что веб-сервер ДОЛЖЕН ждать завершения преобразования, чтобы доставить последние байты файла, соответствующие запросу Range.

Так что мой вопрос в том, верен ли мой ход мыслей? Есть ли лучший способ доставки транскодирующего контента в тег <audio> или <video>, который бы не требовал ожидания завершения всего преобразования? Заранее спасибо!

Ответы [ 4 ]

9 голосов
/ 08 мая 2014

Недавно я столкнулся с той же проблемой, поскольку хочу показать свою библиотеку браузерам.Удивительно, но идея отправить поток через 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)

И, наконец, мы взломали это в HTML5 с помощью videojs:

<!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.

dero

2 голосов
/ 10 сентября 2010

Спасибо за ответ Камило . Я внимательно посмотрел на HTTP-спецификацию, касающуюся запроса Range, и обнаружил:

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/* Я дам вам знать, что происходит.

0 голосов
/ 12 августа 2011

Это должно быть выполнимо через VLC , я смог заставить его работать, настроив VLC для размещения большого avi-файла и перекодировать его в OGG, затем мой html5 сослался на поток:

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

Он смог транскодировать в vlc и прекрасно отрисовывать в моем браузере Chrome и на моем телефоне с Android, но в итоге я выбрал другое решение вместо того, чтобы заниматься созданием собственного веб-приложения. для размещения моей медиаколлекции и создания потоков для запрошенных файлов - я посмотрел и не смог найти бесплатную, которая бы делала это так, как мне нужно / понравилось.

0 голосов
/ 09 сентября 2010

AFAIK вы можете кодировать в стандартный вывод в ffmpeg. Таким образом, вы можете настроить свой HTTP-сервер на:

  • начать кодирование в кеш при получении GET.
  • поток запрашиваемого диапазона байтов для клиента.
  • заполнение буфера и использование его для последующих диапазонов.

Я ничего не понимаю, но я думаю, что вы можете уйти, не зная длины последнего потока.

Кстати, я думаю, что это склонно к DoS.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...