Подтвердить что ты не робот

HTML5 <audio>/<video> и транскодирование в реальном времени с помощью FFMPEG

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

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

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

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

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

4b9b3361

Ответ 1

Недавно я столкнулся с той же проблемой, так как я хочу обслуживать мою библиотеку для браузеров. Удивительно, но идея отправить поток через ffmpeg и доставить на лету работает достаточно хорошо. Основная проблема заключалась в поддержке поиска...

Ниже вы найдете код sniplets в 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

Спасибо за ответ Camilo. Я более подробно рассмотрел спецификацию 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/*, например. Я дам вам знать, что произойдет.

Ответ 3

AFAIK вы можете кодировать в stdout в ffmpeg. Поэтому вы можете настроить свой HTTP-сервер на:

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

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

На стороне примечания, я думаю, что это подвержено DoS.

Ответ 4

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

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

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