So fügen Sie FFmpeg einen Codec hinzu


FFmpeg ist ein großartiges Open Source-Projekt, eine Art Multimedia-Enzyklopädie. Mit FFmpeg können Sie eine Vielzahl von Computer-Multimedia-Aufgaben lösen. Trotzdem besteht manchmal die Notwendigkeit, FFmpeg zu erweitern. Die Standardmethode besteht darin, Änderungen am Projektcode vorzunehmen und anschließend die neue Version zu kompilieren. Der Artikel beschreibt, wie ein neuer Codec hinzugefügt wird. Einige Funktionen zum Anschließen externer Funktionen an FFmpeg werden ebenfalls berücksichtigt. Wenn Sie keinen Codec hinzufügen müssen, kann der Artikel hilfreich sein, um die Architektur von FFmpeg-Codecs und ihre Einstellungen besser zu verstehen. Es wird davon ausgegangen, dass der Leser mit der Architektur von FFmpeg, dem Kompilierungsprozess von FFmpeg, vertraut ist und über Programmiererfahrung mit der FFmpeg-API verfügt. Die Beschreibung gilt für FFmpeg 4.2 "Ada", August 2019.



Inhaltsverzeichnis



Einleitung


Der Codec (Codec, stammt aus der Kombination der Begriffe COder und DECoder) ist ein sehr gebräuchlicher Begriff und seine Bedeutung variiert, wie in solchen Fällen häufig, je nach Kontext etwas. Die primäre Bedeutung ist Software oder Hardware zum Komprimieren / Dekomprimieren von Mediendaten. Anstelle der Begriffe Komprimierung / Dekomprimierung werden häufig die Begriffe Kodierung / Dekodierung verwendet. In einigen Fällen wird unter einem Codec jedoch normalerweise einfach ein Komprimierungsformat verstanden (sie bezeichnen auch das Codec-Format), unabhängig von den für die Komprimierung / Dekomprimierung verwendeten Mitteln. Mal sehen, wie der Begriff Codec in FFmpeg verwendet wird.



1. Codec-Identifikation


FFmpeg-Codecs werden in der libavcodec- Bibliothek kompiliert.



1.1. Codec-ID


