2010-09-03 19 views
18

Więc z mojego serwera WWW, chciałbym użyć FFMPEG przekodować plik multimedialny do pracy z HTML <audio> lub <video> tagu. Łatwo wystarczy?HTML5 <audio>/<video> i transkodowanie na żywo z FFMPEG

Konwersja musiałaby nastąpić w czasie rzeczywistym, gdy klient HTTP zażądał przekonwertowanego pliku. Najlepiej byłoby, gdyby plik był przesyłany strumieniowo z powrotem do klienta HTTP podczas transkodowania (a nie później na końcu, ponieważ może to potrwać jakiś czas zanim jakiekolwiek dane zostaną wysłane z powrotem).

Byłoby dobrze, z wyjątkiem tego, że w dzisiejszych przeglądarkach znacznik audio lub wideo HTML5 żąda pliku multimedialnego w wielu żądaniach HTTP nagłówkiem Range. See this question for details.

W powyższym pytaniu można zobaczyć, że Safari wysyła dziwne fragmenty pliku, w tym kończące się kilka bajtów. Stwarza to problem polegający na tym, że serwer WWW musiał czekać na zakończenie konwersji, aby dostarczyć końcowe bajty pliku, aby spełnić żądanie Range.

Moje pytanie brzmi: czy mój ciąg myśli jest właściwy? Czy istnieje lepszy sposób dostarczania treści transkodowania do znacznika <audio> lub <video>, który nie wymagałby oczekiwania na zakończenie całej konwersji? Z góry dziękuję!

+2

Co się stanie, jeśli 50 osób spróbuje obejrzeć Twój film w tym samym czasie (lub jeśli odświeżę 100 razy)? Transkodowanie w czasie rzeczywistym może działać na jednym wideo na stacji roboczej, ale podejrzewam, że jest to zbyt kosztowne, aby wykonać każde żądanie na serwerze. Może strategia "proszę czekać podczas konwertowania-potem-cache" zadziała? – Seth

+1

Przechowywanie przekonwertowanego pliku jest tym, co już rozważałem w tym momencie. Ale [moja aplikacja] (http://github.com/TooTallNate/nTunes) ma być w każdym razie zablokowana dla kilku użytkowników, ponieważ otwarcie zezwalając na dostęp HTTP do całej biblioteki iTunes jest moim zdaniem niezbyt dobrym pomysłem . – TooTallNate

+0

Chciałbym pójść z podejściem YouTube (sugestia Setha powyżej) - Zgaduję, że w pewnym momencie zadali to pytanie :) – Dolph

Odpowiedz

0

AFAIK można kodować do stdout w ffmpeg. Aby skonfigurować serwer HTTP, należy:

  • rozpocząć kodowanie do pamięci podręcznej po otrzymaniu GET.
  • strumień żądany zakres bajtów do klienta.
  • wypełnianie bufora i używanie go do kolejnych zakresów.

Nie mam pojęcia, ale myślę, że możesz uciec, nie znając długości strumienia końcowego.

Na marginesie, myślę, że jest to podatne na DoS.

2

Dzięki za odpowiedź Camilo. Wziąłem bliżej przyjrzeć się specyfikacji HTTP dotyczące wniosku Zakres Znalezione

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. 

więc jest to naprawdę tylko kwestia testowania przeglądarek jak reagować podczas odpowiadania z Content-Range: bytes 0-1/*, na przykład. Dam ci znać, co się stanie.

+0

Jak można znaleźć zgodność z tym podejściem? Czy to działa dobrze w różnych przeglądarkach? Ostatnim razem, gdy to zrobiłem, stwierdziłem, że lepiej jest "zgadnąć" na całej długości i wypełnić pozostałe, odgadując duże. Wydaje się, że działa to przy stałych wartościach bitrate przyzwoicie. – jocull

+0

Nigdy nie skończyłem, starając się być szczerym. Ktoś jeszcze wie? Ponieważ specyfikacja precyzuje to, może się udać, ale jest to niszowa część specyfikacji, która może wymagać otwarcia błędów na różnych modułach do śledzenia błędów różnych przeglądarek, aby działało poprawnie. – TooTallNate

-1

To powinno być wykonalne poprzez VLC, udało mi się zmusić go do pracy przez ustawienie VLC gospodarzem duży plik avi i przekodować go do OGG, wtedy mój html5 odwołuje strumienia:

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

Było mogłem transkodować w vlc i renderować się dobrze w mojej przeglądarce Chrome i na moim telefonie z Androidem, ale skończyło się na tym, że zabrałem się za different solution, zamiast pracować nad stworzeniem własnej aplikacji do hostowania mojej kolekcji multimediów i tworzenia strumieni dla żądanych plików - Spojrzałem i nie mogłem znaleźć wolnego, który byłby taki, jaki potrzebowałem/polubiłem.

8

Niedawno uruchomiono ten sam problem, ponieważ chcę wyświetlać moją bibliotekę w przeglądarkach. Niespodziewanie pomysł wysłania strumienia przez ffmpeg i dostarczania w locie działa całkiem dobrze. Podstawowym problemem było wsparcie poszukiwania ...

obserwuję, można znaleźć sniplets kodu w Pythonie stosując kolbę do rozwiązania problemu:

Musimy funkcję strumieniowo zawartość:

@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"}) 

Następnie musimy funkcję powrotu czas trwania:

@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) 

I wreszcie, siekać, że w HTML5 przy użyciu 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> 

Przykład działania można znaleźć pod adresem https://github.com/derolf/transcoder.

dero