Build a library version of FFmpeg with optimized arguments.

In Part.3, our goal is to create a basic ffmpeg.wasm v0.1 to transcode avi to mp4. As we only create a basic version of FFmpeg in Part.2, now we need to further optimized with few arguments.

-O3 : optimize code and reduce code size (from 30 MB to 15 MB) (More details HERE) -s PROXY_TO_PTHREAD=1 : to make our program for responsive when using pthread (More details HERE) -o wasm/dist/ffmpeg-core.js : rename ffmpeg.js to ffmpeg-core.js

(From here we call it ffmpeg-core.js as we will create a ffmpeg.js library to wrap the ffmpeg-core.js and provide user friendly APIs.) -s EXPORTED_FUNCTIONS="[_main, _proxy_main]" : export main() and proxy_main() (added by PROXY_TO_PTHREAD) C function to JavaScript world -s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, setValue, writeAsciiToMemory]" : extra functions for manipulating functions, file system and pointers, check Interacting with code and preamble.js for more details.

For more details about these arguments, you can check src/settings.js in emscripten github repository.

With all the new arguments, let’s update our build.sh :

Next, let’s try to interact with ffmpeg.wasm.

Interact with ffmpeg.wasm

To make sure our ffmpeg.wasm is working, let’s try to achieve following command in ffmpeg.wasm:

$ ffmpeg -hide_banner

With -hide_banner argument, ffmpeg hides details about its version and build arguments, a typical output looks like this:

Hyper fast Audio and Video encoder

usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}... Use -h to get full help or, even better, run 'man ffmpeg'

To begin with, let’s create a file called ffmpeg.js with following code:

The execution of the code above requires extra parameters in Node.JS:

$ node --experimental-wasm-threads --experimental-wasm-bulk-memory ffmpeg.js

Explanation for the functions:

onRuntimeInitialized : as WebAssembly requires some time to boot up, you need to wait for this function to be called before using the library.

: as WebAssembly requires some time to boot up, you need to wait for this function to be called before using the library. cwrap : a wrapper function for the C function in JavaScript world. Here we wrap proxy_main() / main() function in fftools/ffmpeg.c . The function signature is int main(int argc, char **argv) , it is straightforward that int is mapping to number and as char **argv is a pointer in C, we can also mapping it to number .

Then we need to pass the arguments to it. The equivalent argument for $ ffmpeg -hide_banner is main(2, ["./ffmpeg", "-hide_banner"]) . The first argument is easy, but how can we pass a string array? Let’s decompose the issue to two parts:

We need to convert string in JavaScript to char array in C We need ot convert a number array in JavaScript to a pointer array in C

The 1st part is easier as we have an utility function from Emscripten called writeAsciiToMemory() to help us, below is an example of using this function:

The 2nd part is tricky, we need to create a pointer array in C with 32 bit integer as the pointer is in 32 bit integer. We need to use setValue here to create the array we need:

Merge all the snippets above, now we can interact with ffmpeg.wasm and generate expected result:

Now, we can interact with ffmpeg.wasm with ease, but how can we pass the video file to it? That is the focus of next section: File System.

Manage Emscripten File System.

In Emscripten, there is a virtual file system to support standard file read/write in C, thus we need to write our video file into this File System before passing the arguments to ffmpeg.wasm.

Find more details in File System API.

Most of the time, there are only 2 FS functions you need to complete the task: FS.writeFile() and FS.readFile() .

For all the data writing to or reading from the File System, it must be in Uint8Array type in JavaScript, remember to do type conversion before consuming the data.

For this tutorial, let’s use a file called flame.avi (You can download it HERE) and read it with fs.readFileSync() and write it to Emscripten File System with FS.writeFile() .

Develop ffmpeg.wasm v0.1 with transcoding feature.

Now we are able to pass arguments to ffmpeg.wasm and save files to File System, let’s compose all of them together and get our ffmpeg.wasm v0.1 working.

The last one more detail we need to be aware of is that ffmpeg() above is actually running asynchronously, so to get the output file we need to use a setInterval() to parse the log file to know if the tranding completes.

Put everything together, now we have our first ffmpeg.wasm to transcode a avi file to mp4 file with any issues:

You can visit the repository here to see how it works in details: https://github.com/ffmpegwasm/FFmpeg/tree/n4.3.1-p3

And feel free to download the build artifacts here: https://github.com/ffmpegwasm/FFmpeg/releases/tag/n4.3.1-p3