Closed gogoend closed 3 years ago
原文:https://itnext.io/build-ffmpeg-webassembly-version-ffmpeg-js-part-3-ffmpeg-js-v0-1-0-transcoding-avi-to-mp4-f729e503a397 作者:Jerome Wu 翻译:gogoend
前一篇文章:[译] 构建WebAssembly版本的FFmpeg——ffmpeg.wasm:第二部分:使用Emscripten编译
在这一部分你将了解到:
在第三部分,我们的目标是创建一个基础的ffmpeg.wasm v0.1.0,以用于将avi转换为mp4。但在第二部分,我们仅仅创建了一个“裸金属”版本的FFmpeg,我们需要使用几个参数来对其更深一步地进行优化。
-Oz
-o javascript/ffmpeg-core.js
-s MODULARIZE=1
-s EXPORTED_FUNCTIONS="[_ffmpeg]"
-s EXTRA_EXPORTED_RUNTIME_METHODS="[cwrap, FS, getValue, setValue]"
-s ALLOW_MEMORY_GROWTH=1
-lpthread
对于这些参数的详细信息,你可以参阅GitHub上 emscripten 仓库中的src/settings.js。
当我们添加-s MODULARIZE=1参数时,为了适应模块化的相关要求,我们有必要对源代码进行一些更新(基本上就是移除main() 函数)。我们只需更改3行代码,即可使其工作。
fftools/ffmpeg.c:重命名为ffmpeg
fftools/ffmpeg.c
- int main(int argc, char **argv) + int ffmpeg(int argc, char **argv)
fftools/ffmpeg.h: 在该文件最后加入ffmpeg,以输出ffmpeg方法。
fftools/ffmpeg.h
+ int ffmpeg(int argc, char** argv); #endif /* FFTOOLS_FFMPEG_H */
fftools/cmdutils.c: 注释掉exit(ret),因为我们不希望我们的库为我们退出runtime。(在未来我们会增强这一部分)。
fftools/cmdutils.c
void exit_program(int ret){ if (program_exit) program_exit(ret); - exit(ret); + // exit(ret); }
我们新的构建脚本:
...
现在我们的ffmpeg-core.js就已经准备好了!
如果你熟悉ffmpeg,那你一定知道在命令行下ffmpeg的通用用法:
$ ffmpeg -i input.avi output.mp4
我们重用main函数,来作为我们的ffmpeg函数,我们将会这样调用我们的函数:
const args = ['./ffmpeg', '-i', 'input.avi', 'output.mp4']; ffmpeg(args.length, args);
当然,要实现这一点并不容易。我们需要做一些预处理,来为JavaScript的世界与C的世界搭建桥梁。我们从emscripten的文件系统开始吧。
Emscripten中包含一个虚拟的文件系统,用以支持C语言中标准文件的读写。我们重用了命令行版本的FFmpeg,该版本可将视频文件读写到文件系统,因此了解基本概念十分重要。
更多详情请参阅File System API
若要使用文件系统,首先你需要将FS API从Emscripten输出,这是通过上文中说到的一个参数实现的:
要将一个文件存入文件系统,你需要准备一个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); });
从emscripten文件系统加载文件也与此类似:
require('./ffmpeg-core.js)() .then(Module => { const data = Module.FS.readFile('output.mp4'); });
在了解这些概念后,现在我们可以继续开发隐藏了复杂的概念,提供了用户友好API的ffmpeg.wasm。
ffmpeg.wasm的开发并不容易,因为你需要在JavaScript与C语言之间进行转换。但如果你熟悉指针,你将会更加容易理解我们现在大部分时间在做什么。
这一部分,我们的目标是开发一个类似这样的ffmpeg.wasm:
首先,我们需要加载ffmpeg-core.js。由emscripten构建的库,加载过程是异步的,因为我们不想让它阻塞我们的主线程。
一种典型的加载过程可能是: 你或许对我们使用Promise来包裹另一个Promise感到诧异,这是因为FFmpegCore()不是一个真正的promise,仅仅是一个具有类似API的函数。
FFmpegCore()
Module是让我们进行所有操作的一个初始化库,这里我们使用 Singleton 工具来存储它,并在之后得到。
Module
下一步我们将使用Module来得到我们的ffmpeg函数,这里我们需要使用cwrap:
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),所以在WebAssembly中,指针的类型是一个32位无符号整数。这就是argv的类型为数值的原因。
argc
argv
要调用ffmpeg(),第一个参数很容易,因为我们可以无需任何更改地在JavaScript中使用原生的数值,但第二个参数就不容易了,因为我们需要创建一个指向char类型的二维数组的指针(JavaScript中的Uint8)。
将这一问题拆分为两个小问题:
要解决第一个问题,我们来创建一个工具函数,名为str2ptr: Module._malloc()同C语言里的malloc()方法类似,它在堆中分配一部分的内存空间。Module.setValue()在给定的指针中设置真实值。
str2ptr
Module._malloc()
malloc()
Module.setValue()
不要忘了在char数组最后加一个0,否则你可能会遇上意料之外的动作。
现在我们就解决了第一个问题,那我们来创建strList2ptr函数来解决后面这个问题。
strList2ptr
这里的关键是理解指针在JavaScript中是一个Uint32值,因此listPtr是一个Uint32数组指针,它存储有指向Uint8数组的指针。
listPtr
将这些东西放在一起,我们就有了ffmepg.transcode()的第一个版本。
ffmepg.transcode()
没错,现在我们就有了将avi转码为mp4的ffmpeg.wasm v0.1.0。
如果你想直接尝试ffmpeg.wasm v0.1.0,现在你就可以通过这个命令来安装它:
$ npm install @ffmpeg/ffmpeg@0.1.0
其用法十分直接。
不过还是注意一下,目前仅仅是一个可在Node.js上运行的版本,但我们将会在构建WebAssembly版本的FFmpeg——ffmpeg.wasm:第四部分:ffmpeg.wasm v0.2 —— Web Worker 与 Libx264来开发浏览器版本。
期待在第四部分与你相见!😃
仓库:
前一篇文章:[译] 构建WebAssembly版本的FFmpeg——ffmpeg.wasm:第二部分:使用Emscripten编译
在这一部分你将了解到:
使用优化的参数构建一个库版本的FFmpeg
在第三部分,我们的目标是创建一个基础的ffmpeg.wasm v0.1.0,以用于将avi转换为mp4。但在第二部分,我们仅仅创建了一个“裸金属”版本的FFmpeg,我们需要使用几个参数来对其更深一步地进行优化。
-Oz
:优化代码,减少代码大小(从30MB缩减到15MB);-o javascript/ffmpeg-core.js
:将js文件与wasm文件输出到javascript文件夹。 (在这里,我们将其称为ffmpeg-core.js —— 我们将创建一个ffmpeg.wasm库来包装ffmpeg-core.js,并向用户提供友好的API。)-s MODULARIZE=1
:创建一个库而非一个命令行工具(需要修改源代码,详见下方)-s EXPORTED_FUNCTIONS="[_ffmpeg]"
:将C语言中的函数“ffmpeg”输出到JavaScript的世界-s EXTRA_EXPORTED_RUNTIME_METHODS="[cwrap, FS, getValue, setValue]"
:用于操作函数、文件系统以及指针的一些额外的方法,参阅Interacting with code以了解详情。-s ALLOW_MEMORY_GROWTH=1
:当内存用尽的时候,允许扩大内存。-lpthread
:已删除,因为我们要创建我们自己版本的worker。(详见第四部分)当我们添加
-s MODULARIZE=1
参数时,为了适应模块化的相关要求,我们有必要对源代码进行一些更新(基本上就是移除main() 函数)。我们只需更改3行代码,即可使其工作。fftools/ffmpeg.c
:重命名为ffmpegfftools/ffmpeg.h
: 在该文件最后加入ffmpeg,以输出ffmpeg方法。fftools/cmdutils.c
: 注释掉exit(ret),因为我们不希望我们的库为我们退出runtime。(在未来我们会增强这一部分)。我们新的构建脚本:
现在我们的ffmpeg-core.js就已经准备好了!
如果你熟悉ffmpeg,那你一定知道在命令行下ffmpeg的通用用法:
我们重用main函数,来作为我们的ffmpeg函数,我们将会这样调用我们的函数:
当然,要实现这一点并不容易。我们需要做一些预处理,来为JavaScript的世界与C的世界搭建桥梁。我们从emscripten的文件系统开始吧。
管理Emscripten的文件系统
Emscripten中包含一个虚拟的文件系统,用以支持C语言中标准文件的读写。我们重用了命令行版本的FFmpeg,该版本可将视频文件读写到文件系统,因此了解基本概念十分重要。
若要使用文件系统,首先你需要将FS API从Emscripten输出,这是通过上文中说到的一个参数实现的:
要将一个文件存入文件系统,你需要准备一个Uint8Array格式的数组。在Node.js环境下,你可以这样做:
而要将文件保存到emscripten文件系统,需要调用FS.writeFile()函数:
从emscripten文件系统加载文件也与此类似:
在了解这些概念后,现在我们可以继续开发隐藏了复杂的概念,提供了用户友好API的ffmpeg.wasm。
开发具有转码功能的ffmpeg.wasm v0.1.0
ffmpeg.wasm的开发并不容易,因为你需要在JavaScript与C语言之间进行转换。但如果你熟悉指针,你将会更加容易理解我们现在大部分时间在做什么。
这一部分,我们的目标是开发一个类似这样的ffmpeg.wasm:
首先,我们需要加载ffmpeg-core.js。由emscripten构建的库,加载过程是异步的,因为我们不想让它阻塞我们的主线程。
一种典型的加载过程可能是: 你或许对我们使用Promise来包裹另一个Promise感到诧异,这是因为
FFmpegCore()
不是一个真正的promise,仅仅是一个具有类似API的函数。Module
是让我们进行所有操作的一个初始化库,这里我们使用 Singleton 工具来存储它,并在之后得到。下一步我们将使用
Module
来得到我们的ffmpeg函数,这里我们需要使用cwrap
:cwrap
的第一个参数是函数名(必须在EXPORTED_FUNCTIONS中包含下划线),第二个参数是返回值类型,第三个参数是函数参数的类型( int argc 和 char **argv )。很明显,
argc
是一个数值,但为何argv
也是一个数值?这是因为,argv
实际上是一个指针,指针中所存储的是内存地址(类似0xfffffff),所以在WebAssembly中,指针的类型是一个32位无符号整数。这就是argv
的类型为数值的原因。要调用ffmpeg(),第一个参数很容易,因为我们可以无需任何更改地在JavaScript中使用原生的数值,但第二个参数就不容易了,因为我们需要创建一个指向char类型的二维数组的指针(JavaScript中的Uint8)。
将这一问题拆分为两个小问题:
要解决第一个问题,我们来创建一个工具函数,名为
str2ptr
:Module._malloc()
同C语言里的malloc()
方法类似,它在堆中分配一部分的内存空间。Module.setValue()
在给定的指针中设置真实值。现在我们就解决了第一个问题,那我们来创建
strList2ptr
函数来解决后面这个问题。这里的关键是理解指针在JavaScript中是一个Uint32值,因此
listPtr
是一个Uint32数组指针,它存储有指向Uint8数组的指针。将这些东西放在一起,我们就有了
ffmepg.transcode()
的第一个版本。没错,现在我们就有了将avi转码为mp4的ffmpeg.wasm v0.1.0。
如果你想直接尝试ffmpeg.wasm v0.1.0,现在你就可以通过这个命令来安装它:
其用法十分直接。
不过还是注意一下,目前仅仅是一个可在Node.js上运行的版本,但我们将会在构建WebAssembly版本的FFmpeg——ffmpeg.wasm:第四部分:ffmpeg.wasm v0.2 —— Web Worker 与 Libx264来开发浏览器版本。
期待在第四部分与你相见!😃
仓库: