Heim > Backend-Entwicklung > Python-Tutorial > Videodaten-E/A über den ffmpeg-Unterprozess

Videodaten-E/A über den ffmpeg-Unterprozess

Susan Sarandon
Freigeben: 2024-12-27 21:30:16
Original
807 Leute haben es durchsucht

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.

Video data IO through ffmpeg subprocess

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
Nach dem Login kopieren
Nach dem Login kopieren

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
Nach dem Login kopieren
Nach dem Login kopieren

Es gab sogar eine Erklärung, was jedes dieser Argumente bedeutet, wie im Screenshot unten gezeigt.

Video data IO through ffmpeg subprocess
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
Nach dem Login kopieren
Nach dem Login kopieren

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
Nach dem Login kopieren
Nach dem Login kopieren

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
Nach dem Login kopieren

Mit jedem heruntergeladenen Block,

  1. Wir geben es über process.stdin.write(chunk) an den Prozess weiter.
  2. Sobald wir fertig sind, schreiben wir ein EOF (process.stdin.write_eof()), um das Ende der Dateieingabe anzuzeigen,
  3. gefolgt von einem .close() (und einem entsprechenden waiting .wait_closed())

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))
Nach dem Login kopieren

In einer Schleife, wir

  1. Rufen Sie einen Datenblock von ffmpeg stdout ab
  2. Wenn chunk eine leere Zeichenfolge ist, unterbrechen Sie die Schleife
  3. Andernfalls verschieben Sie den Block in die Warteschlange (über asyncio.to_thread, da wir hier eine prozesssichere Version verwenden)
  4. Dann warten wir darauf, dass der Befehl über „process.wait()“ ordnungsgemäß beendet wird.

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!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage