كيفية إضافة برنامج الترميز إلى FFmpeg


FFmpeg هو مشروع مفتوح المصدر كبير ، وهو نوع من موسوعة الوسائط المتعددة. مع FFmpeg ، يمكنك حل عدد كبير من مهام الوسائط المتعددة للكمبيوتر. ولكن لا يزال ، في بعض الأحيان هناك حاجة لتوسيع FFmpeg. الطريقة القياسية هي إجراء تغييرات على رمز المشروع ومن ثم ترجمة الإصدار الجديد. تفاصيل المقالة كيفية إضافة برنامج ترميز جديد. بعض الميزات لربط الوظائف الخارجية مع FFmpeg تعتبر أيضا. إذا لم تكن هناك حاجة لإضافة برنامج ترميز ، فقد تكون المقالة مفيدة لفهم أفضل لبنية برامج ترميز FFmpeg وإعداداتها. من المفترض أن يكون القارئ معتادًا على بنية FFmpeg ، وعملية تجميع FFmpeg ، ولديه أيضًا خبرة برمجية باستخدام API FFmpeg. الوصف صالح لـ FFmpeg 4.2 "Ada" ، أغسطس 2019.



جدول المحتويات



مقدمة


يعد برنامج الترميز (برنامج الترميز) الذي يأتي من مجموعة المصطلحين COder و DECoder) مصطلحًا شائعًا للغاية ، وكما يحدث غالبًا في مثل هذه الحالات ، يختلف معناه بعض الشيء حسب السياق. المعنى الأساسي هو البرنامج أو الأجهزة لضغط / إلغاء ضغط بيانات الوسائط. بدلاً من المصطلحات compression / decompression ، غالبًا ما تستخدم المصطلحات encoding / decoding. ولكن في بعض الحالات ، يُفهم عادةً أن برنامج الترميز يعني مجرد تنسيق ضغط (كما يقول تنسيق برنامج الترميز) ، بغض النظر عن الوسائل المستخدمة للضغط / إلغاء الضغط. دعونا نرى كيف يتم استخدام مصطلح الترميز في FFmpeg.



1. تحديد الترميز


يتم تصنيف برامج الترميز FFmpeg في مكتبة libavcodec .



1.1. كود الترميز


enum AVCodecID تعريف enum AVCodecID في enum AVCodecID libavcodec/avcodec.h . يحدد كل عنصر من عناصر هذا التعداد تنسيق الضغط. يجب أن تكون عناصر هذا التعداد من النوع AV_CODEC_ID_XXX ، حيث XXX اسم معرف برنامج الترميز الفريد في الحالة العليا. فيما يلي أمثلة AV_CODEC_ID_H264 برنامج الترميز: AV_CODEC_ID_H264 ، AV_CODEC_ID_AAC . للحصول على وصف أكثر تفصيلًا لمعرّف برنامج الترميز ، استخدم بنية AVCodecDescriptor (تم الإعلان عنها في libavcodec/avcodec.h ، libavcodec/avcodec.h في شكل مختصر):


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

العضو الرئيسي في هذه البنية هو id ، أما باقي الأعضاء فيقدمون معلومات إضافية حول معرّف برنامج الترميز. يرتبط كل معرف لبرنامج الترميز بشكل فريد بنوع الوسائط (عضو في type ) وله اسم فريد (عضو في name ) ، وهو مكتوب في الحالة الصغيرة. يتم تعريف صفيف من النوع AVCodecDescriptor في الملف libavcodec/codec_desc.c AVCodecDescriptor . لكل معرف برنامج الترميز ، هناك عنصر صفيف المقابلة. يجب أن يتم ترتيب عناصر هذه المجموعة حسب قيم id ، حيث يتم استخدام البحث الثنائي للبحث عن العناصر. للحصول على معلومات حول معرّف برنامج الترميز ، يمكنك استخدام الوظائف:


 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. الترميز


برنامج الترميز نفسه - مجموعة من الأدوات اللازمة لتنفيذ تشفير / فك تشفير بيانات الوسائط ، يجمع بين بنية AVCodec (تم الإعلان عنها في libavcodec/avcodec.h ). فيما يلي نسخته المختصرة ، ستتم مناقشة الأكثر اكتمالا أدناه.


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

