ترجمة FFmpeg إلى WebAssembly (= ffmpeg.js): الجزء 3 - تحويل avi إلى mp4



قائمة الأجزاء المترجمة من السلسلة:


  1. الطبخ
  2. ترجمة مع المخطوطات
  3. تحويل avi إلى mp4 (أنت هنا)



في هذا الجزء ، سوف نحلل:



  1. تجميع مكتبة FFmpeg مع وسيطات محسّنة.
  2. إدارة نظام الملفات Emscripten.
  3. تطوير ffmpeg.js v0.1.0 وتحويل الفيديو.



ترجمة مكتبة FFmpeg باستخدام الوسائط المحسّنة


على الرغم من أن الهدف النهائي لهذا الجزء هو إنشاء ffmpeg.js v0.1.0 لتحويل avi إلى mp4 ، إلا أننا في الجزء السابق أنشأنا فقط نسخة "عارية" من FFmpeg ، والتي سيكون من الرائع تحسينها مع العديد من المعلمات.


  1. -أوز : تحسين الكود وتقليل حجمه (من 30 إلى 15 ميغابايت)
  2. -o javascript / ffmpeg-core.js : احفظ js وملفات wasm في دليل javascript. (من أين سنتصل بـ ffmpeg-core.js من مكتبة مجمّع ffmpeg.js ، التي توفر واجهة برمجة تطبيقات جميلة)
  3. -s MODULARIZE = 1 : إنشاء مكتبة بدلاً من أداة مساعدة لسطر الأوامر (ستحتاج إلى تعديل المصادر والتفاصيل أدناه)
  4. -s EXPORTED_FUNCTIONS = "[_ ffmpeg]" : تصدير وظيفة ffmpeg C إلى عالم JavaScript
  5. -s EXTRA_EXPORTED_RUNTIME_METHODS = "[cwrap، FS، getValue، setValue]" : وظائف إضافية للعمل مع نظام الملفات والمؤشرات ، يمكن العثور على التفاصيل في المقال التفاعل مع التعليمات البرمجية .
  6. -S ALLOW_MEMORY_GROWTH = 1 : قم بإزالة الحد على الذاكرة المستهلكة
  7. -lpthread : تمت الإزالة لأننا نخطط لإنشاء عامل خاص بنا. (هذا تراكم للجزء الرابع من المنشورات)

يمكن العثور على مزيد من التفاصيل حول كل من الوسيطات في src / settings.js في مستودع تخزين github emscripten.


عند إضافة -s MODULARIZE = 1 ، نحتاج إلى تعديل الكود المصدري لتلبية متطلبات النمطية (في الواقع ، تخلص من الوظيفة الرئيسية ()). يجب عليك تغيير ثلاثة خطوط فقط.


1. fftools / ffmpeg.c : إعادة تسمية الرئيسي ل ffmpeg


- int main(int argc, char **argv) + int ffmpeg(int argc, char **argv) 

2. fftools / ffmpeg.h : أضف ffmpeg إلى نهاية الملف لتصدير الوظيفة


 + int ffmpeg(int argc, char** argv); #endif /* FFTOOLS_FFMPEG_H */ 

3. fftools / cmdutils.c : قم بالتعليق على الخروج (إعادة) حتى لا تخرج مكتبتنا من وقت التشغيل (سنحسن هذه النقطة لاحقًا).


 void exit_program(int ret){ if (program_exit) program_exit(ret); - exit(ret); + // exit(ret); } 

إصدارنا الجديد من البرنامج النصي للتجميع:


 emcc \ -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample \ -Qunused-arguments -Oz \ -o javascript/ffmpeg-core.js fftools/ffmpeg_opt.o fftools/ffmpeg_filter.o fftools/ffmpeg_hw.o fftools/cmdutils.o fftools/ffmpeg.o \ -lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm \ -s MODULARIZE=1 \ -s EXPORTED_FUNCTIONS="[_ffmpeg]" \ -s EXTRA_EXPORTED_RUNTIME_METHODS="[cwrap, FS, getValue, setValue]" \ -s TOTAL_MEMORY=33554432 \ -s ALLOW_MEMORY_GROWTH=1 

ffmpeg-core.js جاهز!


إذا كانت لديك تجربة مع ffmpeg ، فأنت تعلم بالفعل كيف يبدو الأمر النموذجي:


 $ ffmpeg -i input.avi output.mp4 

ونظرًا لأننا نستخدم دالة ffmpeg بدلاً من main ، فإن استدعاء الأوامر سيبدو كما يلي:


 const args = ['./ffmpeg', '-i', 'input.avi', 'output.mp4']; ffmpeg(args.length, args); 

بالطبع ، ليس كل شيء بهذه البساطة ، سنحتاج إلى بناء جسر بين عوالم JavaScript و C ، لذلك لنبدأ بنظام الملفات emscripten.


إدارة نظام الملفات Emscripten


يحتوي Emscripten على نظام ملفات افتراضي لدعم القراءة / الكتابة من C ، والذي يستخدمه ffmpeg-core.js للعمل مع ملفات الفيديو.


اقرأ المزيد حول هذا الموضوع في واجهة برمجة تطبيقات نظام الملفات .


لكي يعمل كل شيء ، نصدر FS API من emscripten ، والذي يرجع إلى المعلمة أعلاه:


 -s EXTRA_EXPORTED_RUNTIME_METHODS="[cwrap, FS, getValue, setValue]" 

لحفظ الملف ، تحتاج إلى تحضير الصفيف بتنسيق Uint8Array في بيئة Node.js ، والذي يمكن القيام به مثل هذا:


 const fs = require('fs'); const data = new Uint8Array(fs.readFileSync('./input.avi')); 

وحفظه إلى نظام الملفات emscripten باستخدام FS.writeFile ():


 require('./ffmpeg-core.js)() .then(Module => { Module.FS.writeFile('input.avi', data); }); 

ولتنزيل الملف من المخطوطات:


 require('./ffmpeg-core.js)() .then(Module => { const data = Module.FS.readFile('output.mp4'); }); 

لنبدأ في تطوير ffmpeg.js لإخفاء هذه التعقيدات خلف واجهة برمجة تطبيقات جميلة.


تطوير ffmpeg.js v0.1.0 وتحويل الفيديو


إن تطوير ffmpeg.js ليس تافهاً ، حيث تحتاج دائمًا إلى التبديل بين عالمتي JavaScript و C ، ولكن إذا كنت معتادًا على المؤشرات ، فسيكون من الأسهل بكثير فهم ما يحدث هنا.


مهمتنا هي تطوير ffmpeg.js مثل هذا:


 const fs = require('fs'); const ffmpeg = require('@ffmpeg/ffmpeg'); (async () => { await ffmpeg.load(); const data = ffmpeg.transcode('./input.avi', 'mp4'); fs.writeFileSync('./output.mp4', data); })(); 

أولاً ، قم بتنزيل ffmpeg-core.js ، الذي يتم عادةً بشكل غير متزامن حتى لا يتم حظر الخيط الرئيسي.


إليك ما يبدو عليه:


 const { setModule } = require('./util/module'); const FFmpegCore = require('./ffmpeg-core'); module.exports = () => ( new Promise((resolve, reject) => { FFmpegCore() .then((Module) => { setModule(Module); resolve(); }); }) ); 

قد يبدو غريباً أن نلتزم بوعد واحد في آخر ، وذلك لأن FFmpegCore () ليس وعدًا حقيقيًا ، ولكن مجرد دالة تحاكي API الوعد.


الخطوة التالية هي استخدام الوحدة النمطية للحصول على وظيفة ffmpeg باستخدام دالة cwrap :


 // int ffmpeg(int argc, char **argv) const ffmpeg = Module.cwrap('ffmpeg', 'number', ['number', 'number']); 

الوسيطة الأولى إلى cwrap هي اسم الوظيفة (التي يجب أن تكون في EXPORTED_FUNCTIONS مع تسطير أسفل السطر السابق) ، والثاني هو نوع قيمة الإرجاع ، والثالث هو نوع وسيطات الدالة (int argc و char ** argv).


من الواضح لماذا argc رقم ، ولكن لماذا argv رقم أيضًا؟ argv هو مؤشر ، ويقوم المؤشر بتخزين العنوان في الذاكرة (من النوع 0xfffffff) ، وبالتالي فإن نوع المؤشر غير موقّع 32 بت في WebAssembly. هذا هو السبب في أننا نحدد الرقم كنوع argv .


للاتصال بـ ffmpeg () ، ستكون الوسيطة الأولى عبارة عن رقم منتظم في JavaScript ، ولكن يجب أن تكون الوسيطة الثانية مؤشرًا لمجموعة من الأحرف (Uint8 في JavaScript).


نقسم هذه المهمة إلى مهمتين فرعيتين:


  1. كيفية إنشاء مؤشر لمجموعة من الشخصيات؟
  2. كيفية إنشاء مؤشر إلى مجموعة من المؤشرات؟

سنحل المشكلة الأولى عن طريق إنشاء الأداة المساعدة str2ptr :


 const { getModule } = require('./module'); module.exports = (s) => { const Module = getModule(); const ptr = Module._malloc((s.length+1)*Uint8Array.BYTES_PER_ELEMENT); for (let i = 0; i < s.length; i++) { Module.setValue(ptr+i, s.charCodeAt(i), 'i8'); } Module.setValue(ptr+s.length, 0, 'i8'); return ptr; }; 

Module._malloc () يشبه malloc () في C ، فإنه يخصص جزء من الذاكرة على كومة الذاكرة المؤقتة. Module.setValue () يعين القيمة المحددة بواسطة المؤشر.


تذكر إضافة 0 إلى نهاية صفيف الأحرف لتجنب المواقف غير المتوقعة.


بعد التعامل مع المهمة الفرعية الأولى ، قم بإنشاء strList2ptr لحل المشكلة الثانية:


 const { getModule } = require('./module'); const str2ptr = require('./str2ptr'); module.exports = (strList) => { const Module = getModule(); const listPtr = Module._malloc(strList.length*Uint32Array.BYTES_PER_ELEMENT); strList.forEach((s, idx) => { const strPtr = str2ptr(s); Module.setValue(listPtr + (4*idx), strPtr, 'i32'); }); return listPtr; }; 

الشيء الرئيسي الذي يجب فهمه هنا هو أن المؤشر هو قيمة Uint32 داخل JavaScript ، لذلك listPtr هو مؤشر إلى صفيف Uint32 يخزن المؤشرات إلى صفيف Uint8.


بتجميعها معًا ، نحصل على التنفيذ التالي لـ ffmepg.transcode () :


 const fs = require('fs'); const { getModule } = require('./util/module'); const strList2ptr = require('./util/strList2ptr'); module.exports = (inputPath, outputExt) => { const Module = getModule(); const data = new Uint8Array(fs.readFileSync(inputPath)); const ffmpeg = Module.cwrap('ffmpeg', 'number', ['number', 'number']); const args = ['./ffmpeg', '-i', 'input.avi', `output.${outputExt}`]; Module.FS.writeFile('input.avi', data); ffmpeg(args.length, strList2ptr(args)); return Buffer.from(Module.FS.readFile(`output.${outputExt}`)); }; 

القيام به! الآن لدينا ffmpeg.js v0.1.0 لتحويل avi إلى mp4.


يمكنك اختبار النتيجة بنفسك عن طريق تثبيت المكتبة:


 $ npm install @ffmpeg/ffmpeg@0.1.0 

وتحويل الملف مثل هذا:


 const fs = require('fs'); const ffmpeg = require('@ffmpeg/ffmpeg'); (async () => { await ffmpeg.load(); const data = ffmpeg.transcode('./input.avi', 'mp4'); fs.writeFileSync('./output.mp4', data); })(); 

فقط ضع في اعتبارك أنه حتى الآن تعمل المكتبة فقط مع Node.js ، ولكن في الجزء التالي سنضيف دعمًا للعامل على الويب (وعملية_العملية في Node.js).


رموز المصدر:


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


All Articles