Die enum AVCodecID in der enum AVCodecID libavcodec/avcodec.h definiert. Jedes Element dieser Aufzählung gibt das Komprimierungsformat an. Elemente dieser Aufzählung müssen die Form AV_CODEC_ID_XXX , wobei XXX eindeutige Codec- AV_CODEC_ID_XXX Name in Großbuchstaben ist. Hier sind Beispiele für Codec- AV_CODEC_ID_H264 : AV_CODEC_ID_H264 , AV_CODEC_ID_AAC . Für eine detailliertere Beschreibung der Codec- AVCodecDescriptor verwenden Sie die AVCodecDescriptor Struktur (deklariert in libavcodec/avcodec.h , abgekürzt):


 typedef struct AVCodecDescriptor { enum AVCodecID id; enum AVMediaType type; const char *name; const char *long_name; // ... } AVCodecDescriptor; 

Das Schlüsselelement dieser Struktur ist id . Die übrigen Elemente enthalten zusätzliche Informationen zur Codec- id . Jeder Codec-Bezeichner ist eindeutig einem Medientyp ( type ) zugeordnet und verfügt über einen eindeutigen Namen ( name ), der in Kleinbuchstaben geschrieben ist. Ein Array vom Typ AVCodecDescriptor in der Datei libavcodec/codec_desc.c AVCodecDescriptor . Für jede Codec-ID gibt es ein entsprechendes Array-Element. Elemente dieses Arrays sollten nach id Werten sortiert werden, da die binäre Suche für die Suche nach Elementen verwendet wird. Um Informationen über die Codec-ID zu erhalten, können Sie die folgenden Funktionen verwenden:


 const AVCodecDescriptor* avcodec_descriptor_get(enum AVCodecID id); const AVCodecDescriptor* avcodec_descriptor_get_by_name(const char *name); enum AVMediaType avcodec_get_type(enum AVCodecID codec_id); const char* avcodec_get_name(enum AVCodecID id); 


1.2. Codec


Der Codec selbst - eine Reihe von Tools, die zum Codieren / Decodieren von Mediendaten erforderlich sind, kombiniert die AVCodec Struktur (deklariert in libavcodec/avcodec.h ). Hier ist seine gekürzte Version, die weiter unten noch ausführlicher besprochen wird.


 typedef struct AVCodec { const char *name; const char *long_name; enum AVMediaType type; enum AVCodecID id; // ... } AVCodec; 

Das wichtigste Mitglied dieser Struktur ist id , die Codec- id Es gibt auch ein Mitglied, das den Medientyp ( type ) definiert, dessen Wert jedoch mit dem Wert desselben Mitglieds aus AVCodecDescriptor . Codecs werden in zwei Kategorien unterteilt: Codierer, die Medien komprimieren oder codieren, und Decodierer, die den umgekehrten Vorgang ausführen - Dekomprimieren oder Decodieren. (In russischen Texten verwendet der Encoder manchmal anstelle des Begriffs AVCodec aus dem Englischen - dem Encoder.) In AVCodec gibt es kein spezielles AVCodec , das die Codec-Kategorie definiert (obwohl die Kategorie indirekt mit den Funktionen av_codec_is_encoder() und av_codec_is_decoder() , wird diese Kategorie bei der Registrierung festgelegt. Wie dies gemacht wird, wird im Folgenden gezeigt: Mehrere Codecs können dieselbe Codec-ID haben. Wenn sie dieselbe Kategorie haben, müssen sie sich nach dem Namen (Mitgliedsnamen) unterscheiden. Ein Codierer und ein Decodierer, die dieselbe Codec-ID haben, können dies einen haben Derselbe Name, der auch mit dem Namen der Codec-ID übereinstimmen kann (diese Übereinstimmungen sind jedoch optional.) In einer solchen Situation kann es zu Verwirrung kommen, aber es ist nichts zu tun, Sie müssen klar verstehen, zu welcher Entität der Name gehört Der Codec muss eindeutig sein. Um nach registrierten Codecs zu suchen, gibt es Funktionen:


 AVCodec* avcodec_find_encoder_by_name(const char *name); AVCodec* avcodec_find_decoder_by_name(const char *name); AVCodec* avcodec_find_encoder(enum AVCodecID id); AVCodec* avcodec_find_decoder(enum AVCodecID id); 

Da mehrere Codecs denselben Bezeichner haben können, geben die letzten beiden Funktionen einen von ihnen zurück, der als Standard-Codec für einen bestimmten Codec-Bezeichner angesehen werden kann.


Mit dem Befehl kann eine Liste aller registrierten Codecs angefordert werden


ffmpeg -codecs >codecs.txt


Nach dem Ausführen des Befehls enthält die Datei codecs.txt diese Liste. Jede Codec-ID wird durch einen separaten Datensatz (Zeile) dargestellt. Hier zum Beispiel der Eintrag für die Codec-Kennung AV_CODEC_ID_H264 :


DEV.LS
h264
H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
(decoders: h264 h264_qsv h264_cuvid)
(encoders: libx264 libx264rgb h264_amf h264_nvenc h264_qsv nvenc nvenc_h264)


Zu Beginn der Aufnahme stehen spezielle Zeichen zur Verfügung, die die gemeinsamen Merkmale dieser Codec-Kennung bestimmen: D - registrierte Decoder, E - registrierte Encoder, V - für Video verwendet, L - Möglichkeit einer verlustbehafteten Komprimierung, S - Möglichkeit einer verlustfreien Komprimierung. Als nächstes folgt der Name der Codec- h264 ( h264 ), gefolgt von einem langen Namen der Codec- h264 ( H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 ) und einer Liste der Namen registrierter Decoder und Encoder.



2. Hinzufügen eines neuen Codecs zu FFmpeg


Wir werden die Prozedur zum Hinzufügen eines neuen Codecs zu FFmpeg am Beispiel eines Audio-Codecs betrachten, den wir FROX nennen.


Schritt 1. Fügen Sie der enum AVCodecID ein neues Element enum AVCodecID .


Diese Auflistung befindet sich in der libavcodec/avcodec.h . Beim Hinzufügen müssen Sie die folgenden Regeln beachten:


  1. Der Wert eines Elements darf nicht mit den Werten vorhandener Aufzählungselemente übereinstimmen.
  2. Ändern Sie nicht die Werte vorhandener Aufzählungselemente.
  3. Veröffentlichen Sie einen neuen Wert in einer Gruppe ähnlicher Codecs.

Gemäß der Vorlage sollte der Bezeichner dieses Elements AV_CODEC_ID_FROX . AV_CODEC_ID_PCM_S64LE es vor AV_CODEC_ID_PCM_S64LE und geben Sie den Wert 0x10700 .


Schritt 2. Fügen Sie das Element dem Array codec_descriptors (Datei libavcodec/codec_desc.c ).


 static const AVCodecDescriptor codec_descriptors[] = { // ... { .id = AV_CODEC_ID_FROX, .type = AVMEDIA_TYPE_AUDIO, .name = "frox", .long_name = NULL_IF_CONFIG_SMALL("FROX audio"), .props = AV_CODEC_PROP_LOSSLESS, }, // ... }; 

Sie müssen das Element an der richtigen Stelle einfügen. Die Monotonie der Array-Elemente durch den id Wert sollte nicht verletzt werden.


Schritt 3. Definieren Sie die Instanzen von AVCodec separat für den Encoder und den Decoder.


Dazu müssen Sie zunächst die Struktur für den Kontext des Codecs und mehrere Funktionen festlegen, mit denen die eigentliche Codierung / Decodierung und einige andere erforderliche Vorgänge ausgeführt werden. In diesem Abschnitt werden diese Definitionen äußerst schematisch vorgenommen, eine detailliertere Beschreibung erfolgt später. Wir werden den Code in die Datei libavcodec/frox.c


 #include "avcodec.h" // context typedef struct FroxContext { // ... } FroxContext; // decoder static int frox_decode_init(AVCodecContext *codec_ctx) { return -1; } static int frox_decode_close(AVCodecContext *codec_ctx) { return -1; } static int frox_decode(AVCodecContext *codec_ctx, void* outdata, int *outdata_size, AVPacket *pkt) { return -1; } AVCodec ff_frox_decoder = { .name = "frox_dec", .long_name = NULL_IF_CONFIG_SMALL("FROX audio decoder"), .type = AVMEDIA_TYPE_AUDIO, .id = AV_CODEC_ID_FROX, .priv_data_size = sizeof(FroxContext), .init = frox_decode_init, .close = frox_decode_close, .decode = frox_decode, .capabilities = AV_CODEC_CAP_LOSSLESS, .sample_fmts = (const enum AVSampleFormat[]) {AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE}, .channel_layouts = (const int64_t[]) {AV_CH_LAYOUT_MONO, 0 }, }; // encoder static int frox_encode_init(AVCodecContext *codec_ctx) { return -1; } static int frox_encode_close(AVCodecContext *codec_ctx) { return -1; } static int frox_encode(AVCodecContext *codec_ctx, AVPacket *pkt, const AVFrame *frame, int *got_pkt_ptr) { return -1; } AVCodec ff_frox_encoder = { .name = "frox_enc", .long_name = NULL_IF_CONFIG_SMALL("FROX audio encoder"), .type = AVMEDIA_TYPE_AUDIO, .id = AV_CODEC_ID_FROX, .priv_data_size = sizeof(FroxContext), .init = frox_encode_init, .close = frox_encode_close, .encode2 = frox_encode, .sample_fmts = (const enum AVSampleFormat[]) {AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE}, .channel_layouts = (const int64_t[]) {AV_CH_LAYOUT_MONO, 0 }, }; 

Der Einfachheit halber haben in diesem Beispiel der Codierer und der Decodierer denselben Kontext - FroxContext , aber meistens haben der Codierer und der Decodierer unterschiedliche Kontexte. Beachten Sie auch, dass AVCodec einem speziellen Muster folgen müssen.


Schritt 4. Fügen AVCodec der Registrierungsliste Instanzen von AVCodec .


Gehen Sie zur Datei libavcodec/allcodecs.c . Am Anfang dieser Datei befindet sich eine Liste der Deklarationen aller registrierten Codecs. Fügen Sie unsere Codecs zu dieser Liste hinzu:


 extern AVCodec ff_frox_decoder; extern AVCodec ff_frox_encoder; 

Während der Ausführung findet das configure Skript alle derartigen Deklarationen und generiert die libavcodec/codec_list.c , die ein Array von Zeigern auf die in libavcodec/allcodecs.c deklarierten Codecs libavcodec/allcodecs.c . Nach dem Ausführen des Skripts in der Datei libavcodec/codec_list.c wir:


 static const AVCodec * const codec_list[] = { // ... &ff_frox_encoder, // ... &ff_frox_decoder, // ... NULL }; 

Während der Ausführung des configure Skripts wird auch die config.h Datei config.h , in der sich die Deklarationen befinden


 #define CONFIG_FROX_DECODER 1 #define CONFIG_FROX_ENCODER 1 

Schritt 5. Bearbeiten Sie libavcodec/Makefile


Öffnen Sie libavcodec/Makefile . Wir finden den Abschnitt # decoders/encoders und fügen dort hinzu


 OBJS-$(CONFIG_FROX_DECODER) += frox.o OBJS-$(CONFIG_FROX_ENCODER) += frox.o 

Schritt 6. Bearbeiten Sie den Code des Multiplexers und Demultiplexers.


Der Multiplexer (Muxer) und der Demultiplexer (Demuxer) müssen den neuen Codec „kennen“. Beim Aufzeichnen ist es erforderlich, die Identifizierungsinformationen für diesen Codec aufzuzeichnen, während beim Lesen die Kennung des Codec aus den Identifizierungsinformationen ermittelt wird. matroska müssen Sie für das matroska Format ( *.mkv ) tun.


1. libavformat/matroska.c in der Datei libavformat/matroska.c dem Array libavformat/matroska.c ein Element für den neuen Codec libavformat/matroska.c :


 const CodecTags ff_mkv_codec_tags[] = { // ... {"A_FROX", AV_CODEC_ID_FROX}, // ... }; 

String "A_FROX" und wird vom Multiplexer als Identifikationsinformation in die Datei geschrieben. In diesem Array ist es der Codec-Kennung zugeordnet, daher kann der Demultiplexer sie beim Lesen leicht ermitteln. Der Demultiplexer schreibt die Codec- codec_id Mitglied codec_id Struktur codec_id . Ein Zeiger auf diese Struktur ist ein Mitglied der AVStream Struktur.


2. libavformat/matroskaenc.c in der Datei libavformat/matroskaenc.c das Element zum Array additional_audio_tags :


 static const AVCodecTag additional_audio_tags[] = { // ... { AV_CODEC_ID_FROX, 0XFFFFFFFF }, // ... }; 

Also ist alles fertig. Führen Sie zuerst das configure . Danach müssen Sie sicherstellen, dass die oben beschriebenen Änderungen in den Dateien libavcodec/codec_list.c und config.h vorgenommen werden. Dann können Sie die Kompilierung ausführen:


make clean
make


Wenn die Kompilierung ffmpeg.exe , wird die ausführbare Datei ffmpeg (oder ffmpeg.exe , wenn das ffmpeg.exe Windows ist) angezeigt. Führen Sie den Befehl aus


./ffmpeg -codecs >codecs.txt


und stellen Sie sicher, dass FFmpeg unsere neuen Codecs "sieht", wir finden den Eintrag in der Datei codecs.txt


DEA..S frox FROX audio (decoders: frox_dec) (encoders: frox_enc)



3. Detaillierte Beschreibung des Kontexts und der erforderlichen Funktionen


In diesem Abschnitt beschreiben wir detaillierter, wie die Struktur des Codec-Kontexts und die erforderlichen Funktionen aussehen können.



3.1. Codec-Kontext


Der Codec-Kontext unterstützt möglicherweise die Installation von Optionen. Bei Encodern wird diese Unterstützung häufig genug verwendet, bei Decodern seltener. Die Struktur, die die Installation von Optionen unterstützt, sollte als erstes Mitglied einen Zeiger auf die AVClass Struktur und dann auf die Optionen selbst haben.


 #include "libavutil/opt.h" typedef struct FroxContext { const AVClass *av_class; int frox_int; char *frox_str; uint8_t *frox_bin; int bin_size; } FroxContext; 

Als nächstes müssen Sie ein Array vom Typ AVOption , von dem jedes Element eine bestimmte Option beschreibt.


 static const AVOption frox_options[] = { { "frox_int", "This is a demo option of int type.", offsetof(FroxContext, frox_int), AV_OPT_TYPE_INT, { .i64 = -1 }, 1, SHRT_MAX }, { "frox_str", "This is a demo option of string type.", offsetof(FroxContext, frox_str), AV_OPT_TYPE_STRING }, { "frox_bin", "This is a demo option of binary type.", offsetof(FroxContext, frox_bin), AV_OPT_TYPE_BINARY }, { NULL }, }; 

Für jede Option müssen Sie einen Namen, eine Beschreibung, einen Versatz in der Struktur und einen Typ definieren. Sie können auch einen Standardwert und für Ganzzahloptionen einen Bereich gültiger Werte definieren.


Als Nächstes müssen Sie eine Instanz vom Typ AVClass .


 static const AVClass frox_class = { .class_name = "FroxContext", .item_name = av_default_item_name, .option = frox_options, .version = LIBAVUTIL_VERSION_INT, }; 

Ein Zeiger auf diese Instanz muss verwendet werden, um das entsprechende AVCodec Mitglied zu initialisieren.


 AVCodec ff_frox_decoder = { // ... .priv_data_size = sizeof(FroxContext), .priv_class = &frox_class, // ... }; AVCodec ff_frox_encoder = { // ... .priv_data_size = sizeof(FroxContext), .priv_class = &frox_class, // ... }; 

Jetzt beim Ausführen der Funktion


 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec); 

Eine Instanz der AVCodecContext Struktur wird AVCodecContext und das codec Mitglied wird initialisiert. Als nächstes wird basierend auf dem Wert codec->priv_data_size der erforderliche Speicher für die FroxContext Instanz zugewiesen, wobei der Wert codec->priv_class erste Mitglied dieser Instanz wird initialisiert und anschließend die Funktion av_opt_set_defaults() aufgerufen, mit der die Standardwerte für die Optionen festgelegt werden. Ein Zeiger auf eine Instanz von FroxContext ist über das Mitglied priv_data Struktur FroxContext verfügbar.


Bei der Arbeit mit der FFmpeg-API können Werte für Optionen direkt festgelegt werden.


 const AVCodec *codec; // ... AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); // ... av_opt_set(codec_ctx->priv_data, "frox_str", "meow", 0); av_opt_set_int(codec_ctx->priv_data, "frox_int", 42, 0); 

Eine andere Möglichkeit ist die Verwendung des avcodec_open2() , das beim Aufruf von avcodec_open2() als drittes Argument avcodec_open2() (siehe unten).


Funktion verwenden


 const AVOption* av_opt_next(const void* ctx, const AVOption* prev); 

Sie können eine Liste aller vom Codec-Kontext unterstützten Optionen abrufen. Dies ist nützlich, wenn Sie einen Codec untersuchen. Vorher müssen Sie jedoch sicherstellen, dass codec_ctx->codec->priv_class auf einen Wert ungleich Null gesetzt ist, da der Kontext sonst keine Optionen unterstützt und jede Operation mit Optionen das Programm zum Absturz bringt.



3.2. Funktionen


Lassen Sie uns nun genauer untersuchen, wie die Funktionen, die bei der Initialisierung des Codecs und der tatsächlichen Codierung / Decodierung verwendet werden, angeordnet sind. Normalerweise müssen sie immer einen Zeiger auf einen FroxContext .


 AVCodecContext *codec_ctx; // ... FroxContext* frox_ctx = codec_ctx->priv_data; 

Die Funktionen frox_decode_init() und frox_encode_init() werden aufgerufen, wenn die Funktion ausgeführt wird


 int avcodec_open2( AVCodecContext *codec_ctx, const AVCodec *codec, AVDictionary **options); 

Sie müssen die erforderlichen Ressourcen zuweisen, damit der Codec funktioniert, und bei Bedarf einige Mitglieder der AVCodecContext Struktur initialisieren, z. B. frame_size für einen Audio- frame_size .


Die Funktionen frox_decode_close() und frox_encode_close() werden bei der Ausführung aufgerufen


 int avcodec_close(AVCodecContext *codec_ctx); 

Sie müssen die zugewiesenen Ressourcen freigeben.


Betrachten Sie eine Funktion zum Implementieren der Decodierung


 int frox_decode( AVCodecContext *codec_ctx, void *outdata, int *outdata_size, AVPacket *pkt); 

Sie sollte die folgenden Operationen implementieren:


  1. Tatsächliche Dekodierung;
  2. Zuweisung des notwendigen Puffers für den Ausgaberahmen;
  3. Kopieren Sie dekodierte Daten in den Bildspeicher.

Überlegen Sie, wie Sie den erforderlichen Puffer für den Ausgaberahmen zuweisen. Der Parameter outdata verweist tatsächlich auf einen AVFrame . Sie müssen also zuerst eine Typkonvertierung durchführen:


 AVFrame* frm = outdata; 

Als nächstes müssen Sie einen Puffer zum Speichern von Rahmendaten zuweisen. Initialisieren Sie dazu die AVFrame Mitglieder, die die AVFrame bestimmen. Für Audio ist dies nb_samples , channel_layout , format (für nb_samples , nb_samples und channel_layout ).


Danach müssen Sie die Funktion aufrufen


 int av_frame_get_buffer(AVFrame* frm, int alignment); 

Der Zeiger auf den Frame, bei dem es sich um den konvertierten outdata Parameter handelt, wird als erstes Argument verwendet, und es wird empfohlen, als zweites Argument Null zu übergeben. Nach Verwendung des Frames (dies geschieht bereits außerhalb des Codecs) wird der von dieser Funktion zugewiesene Puffer von der Funktion freigegeben


 void av_frame_unref(AVFrame* frm); 

Die Funktion frox_decode() sollte die Anzahl der zum Dekodieren verwendeten Bytes aus dem Paket zurückgeben, auf das pkt . Wenn die Rahmenbildung abgeschlossen ist, wird der Variablen, auf die outdata_size , ein Wert ungleich Null zugewiesen, andernfalls erhält diese Variable den Wert 0 .


Betrachten Sie eine Funktion zum Implementieren der Codierung


 int frox_encode( AVCodecContext *codec_ctx, AVPacket *pkt, const AVFrame *frame, int *got_pkt_ptr); 

Sie sollte die folgenden Operationen implementieren:


  1. Tatsächliche Kodierung;
  2. Zuweisung des notwendigen Puffers für das Ausgabepaket;
  3. Kopieren Sie verschlüsselte Daten in den Paketpuffer.

Verwenden Sie die Funktion, um den gewünschten Puffer auszuwählen


 int av_new_packet(AVPacket *pkt, int pack_size); 

Der Parameter pkt als erstes Argument verwendet, und die Größe der codierten Daten ist das zweite. Nach der Verwendung des Pakets (dies geschieht bereits außerhalb des Codecs) werden die von dieser Funktion zugewiesenen Puffer von der Funktion freigegeben


 void av_packet_unref(AVPacket *pkt); 

Wenn das Paket abgeschlossen ist, wird der Variablen, auf die got_pkt_ptr , ein Wert ungleich Null zugewiesen, andernfalls erhält diese Variable den Wert 0 . Liegt kein Fehler vor, gibt die Funktion Null zurück, andernfalls einen Fehlercode.


Bei der Implementierung des Codecs wird normalerweise die Protokollierung verwendet (bei Fehlern kann dies als obligatorische Anforderung angesehen werden). Hier ist ein Beispiel:


 static int frox_decode_close(AVCodecContext *codec_ctx) { av_log(codec_ctx, AV_LOG_INFO, "FROX decode close\n"); // ... } 

In diesem Fall wird bei der Ausgabe in das Protokoll der Codec-Name als Kontextname verwendet.



3.3. Zeitstempel


Zum Einstellen der Zeit in FFmpeg wird eine Zeitbasis verwendet, die in Sekunden unter Verwendung der durch den AVRational Typ dargestellten rationalen Zahl AVRational . (Ein ähnlicher Ansatz wird in C ++ 11 verwendet. 1/1000 setzt beispielsweise die Millisekunde.) Frames und Pakete haben Zeitstempel vom Typ int64_t , deren Werte die Zeit in den entsprechenden Zeiteinheiten enthalten. Ein Frame, AVFrame eine AVFrame Struktur, verfügt über einen Member pts (Präsentationszeitstempel), dessen Wert die relative Zeit der im Frame erfassten Szene bestimmt. Ein Paket, AVPacket eine AVPacket Struktur, enthält die Member AVPacket (Präsentationszeitstempel) und dts (Dekomprimierungszeitstempel). Der Wert dts bestimmt die relative Übertragungszeit des zu decodierenden Pakets. Für einfache Codecs ist es dasselbe wie für h264 , aber für komplexe Codecs kann es anders sein (z. B. für h264 bei Verwendung von B-Frames), h264 , Pakete können in der falschen Reihenfolge decodiert werden, in der Frames verwendet werden sollen.


Die Zeiteinheit ist für den Stream und den Codec definiert, die AVStream Struktur hat ein entsprechendes Member - time_base , dasselbe Member hat die AVCodecContext Struktur.


Die Zeitstempel des mit av_read_frame() aus dem Stream extrahierten Pakets werden in Zeiteinheiten dieses Streams angegeben. Bei der Dekodierung wird die Zeiteinheit des Codecs nicht verwendet. Für einen Videodecoder ist dies normalerweise einfach nicht festgelegt, für einen Audiodecoder hat dies einen Standardwert - die Inverse der Abtastfrequenz. Der Decoder sollte einen Zeitstempel für den Ausgaberahmen basierend auf dem Zeitstempel des Pakets festlegen. FFmpeg definiert ein solches Label unabhängig und schreibt es in das best_effort_timestamp der best_effort_timestamp Struktur. Alle diese Zeitstempel verwenden die Zeiteinheit des Streams, aus dem das Paket extrahiert wird.


Für den Encoder müssen Sie die Zeiteinheit angeben. In dem Clientcode, der die Dekodierung organisiert, müssen Sie den Wert für das time_base Struktur avcodec_open2() time_base , bevor Sie avcodec_open2() aufrufen. Verwenden Sie normalerweise die Zeiteinheit, die für Zeitstempel des codierten Frames verwendet wird. Geschieht dies nicht, geben Video-Encoder normalerweise einen Fehler aus, Audio-Encoder setzen den Standardwert - die Inverse der Abtastfrequenz. Ob ein Codec eine bestimmte Zeiteinheit ändern kann, ist nicht ganz klar. Für alle Fälle ist es besser, nach dem Aufruf von avcodec_open2() immer den Wert von avcodec_open2() zu überprüfen und, falls er sich geändert hat, die Zeitstempel der Eingaberahmen pro Zeiteinheit des Codecs neu zu berechnen. dts Kodierungsprozesses müssen Sie die pts und pts dts Pakets installieren. Nach dem Codieren muss vor dem Schreiben eines Pakets in den Ausgabestream der Paketzeitstempel von der Codec-Zeiteinheit in die Stream-Zeiteinheit neu berechnet werden. Verwenden Sie dazu die Funktion


 void av_packet_rescale_ts( AVPacket *pkt, AVRational tb_src, AVRational tb_dst); 

Beim Schreiben von Paketen in den Stream ist darauf zu achten, dass die dts Werte strikt ansteigen, da sonst der Multiplexer einen Fehler auslöst. (Weitere Informationen finden Sie in der Dokumentation zur Funktion av_interleaved_write_frame() .)



3.4. Andere vom Codec verwendete Funktionen


Wenn Sie eine AVCodec Instanz initialisieren, können zwei weitere Funktionen registriert werden. Hier sind die relevanten Mitglieder von AVCodec :


 typedef struct AVCodec { // ... void (*init_static_data)(AVCodec *codec); void (*flush)(AVCodecContext *codec_ctx); // ... } AVCodec; 

Der erste von ihnen wird einmalig bei der Registrierung des Codecs aufgerufen.


Der zweite setzt den internen Zustand des Codecs zurück, der während der Ausführung der Funktion aufgerufen wird


 void avcodec_flush_buffers(AVCodecContext *codec_ctx); 

Dieser Aufruf ist beispielsweise erforderlich, wenn die aktuelle Wiedergabeposition gewaltsam geändert wird.



4. Externe Implementierung des Codecs



4.1. Externer Funktionsanschluss


Betrachten Sie die folgende Codec-Organisation: Der in FFmpeg registrierte Codec spielt die Rolle eines Frameworks und delegiert die eigentliche Codierungs- / Decodierungsprozedur an externe Funktionen (eine Art von Plugins), die außerhalb von FFmpeg implementiert sind.


Eine solche Lösung kann aus vielen Gründen wünschenswert sein. Hier sind einige von ihnen:


  1. , FFmpeg ;
  2. C, , C++;
  3. framework, FFmpeg.

, FFmpeg «», FFmpeg API. «» FFmpeg ( , ), . — . .


 typedef int(*dec_extern_t)(const void*, int, void*); static int frox_decode( AVCodecContext* codec_ctx, void* outdata, int *outdata_size, AVPacket* pkt) { int ret = -1; void* out_buff; //      out_buff FroxContext *fc = codec_ctx->priv_data; if (fc->bin_size > 0) { if (fc->bin_size == sizeof(dec_extern_t)) { dec_extern_t edec; memcpy(&edec, fc->frox_bin, fc->bin_size); ret = (*edec)(pkt->data, pkt->size, out_buff); if (ret >= 0) { //     out_buff   } } else { /*  */ } } else { /*    */ } // ... return ret; } 

FFmpeg API ( C++) .


 extern "C" { int DecodeFroxData(const void* buff, int size, void* outBuff); typedef int(*dec_extern_t)(const void*, int, void*); #include <libavcodec/avcodec.h> #include <libavutil/opt.h> } // ... AVCodecContext* ctx; // ... dec_extern_t dec = DecodeFroxData; void* pv = &dec; auto pb = static_cast<const uint8_t*>(pv); auto sz = sizeof(dec); av_opt_set_bin(ctx->priv_data, "frox_bin", pb, sz, 0); 


4.2.


— . , . , . , , FFmpeg , «» , . . , . FFmpeg API - , , . . , . PC (Windows) DirectShow AVI . PC - DirectShow. 32- FourCC. ( biCompression BITMAPINFOHEADER .) , DirectShow , PC -. FFmpeg , , , codec_tag AVCodecParameters FourCC, . FFmpeg API , . FFmpeg FFmpeg API.


, *.mkv FFmpeg ( ENCODER ).



Fazit


, , FFmpeg: , changelog, .. «» FFmpeg, , .



Ressourcen


FFmpeg


[1] FFmpeg —
[2] FFmpeg —
[3] FFmpeg —
[4] FFmpeg — Ubuntu



[5] FFmpeg Compilation Guide
[6] Compilation of FFmpeg 4.0 in Windows 10


FFmpeg API


[7] ffmpeg



[8] FFmpeg codec HOWTO
[9] FFmpeg video codec tutorial




Source: https://habr.com/ru/post/de480714/


All Articles