قائمة الأجزاء المترجمة من السلسلة:
- الطبخ
- ترجمة مع المخطوطات
- تحويل avi إلى mp4 (أنت هنا)
في هذا الجزء ، سوف نحلل:
- تجميع مكتبة FFmpeg مع وسيطات محسّنة.
- إدارة نظام الملفات Emscripten.
- تطوير ffmpeg.js v0.1.0 وتحويل الفيديو.
ترجمة مكتبة FFmpeg باستخدام الوسائط المحسّنة
على الرغم من أن الهدف النهائي لهذا الجزء هو إنشاء ffmpeg.js v0.1.0 لتحويل avi إلى mp4 ، إلا أننا في الجزء السابق أنشأنا فقط نسخة "عارية" من FFmpeg ، والتي سيكون من الرائع تحسينها مع العديد من المعلمات.
- -أوز : تحسين الكود وتقليل حجمه (من 30 إلى 15 ميغابايت)
- -o javascript / ffmpeg-core.js : احفظ js وملفات wasm في دليل javascript. (من أين سنتصل بـ ffmpeg-core.js من مكتبة مجمّع ffmpeg.js ، التي توفر واجهة برمجة تطبيقات جميلة)
- -s MODULARIZE = 1 : إنشاء مكتبة بدلاً من أداة مساعدة لسطر الأوامر (ستحتاج إلى تعديل المصادر والتفاصيل أدناه)
- -s EXPORTED_FUNCTIONS = "[_ ffmpeg]" : تصدير وظيفة ffmpeg C إلى عالم JavaScript
- -s EXTRA_EXPORTED_RUNTIME_METHODS = "[cwrap، FS، getValue، setValue]" : وظائف إضافية للعمل مع نظام الملفات والمؤشرات ، يمكن العثور على التفاصيل في المقال التفاعل مع التعليمات البرمجية .
- -S ALLOW_MEMORY_GROWTH = 1 : قم بإزالة الحد على الذاكرة المستهلكة
- -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).
نقسم هذه المهمة إلى مهمتين فرعيتين:
- كيفية إنشاء مؤشر لمجموعة من الشخصيات؟
- كيفية إنشاء مؤشر إلى مجموعة من المؤشرات؟
سنحل المشكلة الأولى عن طريق إنشاء الأداة المساعدة 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).
رموز المصدر: