Mengkompilasi FFmpeg ke WebAssembly (= ffmpeg.js): Bagian 3 - Mengubah avi ke mp4



Daftar bagian terjemahan seri:


  1. Memasak
  2. Kompilasi dengan Emscripten
  3. Konversi avi ke mp4 (Anda di sini)



Pada bagian ini, kita akan menganalisis:



  1. Kompilasi pustaka FFmpeg dengan argumen yang dioptimalkan.
  2. Manajemen sistem file Emscripten.
  3. Pengembangan ffmpeg.js v0.1.0 dan konversi video.



Kompilasi pustaka FFmpeg dengan argumen yang dioptimalkan


Walaupun tujuan akhir dari bagian ini adalah untuk membuat ffmpeg.js v0.1.0 untuk mengkonversi avi ke mp4, pada bagian sebelumnya kami hanya membuat FFmpeg versi โ€œtelanjangโ€, yang akan lebih baik untuk dioptimalkan dengan beberapa parameter.


  1. -Oz : optimalkan kode dan kurangi ukurannya (dari 30 menjadi 15 MB)
  2. -o javascript / ffmpeg-core.js : simpan file js dan wasm ke direktori javascript. (dari tempat kami akan memanggil ffmpeg-core.js dari pustaka pembungkus ffmpeg.js, yang menyediakan API yang bagus)
  3. -s MODULARIZE = 1 : buat perpustakaan alih-alih utilitas baris perintah (Anda perlu memodifikasi sumber, detail di bawah)
  4. -s EXPORTED_FUNCTIONS = "[_ ffmpeg]" : ekspor fungsi C "ffmpeg" ke dunia JavaScript
  5. -s EXTRA_EXPORTED_RUNTIME_METHODS = "[cwrap, FS, getValue, setValue]" : fungsi tambahan untuk bekerja dengan sistem file dan pointer, detail dapat ditemukan di artikel Berinteraksi dengan kode .
  6. -s ALLOW_MEMORY_GROWTH = 1 : hapus batas pada memori yang dikonsumsi
  7. -lpthread : dihapus karena kami berencana untuk membuat pekerja kami sendiri. (ini adalah jaminan simpanan untuk bagian keempat dari publikasi)

Rincian lebih lanjut tentang masing-masing argumen dapat ditemukan di src / settings.js di emscripten repositori github.


Ketika menambahkan -s MODULARIZE = 1, kita perlu memodifikasi kode sumber untuk memenuhi persyaratan modularitas (pada kenyataannya, singkirkan fungsi utama ()). Anda harus mengubah hanya tiga baris.


1. fftools / ffmpeg.c : ganti nama main menjadi ffmpeg


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

2. fftools / ffmpeg.h : tambahkan ffmpeg di akhir file untuk mengekspor fungsi


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

3. fftools / cmdutils.c : beri komentar keluar (ret) agar pustaka kami tidak keluar dari runtime untuk kami (kami akan meningkatkan poin ini nanti).


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

Versi skrip kompilasi kami yang baru:


 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 siap!


Jika Anda memiliki pengalaman dengan ffmpeg, Anda sudah tahu seperti apa perintah khas:


 $ ffmpeg -i input.avi output.mp4 

Dan karena kita menggunakan fungsi ffmpeg alih-alih utama, panggilan perintah akan terlihat seperti ini:


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

Tentu saja, tidak semuanya begitu sederhana, kita perlu membangun jembatan antara dunia JavaScript dan C, jadi mari kita mulai dengan sistem file emscripten.


Manajemen sistem file Emscripten


Emscripten memiliki sistem file virtual untuk mendukung membaca / menulis dari C, yang digunakan ffmpeg-core.js untuk bekerja dengan file video.


Baca lebih lanjut tentang ini di API Sistem File .


Agar semuanya berfungsi, kami mengekspor API FS dari emscripten, yang disebabkan oleh parameter di atas:


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

Untuk menyimpan file, Anda perlu menyiapkan array dalam format Uint8Array di lingkungan Node.js, yang dapat dilakukan seperti ini:


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

Dan simpan ke sistem file emscripten menggunakan FS.writeFile ():


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

Dan untuk mengunduh file dari emscripten:


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

Mari kita mulai mengembangkan ffmpeg.js untuk menyembunyikan kerumitan ini di belakang API yang indah.


Pengembangan ffmpeg.js v0.1.0 dan konversi video


Pengembangan ffmpeg.js bukanlah hal sepele, karena Anda harus selalu beralih antara dunia JavaScript dan C, tetapi jika Anda terbiasa dengan pointer , akan jauh lebih mudah untuk memahami apa yang terjadi di sini.


Tugas kami adalah mengembangkan ffmpeg.js seperti ini:


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

Pertama, unduh ffmpeg-core.js, yang secara tradisional dilakukan secara tidak sinkron agar tidak memblokir utas utama.


Begini tampilannya:


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

Mungkin tampak aneh bahwa kita membungkus satu janji dengan yang lain, ini karena FFmpegCore () bukan janji yang nyata, tetapi hanya fungsi yang mensimulasikan API janji.


Langkah selanjutnya adalah menggunakan Module untuk mendapatkan fungsi ffmpeg menggunakan fungsi cwrap :


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

Argumen pertama untuk cwrap adalah nama fungsi (yang harus dalam EXPORTED_FUNCTIONS dengan garis bawah sebelumnya), yang kedua adalah jenis nilai pengembalian, yang ketiga adalah jenis argumen fungsi (int argc dan char ** argv).


Jelas mengapa argc adalah angka, tetapi mengapa argv juga angka? argv adalah pointer, dan pointer menyimpan alamat dalam memori (tipe 0xfffffff), jadi tipe pointer 32 bit tidak ditandai di WebAssembly. Itu sebabnya kami menentukan nomor sebagai tipe argv .


Untuk memanggil ffmpeg (), argumen pertama akan berupa angka reguler dalam JavaScript, tetapi argumen kedua harus menjadi penunjuk ke array karakter (Uint8 dalam JavaScript).


Kami membagi tugas ini menjadi 2 subtugas:


  1. Bagaimana cara membuat pointer ke array karakter?
  2. Bagaimana cara membuat pointer ke array pointer?

Kami akan memecahkan masalah pertama dengan membuat utilitas 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 () mirip dengan malloc () di C, ia mengalokasikan sejumlah memori pada heap. Module.setValue () mengatur nilai spesifik dengan pointer.


Ingatlah untuk menambahkan 0 di akhir array karakter untuk menghindari situasi yang tidak terduga.


Setelah berurusan dengan subtugas pertama, buat strList2ptr untuk menyelesaikan yang kedua:


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

Hal utama yang harus dipahami di sini adalah bahwa pointer adalah nilai Uint32 di dalam JavaScript, jadi listPtr adalah pointer ke array Uint32 yang menyimpan pointer ke array Uint8.


Menyatukan semuanya, kita mendapatkan implementasi ffmepg.transcode () berikut :


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

Selesai! Sekarang kita memiliki ffmpeg.js v0.1.0 untuk mengkonversi avi ke mp4.


Anda dapat menguji hasilnya sendiri dengan menginstal perpustakaan:


 $ npm install @ffmpeg/ffmpeg@0.1.0 

Dan mengonversi file seperti ini:


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

Perlu diingat bahwa sejauh ini perpustakaan hanya berfungsi untuk Node.js, tetapi di bagian selanjutnya kami akan menambahkan dukungan untuk pekerja web (dan child_process di Node.js).


Kode Sumber:


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


All Articles