Created
October 3, 2021 02:58
-
-
Save Aeroxander/96cee8b7fb8ba73d090abecb182dd493 to your computer and use it in GitHub Desktop.
no web worker ffmpeg
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# !/bin/bash -x | |
set -e -o pipefail | |
# verify Emscripten version | |
emcc -v | |
# configure FFMpeg with Emscripten | |
CONFIG_ARGS=( | |
--target-os=none # use none to prevent any os specific configurations | |
--arch=x86_32 # use x86_32 to achieve minimal architectural optimization | |
--enable-cross-compile # enable cross compile | |
--disable-x86asm # disable x86 asm | |
--disable-inline-asm # disable inline asm | |
--disable-stripping # disable stripping | |
--disable-programs # disable programs build (incl. ffplay, ffprobe & ffmpeg) | |
--disable-doc # disable doc | |
--nm="llvm-nm" | |
--ar=emar | |
--ranlib=emranlib | |
--cc=emcc | |
--cxx=em++ | |
--objcc=emcc | |
--dep-cc=emcc | |
# 去掉不需要的组件 | |
--disable-avdevice | |
--disable-swresample | |
--disable-postproc | |
--disable-network | |
--disable-pthreads | |
--disable-w32threads | |
--disable-os2threads | |
# 解封装,编解码等 | |
--disable-everything # 减少wasm体积的关键,除了以下的组件外的个别组件都disable | |
--enable-filters | |
--enable-muxer=image2 | |
--enable-demuxer=mov # mov,mp4,m4a,3gp,3g2,mj2 | |
--enable-demuxer=flv | |
--enable-demuxer=h264 | |
--enable-demuxer=asf | |
--enable-encoder=mjpeg | |
--enable-decoder=hevc | |
--enable-decoder=h264 | |
--enable-decoder=mpeg4 | |
--enable-protocol=file | |
) | |
emconfigure ./configure "${CONFIG_ARGS[@]}" | |
# build dependencies | |
emmake make -j4 | |
# build ffmpeg.wasm | |
mkdir -p wasm/dist | |
ARGS=( | |
-I. -I./fftools # Add directory to include search path | |
-Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample # Add directory to library search path | |
-Qunused-arguments # Don't emit warning for unused driver arguments. | |
-o wasm/dist/ffmpeg-core.js fftools/ffmpeg_opt.c fftools/ffmpeg_filter.c fftools/ffmpeg_hw.c fftools/cmdutils.c fftools/ffmpeg.c # output | |
-lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm # library | |
-s USE_SDL=2 # use SDL2 | |
-s MODULARIZE=1 # use modularized version to be more flexible | |
-s EXPORT_NAME="createFFmpegCore" # assign export name for browser | |
# -s PROXY_TO_WORKER=1 # uses a plain Web Worker to run your main program | |
# -s ENVIRONMENT='web,worker' | |
#-s USE_PTHREADS=1 | |
#-s PROXY_TO_PTHREAD=1 | |
-s EXPORTED_FUNCTIONS="[_main]" # export main and proxy_main funcs | |
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, setValue, writeAsciiToMemory]" # export extra runtime methods | |
-s INITIAL_MEMORY=33554432 # 33554432 bytes = 32MB | |
-s ALLOW_MEMORY_GROWTH=1 # allows the total amount of memory used to change depending on the demands of the application | |
--post-js wasm/post-js.js # emits a file after the emitted code. use to expose exit function | |
-O3 # optimize code and reduce code size | |
) | |
emcc "${ARGS[@]}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { defaultArgs, baseOptions } = require('./config'); | |
const { setLogging, setCustomLogger, log } = require('./utils/log'); | |
const parseProgress = require('./utils/parseProgress'); | |
const parseArgs = require('./utils/parseArgs'); | |
const { defaultOptions, getCreateFFmpegCore } = require('./node'); | |
const { version } = require('../package.json'); | |
const NO_LOAD = Error('ffmpeg.wasm is not ready, make sure you have completed load().'); | |
module.exports = (_options = {}) => { | |
const { | |
log: logging, | |
logger, | |
progress: optProgress, | |
...options | |
} = { | |
...baseOptions, | |
...defaultOptions, | |
..._options, | |
}; | |
let Core = null; | |
let ffmpeg = null; | |
let runResolve = null; | |
let running = false; | |
let progress = optProgress; | |
const detectCompletion = (message) => { | |
if (message === 'FFMPEG_END' && runResolve !== null) { | |
runResolve(); | |
runResolve = null; | |
running = false; | |
} | |
}; | |
const parseMessage = ({ type, message }) => { | |
log(type, message); | |
parseProgress(message, progress); | |
detectCompletion(message); | |
}; | |
/* | |
* Load ffmpeg.wasm-core script. | |
* In browser environment, the ffmpeg.wasm-core script is fetch from | |
* CDN and can be assign to a local path by assigning `corePath`. | |
* In node environment, we use dynamic require and the default `corePath` | |
* is `$ffmpeg/core`. | |
* | |
* Typically the load() func might take few seconds to minutes to complete, | |
* better to do it as early as possible. | |
* | |
*/ | |
const load = async () => { | |
log('info', 'load ffmpeg-core'); | |
if (Core === null) { | |
log('info', 'loading ffmpeg-core'); | |
/* | |
* In node environment, all paths are undefined as there | |
* is no need to set them. | |
*/ | |
const { | |
createFFmpegCore, | |
corePath, | |
workerPath, | |
wasmPath, | |
} = await getCreateFFmpegCore(options); | |
Core = await createFFmpegCore({ | |
/* | |
* Assign mainScriptUrlOrBlob fixes chrome extension web worker issue | |
* as there is no document.currentScript in the context of content_scripts | |
*/ | |
mainScriptUrlOrBlob: corePath, | |
printErr: (message) => parseMessage({ type: 'fferr', message }), | |
print: (message) => parseMessage({ type: 'ffout', message }), | |
/* | |
* locateFile overrides paths of files that is loaded by main script (ffmpeg-core.js). | |
* It is critical for browser environment and we override both wasm and worker paths | |
* as we are using blob URL instead of original URL to avoid cross origin issues. | |
*/ | |
locateFile: (path, prefix) => { | |
if (typeof window !== 'undefined') { | |
if (typeof wasmPath !== 'undefined' | |
&& path.endsWith('ffmpeg-core.wasm')) { | |
return wasmPath; | |
} | |
if (typeof workerPath !== 'undefined' | |
&& path.endsWith('ffmpeg-core.worker.js')) { | |
return workerPath; | |
} | |
} | |
return prefix + path; | |
}, | |
}); | |
ffmpeg = Core.cwrap('main', 'number', ['number', 'number']); | |
log('info', 'ffmpeg-core loaded'); | |
} else { | |
throw Error('ffmpeg.wasm was loaded, you should not load it again, use ffmpeg.isLoaded() to check next time.'); | |
} | |
}; | |
/* | |
* Determine whether the Core is loaded. | |
*/ | |
const isLoaded = () => Core !== null; | |
/* | |
* Run ffmpeg command. | |
* This is the major function in ffmpeg.wasm, you can just imagine it | |
* as ffmpeg native cli and what you need to pass is the same. | |
* | |
* For example, you can convert native command below: | |
* | |
* ``` | |
* $ ffmpeg -i video.avi -c:v libx264 video.mp4 | |
* ``` | |
* | |
* To | |
* | |
* ``` | |
* await ffmpeg.run('-i', 'video.avi', '-c:v', 'libx264', 'video.mp4'); | |
* ``` | |
* | |
*/ | |
const run = (..._args) => { | |
log('info', `run ffmpeg command: ${_args.join(' ')}`); | |
if (Core === null) { | |
throw NO_LOAD; | |
} else if (running) { | |
throw Error('ffmpeg.wasm can only run one command at a time'); | |
} else { | |
running = true; | |
return new Promise((resolve) => { | |
const args = [...defaultArgs, ..._args].filter((s) => s.length !== 0); | |
runResolve = resolve; | |
ffmpeg(...parseArgs(Core, args)); | |
}); | |
} | |
}; | |
/* | |
* Run FS operations. | |
* For input/output file of ffmpeg.wasm, it is required to save them to MEMFS | |
* first so that ffmpeg.wasm is able to consume them. Here we rely on the FS | |
* methods provided by Emscripten. | |
* | |
* Common methods to use are: | |
* ffmpeg.FS('writeFile', 'video.avi', new Uint8Array(...)): writeFile writes | |
* data to MEMFS. You need to use Uint8Array for binary data. | |
* ffmpeg.FS('readFile', 'video.mp4'): readFile from MEMFS. | |
* ffmpeg.FS('unlink', 'video.map'): delete file from MEMFS. | |
* | |
* For more info, check https://emscripten.org/docs/api_reference/Filesystem-API.html | |
* | |
*/ | |
const FS = (method, ...args) => { | |
log('info', `run FS.${method} ${args.map((arg) => (typeof arg === 'string' ? arg : `<${arg.length} bytes binary file>`)).join(' ')}`); | |
if (Core === null) { | |
throw NO_LOAD; | |
} else { | |
let ret = null; | |
try { | |
ret = Core.FS[method](...args); | |
} catch (e) { | |
if (method === 'readdir') { | |
throw Error(`ffmpeg.FS('readdir', '${args[0]}') error. Check if the path exists, ex: ffmpeg.FS('readdir', '/')`); | |
} else if (method === 'readFile') { | |
throw Error(`ffmpeg.FS('readFile', '${args[0]}') error. Check if the path exists`); | |
} else { | |
throw Error('Oops, something went wrong in FS operation.'); | |
} | |
} | |
return ret; | |
} | |
}; | |
/** | |
* forcibly terminate the ffmpeg program. | |
*/ | |
const exit = () => { | |
if (Core === null) { | |
throw NO_LOAD; | |
} else { | |
running = false; | |
Core.exit(1); | |
Core = null; | |
ffmpeg = null; | |
runResolve = null; | |
} | |
}; | |
const setProgress = (_progress) => { | |
progress = _progress; | |
}; | |
const setLogger = (_logger) => { | |
setCustomLogger(_logger); | |
}; | |
setLogging(logging); | |
setCustomLogger(logger); | |
log('info', `use ffmpeg.wasm v${version}`); | |
return { | |
setProgress, | |
setLogger, | |
setLogging, | |
load, | |
isLoaded, | |
run, | |
exit, | |
FS, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment