Als ich meine Jobsuche neu startete (ja, ich bin immer noch #OpenToWork, ping mich an!), wurde ich in einer der Bewerbungen gebeten, einen Prototyp zu implementieren, der Videodaten verarbeitet. Während ich an dem Projekt arbeitete, bekam ich unerwartet viel Hilfe von generativen KI-Chatbots, da ich auf diesem Gebiet relativ unerfahren war.
Wie im Titel erwähnt, wurde ffmpeg verwendet, um einige Vorverarbeitungsarbeiten durchzuführen. Eines der Ziele des Projekts war es, mehrere Videodateien nacheinander abspielen zu können. Obwohl es mehrere Möglichkeiten gibt, dies zu erreichen, habe ich mich für die naheliegendste Lösung entschieden und sie miteinander verknüpft.
$ cat video1 video2 video3 | python further-work.py
Um das zu erreichen, musste ich die Dateien zunächst in Formate umkodieren, die dies ermöglichten. Nachdem ich mit Google Gemini darüber „diskutiert“ hatte, empfahl mir der Chatbot, für diesen Zweck MPEG-TS zu verwenden.
MPEG Transport Stream (MPEG-TS) funktioniert durch die Kapselung paketierter Elementarströme. Diese Streams umfassen Audio-, Video- und PSIP-Daten, die in kleine Segmente paketiert sind. Jeder Stream wird in 188-Byte-Abschnitte zerlegt und miteinander verschachtelt. Dieser Prozess gewährleistet eine geringere Latenz und eine größere Fehlerresistenz und eignet sich daher ideal für Videokonferenzen, bei denen große Frames zu Audioverzögerungen führen können.
Zitiert von https://castr.com/blog/mpeg-transport-stream-mpeg-ts/
Es gibt andere Dateiformate, die für diesen Zweck verwendet werden könnten, aber sie sind für die Diskussion irrelevant. Nachdem ich das Video in diesem Format neu codiert habe, werden die Videodaten an eine Warteschlange gesendet, um von anderen Modulen genutzt zu werden und in anderen Prozessen ausgeführt zu werden.
Nachdem sowohl die Eingabe (eine Liste der online abzurufenden Videodateien) als auch die Ausgabe (neu kodierter Videodateiinhalt) definiert wurden, war es an der Zeit, herauszufinden, wie das geht. Leider ist ffmpeg ein so kompliziertes Dienstprogramm, das so viele Dinge kann. Es gibt/gab mehrere Versuche, eine Benutzeroberfläche bereitzustellen, die den Benutzern dabei hilft (ich wollte das unbedingt ausprobieren, aber es ist jetzt anscheinend tot). Da die generative KI heutzutage jedoch so hilfreich ist, ist es nur ein paar Schritte entfernt, den richtigen Befehl zu erhalten.
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
Es gab sogar eine Erklärung, was jedes dieser Argumente bedeutet, wie im Screenshot unten gezeigt.
Geminis Versuch, den ffmpeg-Befehl zu erklären
Kurz gesagt, der Befehl akzeptiert Videodateiinhalte über stdin und gibt den neu codierten Videodateiinhalt als stdout aus.
Jetzt ist es an der Zeit, die Implementierung zu codieren, da ich gleichzeitig von ffmpeg lesen und schreiben wollte, es sich also um eine asynchrone Anwendung handelt. Die http-Client-Bibliothek, die wir dieses Mal verwenden, ist httpx, die über eine Methode zum Abrufen von Downloads in kleineren Stapeln verfügt:
$ cat video1 video2 video3 | python further-work.py
Wir kümmern uns später um die eigentliche Verarbeitung, da wir jetzt nur den Code erhalten, um die Blöcke auf dem Bildschirm auszugeben.
Als nächstes schreiben wir eine Funktion zum Aufrufen von ffmpeg über asyncio.create_subprocess_exec
ffmpeg -hwaccel cuda -i pipe:0 -c:v h264_nvenc -b:v 1.5M -c:a aac -b:a 128k -f mpegts -y pipe:1
Idealerweise würden wir hier „process.communicate(file_content)“ verwenden, wie in der Dokumentation empfohlen. Leider müssten wir in diesem Fall zuerst die gesamte Datei herunterladen, was die Antwort unweigerlich verzögern würde, was nicht ideal war.
Stattdessen könnten wir „process.stdin.write()“ verwenden. Aktualisieren wir die ursprüngliche Funktion „write_input“:
import httpx client = httpx.AsyncClient() async def write_input( client: httpx.AsyncClient, video_link: str, process: asyncio.subprocess.Process ) -> None: async with client.stream("GET", video_link) as response: async for chunk in response.aiter_raw(1024): print(chunk) # this is the downloaded video file, in chunks
Mit jedem heruntergeladenen Block,
Zurück zur Funktion „video_send“. Wir setzen die Funktion fort, indem wir „process.stdout“ durchlesen. Die Fähigkeit, sowohl lesen als auch schreiben zu können, ist genau der Grund, warum wir dies durch Asyncio tun. Früher konnten wir in der synchronen Einstellung nur eine nach der anderen in einer festen Reihenfolge ausführen, aber jetzt konnten wir den Planer über die Reihenfolge entscheiden lassen. Jetzt wurde der Funktion der folgende Code hinzugefügt, um den neu codierten Dateiinhalt zu lesen und ihn in die Warteschlange zu stellen:
async def video_send(client: httpx.AsyncClient, video_link: str) -> None: logger.info("DATA: Fetching video from link", link=video_link) process = await asyncio.create_subprocess_exec( "ffmpeg", "-hwaccel", "cuda", "-i", "pipe:0", "-c:v", "h264_nvenc", # "libx264", "-b:v", "1.5M", "-c:a", "aac", "-b:a", "128k", "-f", "mpegts", "-y", "pipe:1", stdin=subprocess.PIPE, stdout=subprocess.PIPE, ) asyncio.create_task(write_input(client, video_link, process))
In einer Schleife, wir
Es scheint jetzt sehr einfach zu sein, aber ich habe die ganze Nacht gebraucht, um es tatsächlich richtig hinzubekommen (und ich habe den Code immer noch überarbeitet, während ich das geschrieben habe). Die eine Hälfte der Zeit habe ich die Dokumentation durchgesehen, um sicherzustellen, dass mir nichts entgangen ist, die andere Zeit habe ich Gemini gebeten, meinen Code zu überprüfen.
Hoffentlich finden Sie das nützlich, und das war's für heute. Hoffentlich kehren wir nächste Woche zu den zuvor versprochenen Advent of Code-Inhalten zurück.
Das obige ist der detaillierte Inhalt vonVideodaten-E/A über den ffmpeg-Unterprozess. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!