أهم عضو في هذه البنية هو id ، id الكودك ، وهناك أيضًا عضو يحدد نوع الوسائط ( type ) ، ولكن يجب أن تتطابق قيمته مع قيمة نفس العضو من AVCodecDescriptor . وتنقسم برامج الترميز إلى فئتين: التشفير ، الذي يضغط أو يشفر الوسائط ، وأجهزة فك التشفير ، التي تؤدي العملية المعاكسة - فك الضغط أو فك التشفير. (في بعض الأحيان ، في النصوص الروسية ، يستخدم المشفر ورقة التتبع من الإنجليزية - المشفر.) لا يوجد عضو خاص في AVCodec يحدد فئة برنامج الترميز (على الرغم من أنه يمكن تحديد الفئة بشكل غير مباشر باستخدام الدالتين av_codec_is_encoder() و av_codec_is_decoder() ويتم تحديدهما أثناء التسجيل. سيتم عرض كيفية القيام بذلك: يمكن أن يكون للعديد من برامج الترميز نفس معرف برنامج الترميز ، وإذا كان لديهم نفس الفئة ، فيجب أن يختلفوا حسب الاسم ( name العضو) ، ويمكن لجهاز التشفير وفك الشفرة الذي يحمل نفس معرف برنامج الترميز أن يكون واحد نفس الاسم ، والذي قد يتزامن أيضًا مع اسم معرف برنامج الترميز (ولكن هذه التطابقات اختيارية) ، قد يؤدي هذا الموقف إلى بعض الالتباس ، ولكن لا يوجد شيء يجب القيام به ، يجب أن تفهم بوضوح الكيان الذي ينتمي إليه الاسم. ضمن فئة واحدة ، الاسم يجب أن يكون برنامج الترميز فريداً ، وللبحث عن برامج الترميز المسجلة ، هناك وظائف:


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

نظرًا لأن العديد من برامج الترميز يمكن أن يكون لها نفس المعرف ، فإن الدالتين الأخيرتين تقومان بإرجاع واحدة منها ، والتي يمكن اعتبارها برنامج الترميز الافتراضي لمعرّف برنامج ترميز معين.


يمكن طلب قائمة بجميع برامج الترميز المسجلة باستخدام الأمر


ffmpeg -codecs >codecs.txt


بعد تنفيذ الأمر ، codecs.txt ملف codecs.txt على هذه القائمة. سيتم تمثيل كل معرّف برنامج الترميز بسجل منفصل (سطر). هنا ، على سبيل المثال ، إدخال معرف برنامج الترميز 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)


في بداية التسجيل ، هناك أحرف خاصة تحدد الميزات الشائعة المتوفرة لمعرّف الترميز: يتم تسجيل وحدات فك الترميز ، ويتم تسجيل أجهزة التشفير E ، يتم استخدام V - للفيديو ، L - هناك إمكانية ضغط مع خسائر ، S - هناك إمكانية ضغط دون خسائر. بعد ذلك يأتي اسم معرف الكودك ( h264 ) ، متبوعًا باسم معرف الكودك الطويل ( H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 ) ، ثم قائمة بأسماء وحدات فك التشفير وأجهزة التشفير المسجلة.



2. إضافة برنامج ترميز جديد إلى FFmpeg


سننظر في الإجراء الخاص بإضافة برنامج ترميز جديد إلى FFmpeg باستخدام مثال لبرنامج ترميز صوتي ، والذي FROX عليه FROX .


الخطوة 1. إضافة عنصر جديد إلى enum AVCodecID .


هذه القائمة موجودة في libavcodec/avcodec.h . عند الإضافة ، يجب عليك اتباع القواعد:


  1. يجب ألا تتزامن قيمة العنصر مع قيم عناصر التعداد الحالية ؛
  2. لا تقم بتغيير قيم عناصر التعداد الحالية ؛
  3. نشر قيمة جديدة في مجموعة من برامج الترميز المشابهة.

وفقًا للقالب ، يجب أن يكون معرف هذا العنصر هو AV_CODEC_ID_FROX . AV_CODEC_ID_PCM_S64LE قبل AV_CODEC_ID_PCM_S64LE واعط القيمة 0x10700 .


الخطوة 2. إضافة العنصر إلى صفيف codec_descriptors (ملف 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, }, // ... }; 

تحتاج إلى إضافة العنصر إلى المكان "الصحيح" ، ويجب عدم انتهاك رتابة عناصر الصفيف بواسطة قيمة id .


الخطوة 3. تحديد مثيلات AVCodec بشكل منفصل لجهاز التشفير وفك التشفير.


للقيام بذلك ، تحتاج أولاً إلى تحديد بنية سياق برنامج الترميز والعديد من الوظائف التي ستؤدي الترميز / فك الترميز الفعلي وبعض العمليات الضرورية الأخرى. في هذا القسم ، سيتم إجراء هذه التعريفات بشكل تخطيطي للغاية ؛ وسيتم تقديم وصف أكثر تفصيلًا لاحقًا. libavcodec/frox.c الكود في ملف 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 }, }; 

بالنسبة للبساطة ، في هذا المثال ، يكون لكل من برنامج التشفير وفك الشفرة نفس السياق - FroxContext ، ولكن في معظم الأحيان يكون لكل من برنامج التشفير وفك الشفرة سياقات مختلفة. لاحظ أيضًا أن AVCodec مثيل AVCodec يجب أن تتبع نمطًا خاصًا.


الخطوة 4. إضافة مثيلات AVCodec إلى قائمة التسجيل.


انتقل إلى ملف libavcodec/allcodecs.c . في بداية هذا الملف توجد قائمة بالإعلانات لجميع برامج الترميز المسجلة. أضف برامج الترميز الخاصة بنا إلى هذه القائمة:


 extern AVCodec ff_frox_decoder; extern AVCodec ff_frox_encoder; 

أثناء التنفيذ ، يقوم البرنامج النصي configure بالبحث عن كل هذه التعريفات ويقوم بإنشاء libavcodec/codec_list.c ، والذي يحتوي على مجموعة من المؤشرات إلى برامج الترميز المعلنة في libavcodec/allcodecs.c . بعد تنفيذ البرنامج النصي في ملف libavcodec/codec_list.c سنرى:


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

أيضًا ، أثناء تنفيذ البرنامج النصي configure ، يتم config.h ملف config.h ، حيث نجد الإعلانات


 #define CONFIG_FROX_DECODER 1 #define CONFIG_FROX_ENCODER 1 

الخطوة 5. تحرير libavcodec/Makefile


فتح libavcodec/Makefile . نجد القسم # decoders/encoders ، ونضيف هناك


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

الخطوة 6. تحرير رمز المضاعف و demultiplexer.


يجب أن يعرف "مُضاعِف الإرسال" (muxer) و demultiplexer (demuxer) برنامج الترميز الجديد. عند التسجيل ، من الضروري تسجيل معلومات التعريف الخاصة ببرنامج الترميز هذا ، أثناء القراءة ، تحديد معرف برنامج الترميز من معلومات التعريف. إليك ما تحتاج إلى القيام به لتنسيق matroska ( *.mkv matroska *.mkv ).


1. في ملف libavformat/matroska.c ، أضف عنصرًا لبرنامج الترميز الجديد إلى صفيف libavformat/matroska.c :


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

سلسلة "A_FROX" وسيتم كتابتها بواسطة المضاعف إلى الملف "A_FROX" تعريف. في هذه المصفوفة ، يتم ربطها بمعرّف برنامج الترميز ، وبالتالي ، عند القراءة ، يمكن لمزيل تعدد الإرسال تحديده بسهولة. يكتب demultiplexer معرف برنامج الترميز لعضو codec_id بنية codec_id . مؤشر هذا الهيكل هو عضو في بنية AVStream .


2. في الملف libavformat/matroskaenc.c ، أضف العنصر إلى صفيف libavformat/matroskaenc.c :


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

لذلك كل شيء جاهز. أولاً ، قم بتشغيل configure البرنامج النصي. بعد ذلك ، تحتاج إلى التأكد من إجراء التغييرات الموضحة أعلاه في الملفات libavcodec/codec_list.c و config.h . ثم يمكنك تشغيل الترجمة:


make clean
make


إذا تمت عملية ffmpeg.exe ، ffmpeg.exe ffmpeg القابل للتنفيذ (أو ffmpeg.exe ، إذا كان نظام التشغيل الهدف هو Windows). تنفيذ الأمر


./ffmpeg -codecs >codecs.txt


وتأكد من أن FFmpeg "يرى" برامج الترميز الجديدة الخاصة بنا ، نجد الإدخال في ملف codecs.txt


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



3. وصف مفصل للسياق والوظائف المطلوبة


في هذا القسم ، وصفنا بمزيد من التفصيل شكل بنية سياق الترميز والوظائف اللازمة.



3.1. سياق الترميز


قد يدعم سياق برنامج الترميز تثبيت الخيارات. بالنسبة لأجهزة التشفير ، يتم استخدام هذا الدعم في كثير من الأحيان بما فيه الكفاية ، لأجهزة فك التشفير أقل كثيرًا. يجب أن يكون للهيكل الذي يدعم تثبيت الخيارات مؤشرًا إلى بنية AVClass كأول عضو ثم للخيارات نفسها.


 #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; 

بعد ذلك ، تحتاج إلى تحديد مجموعة من النوع AVOption ، AVOption كل عنصر منها خيارًا محددًا.


 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 }, }; 

لكل خيار ، يجب عليك تحديد الاسم والوصف والإزاحة في الهيكل والنوع. يمكنك أيضًا تحديد قيمة افتراضية ولخيارات عدد صحيح مجموعة من القيم الصالحة.


بعد ذلك ، تحتاج إلى تعريف مثيل من نوع AVClass .


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

يجب استخدام مؤشر لهذا المثيل لتهيئة عضو AVCodec المقابل.


 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, // ... }; 

الآن عندما تكون الوظيفة


 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec); 

يتم إنشاء مثيل بنية AVCodecContext عضو codec . بعد ذلك ، استنادًا إلى codec->priv_data_size ، سيتم تخصيص الذاكرة اللازمة FroxContext ، وباستخدام codec->priv_class سيتم تهيئة العضو الأول من هذا المثيل ، ثم يتم استدعاء وظيفة av_opt_set_defaults() ، والتي ستقوم بتعيين القيم الافتراضية للخيارات. سيكون مؤشر لمثيل FroxContext متاحًا من خلال عضو priv_data بنية priv_data .


عند العمل مع FFmpeg API ، يمكن تعيين قيم الخيارات مباشرةً.


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

هناك طريقة أخرى تتمثل في استخدام قاموس الخيارات ، والذي سيتم تمريره كوسيطة ثالثة عند استدعاء avcodec_open2() (انظر أدناه).


باستخدام وظيفة


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

يمكنك الحصول على قائمة بجميع الخيارات التي يدعمها سياق برنامج الترميز. هذا مفيد عند فحص برنامج الترميز. ولكن قبل ذلك ، يجب التأكد من أن codec_ctx->codec->priv_class تعيينه على قيمة غير صفرية ، وإلا فإن السياق لا يدعم الخيارات وأن أي عملية مع خيارات ستعطل البرنامج.



3.2. وظائف


دعونا الآن نفحص بمزيد من التفصيل كيفية ترتيب الوظائف المستخدمة في تهيئة برنامج الترميز والترميز / فك التشفير الفعلي. عادة ما يحتاجون دائمًا إلى الحصول على مؤشر إلى FroxContext .


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

سيتم frox_decode_init() و frox_encode_init() عند تنفيذ الوظيفة


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

إنهم بحاجة إلى تخصيص الموارد اللازمة لتشغيل برنامج الترميز ، وإذا لزم الأمر ، قم بتهيئة بعض أعضاء بنية AVCodecContext ، على سبيل المثال ، frame_size صوتي.


سيتم frox_decode_close() و frox_encode_close() عند تنفيذهما


 int avcodec_close(AVCodecContext *codec_ctx); 

انهم بحاجة الى تحرير الموارد المخصصة.


النظر في وظيفة لتنفيذ فك التشفير


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

يجب أن تنفذ العمليات التالية:


  1. فك التشفير الفعلي
  2. تخصيص المخزن المؤقت اللازم لإطار الإخراج ؛
  3. نسخ البيانات فك الشفرة إلى الإطار العازلة.

النظر في كيفية تخصيص المخزن المؤقت اللازم لإطار الإخراج. outdata المعلمة outdata فعليًا إلى AVFrame ، لذلك يجب عليك أولاً إجراء تحويل نوع:


 AVFrame* frm = outdata; 

بعد ذلك ، حدد مخزن مؤقت لتخزين بيانات الإطار. للقيام بذلك ، قم بتهيئة أعضاء AVFrame التي تحدد حجم المخزن المؤقت للإطار. بالنسبة إلى الصوت ، هذا هو nb_samples ، channel_layout ، format (لعرض الفيديو ، height ، format ).


بعد ذلك ، تحتاج إلى استدعاء وظيفة


 int av_frame_get_buffer(AVFrame* frm, int alignment); 

يتم استخدام المؤشر إلى الإطار ، وهو معلمة البيانات outdata المحولة ، كوسيطة أولى ؛ يوصى بتمرير الصفر كوسيطة ثانية. بعد استخدام الإطار (يحدث هذا بالفعل خارج برنامج الترميز) ، يتم تحرير المخزن المؤقت المخصص بواسطة هذه الوظيفة بواسطة الوظيفة


 void av_frame_unref(AVFrame* frm); 

يجب أن ترجع الدالة frox_decode() عدد البايتات المستخدمة في فك تشفير الحزمة من الإشارة إلى pkt . في حالة اكتمال تكوين الإطار ، outdata_size تعيين قيمة غير صفرية للمتغير الذي يشير إليه outdata_size ، وإلا فإن هذا المتغير يحصل على القيمة 0 .


النظر في وظيفة لتنفيذ الترميز


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

يجب أن تنفذ العمليات التالية:


  1. الترميز الفعلي
  2. تخصيص المخزن المؤقت اللازم لحزمة الإخراج ؛
  3. نسخ البيانات المشفرة إلى المخزن المؤقت الحزمة.

لتحديد المخزن المؤقت المطلوب ، استخدم الوظيفة


 int av_new_packet(AVPacket *pkt, int pack_size); 

pkt استخدام المعلمة pkt كوسيطة الأولى ، وحجم البيانات المشفرة هو الثاني. بعد استخدام الحزمة (يحدث هذا بالفعل خارج برنامج الترميز) ، يتم تحرير المخازن المؤقتة المخصصة بواسطة هذه الوظيفة بواسطة الوظيفة


 void av_packet_unref(AVPacket *pkt); 

إذا تم إكمال الحزمة ، فسيتم تعيين المتغير الذي يشير إليه got_pkt_ptr على قيمة غير صفرية ، وإلا فإن هذا المتغير يحصل على القيمة 0 . إذا لم يكن هناك خطأ ، فسوف تُرجع الدالة الصفر ، وإلا رمز الخطأ.


عند تنفيذ برنامج الترميز ، يتم عادةً استخدام التسجيل (للأخطاء يمكن اعتبار ذلك متطلبًا إلزاميًا). هنا مثال:


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

في هذه الحالة ، عند الإخراج إلى السجل ، سيتم استخدام اسم برنامج الترميز كاسم سياق.



3.3. الطوابع الزمنية


لضبط الوقت في FFmpeg ، يتم استخدام قاعدة زمنية محددة بالثواني باستخدام الرقم الرشيد الذي يمثله نوع AVRational . (يتم استخدام int64_t C ++ 11. على سبيل المثال ، يعين 1/1000 مللي ثانية). تحتوي الإطارات والحزم على طوابع زمنية من النوع int64_t ، وتحتوي قيمها على وقت في وحدات الوقت المقابلة. يحتوي الإطار ، أي بنية AVFrame ، على pts أعضاء (الطابع الزمني للعرض التقديمي) ، تحدد قيمته الوقت النسبي للمشهد الذي تم التقاطه في الإطار. تحتوي الحزمة ، وهي بنية AVPacket ، على أعضاء pts (الطابع الزمني للعرض) و dts (الطابع الزمني dts الضغط). تحدد القيمة dts الوقت النسبي لإرسال الحزمة لفك التشفير. بالنسبة إلى برامج الترميز البسيطة ، فهي pts ، لكن بالنسبة إلى برامج الترميز المعقدة يمكن أن تكون مختلفة (على سبيل المثال ، بالنسبة لـ h264 عند استخدام B-frames) ، أي أنه يمكن فك تشفير الحزم بالترتيب الخاطئ الذي يجب أن تستخدم به الإطارات.


يتم تعريف وحدة الوقت للدفق والترميز ، بنية AVStream لديها عضو المطابق - time_base ، نفس العضو لديه بنية AVCodecContext .


سيتم تحديد الطوابع الزمنية للحزمة المستخرجة من الدفق باستخدام av_read_frame() بوحدات وقت هذا الدفق. عند فك التشفير ، لا يتم استخدام الوحدة الزمنية لبرنامج الترميز. بالنسبة لوحدة فك ترميز الفيديو ، عادةً ما لا يتم ضبطها ببساطة ، بالنسبة لوحدة فك ترميز الصوت ، تكون لها قيمة قياسية - عكس تردد أخذ العينات. يجب على وحدة فك الترميز أن تحدد طابعًا زمنيًا لإطار الخرج استنادًا إلى الطابع الزمني للرزمة. يعرّف FFmpeg هذه التسمية بشكل مستقل best_effort_timestamp العضو best_effort_timestamp بنية best_effort_timestamp . ستستخدم كل هذه الطوابع الزمنية وحدة وقت الدفق التي يتم استخراج الحزمة منها.


بالنسبة لجهاز التشفير ، يجب عليك تحديد وحدة الوقت. في رمز العميل الذي ينظم عملية فك التشفير ، يجب عليك تعيين القيمة لعضو time_base بنية time_base قبل استدعاء avcodec_open2() . عادةً ما تأخذ وحدة الوقت المستخدمة للطوابع الزمنية للإطار المشفر. إذا لم يتم ذلك ، فعادةً ما تتسبب برامج تشفير الفيديو في حدوث خطأ ، تقوم أجهزة تشفير الصوت بتعيين القيمة الافتراضية - وهي عكس تردد أخذ العينات. ما إذا كان برنامج الترميز يمكنه تغيير وحدة زمنية معينة ليس واضحًا تمامًا. فقط في حالة ، من الأفضل دائمًا التحقق من قيمة time_base بعد استدعاء avcodec_open2() وإذا كان قد تغير ، قم بإعادة حساب الطوابع الزمنية لإطارات الإدخال لكل وحدة زمنية من برنامج الترميز. في عملية الترميز ، يجب عليك تثبيت pts و dts الحزمة. بعد الترميز ، قبل كتابة حزمة إلى دفق الإخراج ، من الضروري إعادة حساب الطوابع الزمنية للرزمة من وحدة وقت الترميز إلى وحدة وقت الدفق. للقيام بذلك ، استخدم الوظيفة


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

عند كتابة حزم إلى الدفق ، من الضروري التأكد من أن قيم dts تزداد بشكل صارم ، وإلا فإن مُضاعِف الإرسال سوف يلقي خطأً. (لمزيد من المعلومات ، راجع الوثائق الخاصة av_interleaved_write_frame() .)



3.4. وظائف أخرى يستخدمها الترميز


عند تهيئة مثيل AVCodec ، يمكن تسجيل وظيفتين أخريين. هنا الأعضاء المعنيين في AVCodec :


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

يتم استدعاء أولهم مرة واحدة عند تسجيل برنامج الترميز.


والثاني يعيد ضبط الحالة الداخلية لبرنامج الترميز ، وسيتم استدعاؤه أثناء تنفيذ الوظيفة


 void avcodec_flush_buffers(AVCodecContext *codec_ctx); 

هذه الدعوة ضرورية ، على سبيل المثال ، عند تغيير وضع التشغيل الحالي بالقوة.



4. التنفيذ الخارجي لبرنامج الترميز



4.1. وظيفة اتصال خارجي


النظر في منظمة الترميز التالية: يلعب الترميز المسجل في FFmpeg دور إطار عمل ، ويفوض إجراء الترميز / فك التشفير الفعلي إلى الوظائف الخارجية (نوع من المكونات الإضافية) المنفذة خارج FFmpeg.


. هؤلاء بعض منهم:


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



استنتاج


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




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/ar480714/


All Articles