Merge pull request #7 from alfg/refactor/worker-module-calls

Refactor module calls into a Web Worker to prevent blocking main thread
This commit is contained in:
Alfred Gutierrez
2020-12-22 14:40:48 -08:00
committed by GitHub
12 changed files with 141 additions and 94 deletions

View File

@@ -1,4 +1,4 @@
FROM emscripten/emsdk as build
FROM emscripten/emsdk:2.0.11 as build
ARG FFMPEG_VERSION=4.3.1
ARG X264_VERSION=20170226-2245-stable
@@ -30,7 +30,7 @@ RUN cd /tmp/ && \
wget http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz && \
tar zxf ffmpeg-${FFMPEG_VERSION}.tar.gz && rm ffmpeg-${FFMPEG_VERSION}.tar.gz
ARG CFLAGS="-s USE_PTHREADS -O3 -I${PREFIX}/include"
ARG CFLAGS="-s USE_PTHREADS=1 -O3 -I${PREFIX}/include"
ARG LDFLAGS="$CFLAGS -L${PREFIX}/lib -s INITIAL_MEMORY=33554432"
# Compile ffmpeg.

View File

@@ -6,7 +6,8 @@ dist/ffprobe-wasm.js:
-I/opt/ffmpeg/include/ \
-s EXTRA_EXPORTED_RUNTIME_METHODS="[FS, cwrap, ccall, getValue, setValue, writeAsciiToMemory]" \
-s INITIAL_MEMORY=268435456 \
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 \
-pthread \
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil -lm -lx264 -pthread \
-lworkerfs.js \
-o dist/ffprobe-wasm.js \
src/ffprobe-wasm-wrapper.cpp

View File

@@ -1,5 +1,6 @@
#include <vector>
#include <string>
#include <vector>
#include <inttypes.h>
#include <emscripten.h>
#include <emscripten/bind.h>
@@ -65,8 +66,8 @@ typedef struct FramesResponse {
int nb_frames;
} FramesResponse;
FileInfoResponse get_file_info() {
FILE *file = fopen("file", "rb");
FileInfoResponse get_file_info(std::string filename) {
FILE *file = fopen(filename.c_str(), "rb");
if (!file) {
printf("cannot open file\n");
}
@@ -79,9 +80,8 @@ FileInfoResponse get_file_info() {
// Open the file and read header.
int ret;
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
printf("ERROR: could not open the file. Error: %d\n", ret);
printf("%s", av_err2str(ret));
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
printf("ERROR: %s\n", av_err2str(ret));
}
// Get stream info from format.
@@ -135,10 +135,10 @@ FileInfoResponse get_file_info() {
return r;
}
FramesResponse get_frames(int offset) {
FramesResponse get_frames(std::string filename, int offset) {
av_log_set_level(AV_LOG_QUIET); // No logging output for libav.
FILE *file = fopen("file", "rb");
FILE *file = fopen(filename.c_str(), "rb");
if (!file) {
printf("cannot open file\n");
}
@@ -151,9 +151,8 @@ FramesResponse get_frames(int offset) {
// Open the file and read header.
int ret;
if ((ret = avformat_open_input(&pFormatContext, "file", NULL, NULL)) < 0) {
printf("ERROR: could not open the file. Error: %d\n", ret);
printf("%s", av_err2str(ret));
if ((ret = avformat_open_input(&pFormatContext, filename.c_str(), NULL, NULL)) < 0) {
printf("ERROR: %s\n", av_err2str(ret));
}
// Get stream info from format.
@@ -239,7 +238,6 @@ FramesResponse get_frames(int offset) {
return r;
}
EMSCRIPTEN_BINDINGS(constants) {
function("avformat_version", &c_avformat_version);
function("avcodec_version", &c_avcodec_version);

2
www/.gitignore vendored
View File

@@ -22,5 +22,5 @@ pnpm-debug.log*
*.sln
*.sw?
public/*.js
public/*-wasm*.js
public/*.wasm

2
www/package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ffprobe-wasm",
"version": "0.2.1",
"version": "0.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "ffprobe-wasm",
"version": "0.2.1",
"version": "0.3.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",

View File

@@ -0,0 +1,72 @@
onmessage = (e) => {
const type = e.data[0];
const file = e.data[1];
let data;
switch (type) {
case 'get_file_info':
// Mount FS for files.
if (!FS.analyzePath('/work').exists) {
FS.mkdir('/work');
}
FS.mount(WORKERFS, { files: [file] }, '/work');
// Call the wasm module.
const info = Module.get_file_info('/work/' + file.name);
// Remap streams into collection.
const s = [];
for (let i = 0; i < info.streams.size(); i++) {
s.push(info.streams.get(i));
}
const versions = {
libavutil: Module.avutil_version(),
libavcodec: Module.avcodec_version(),
libavformat: Module.avformat_version(),
};
// Send back data response.
data = {
...info,
streams: s,
versions,
}
postMessage(data);
// Cleanup mount.
FS.unmount('/work');
break;
case 'get_frames':
if (!FS.analyzePath('/work').exists) {
FS.mkdir('/work');
}
FS.mount(WORKERFS, { files: [file] }, '/work');
const offset = e.data[2];
const frames = Module.get_frames('/work/' + file.name, offset);
// Remap frames into collection.
const f = [];
for (let i = 0; i < frames.frames.size(); i++) {
f.push(frames.frames.get(i));
}
data = {
...frames,
frames: f,
}
postMessage(data);
// Cleanup mount.
FS.unmount('/work');
break;
default:
break;
}
}
self.importScripts('ffprobe-wasm.js'); // Load ffprobe into worker context.

View File

@@ -12,7 +12,6 @@
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script src="ffprobe-wasm.js"></script>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -12,23 +12,17 @@
></b-form-file>
</b-form-group>
<b-progress
height="2px"
v-if="showProgress"
:value="progress"
max="100"></b-progress>
<div v-if="data">
<div v-if="info">
<div class="mt-3">Selected file: {{ file ? `${file.name}: ${file.size} bytes` : '' }}</div>
<b-tabs class="mt-4">
<b-tab title="Overview" class="mt-2">
<div v-if="data">
<div v-if="info">
<Overview :info="info" />
</div>
</b-tab>
<b-tab title="Frames" class="mt-2" lazy>
<Frames />
<Frames :file="file" />
</b-tab>
</b-tabs>
</div>
@@ -49,37 +43,19 @@ export default {
return {
file: null,
data: null,
progress: 0,
showProgress: false,
info: null,
}
},
computed: {
info() {
return this.data && window.Module.get_file_info();
created() {
this.$worker.onmessage = (e) => {
this.info = e.data;
}
},
methods: {
onFile(event) {
this.data = null;
this.progress = 0;
this.showProgress = true;
const file = event.dataTransfer ? event.dataTransfer.files[0] : event.target.files[0];
const reader = new FileReader();
// reader.onload = e => this.$emit("load", event.target.result);
reader.onload = (event) => {
this.progress = 100;
this.data = new Uint8Array(event.target.result);
window.Module.FS.writeFile('file', new Uint8Array(this.data));
setTimeout(() => { this.showProgress = false; }, 2000);
}
reader.onprogress = (event) => {
if (event.lengthComputable) {
this.progress = parseInt(((event.loaded / event.total) * 100), 10);
}
}
reader.readAsArrayBuffer(file);
this.$worker.postMessage([ 'get_file_info', file ]);
}
}
}

View File

@@ -1,62 +1,69 @@
<template>
<div>
<h4>Frames</h4>
<p class="text-right">Total: {{ data.nb_frames }}</p>
<div v-if="!data">Loading...</div>
<div v-if="data">
<p class="text-right">Total: {{ data.nb_frames }}</p>
<b-pagination
v-model="currentPage"
@change="onPageChanged"
:total-rows="pages"
:per-page="perPage"
align="right"
></b-pagination>
<b-pagination
v-model="currentPage"
@change="onPageChanged"
:total-rows="pages"
:per-page="perPage"
align="right"
></b-pagination>
<b-table striped hover :items="frames">
<template #cell(pict_type)="data">
{{ String.fromCharCode(data.value) }}
</template>
</b-table>
<b-table striped hover :items="data.frames" :busy="isBusy">
<template #table-busy>
<div class="text-center text-primary my-2">
<b-spinner class="align-middle"></b-spinner>
</div>
</template>
<template #cell(pict_type)="data">
{{ String.fromCharCode(data.value) }}
</template>
</b-table>
<b-pagination
v-model="currentPage"
@change="onPageChanged"
:total-rows="pages"
:per-page="perPage"
align="right"
></b-pagination>
<b-pagination
v-model="currentPage"
@change="onPageChanged"
:total-rows="pages"
:per-page="perPage"
align="right"
></b-pagination>
</div>
</div>
</template>
<script>
export default {
name: 'Frames',
props: ['file'],
data() {
return {
data: null,
currentPage: 1,
perPage: 48,
isBusy: false,
};
},
computed: {
pages() {
return this.data.nb_frames;
},
frames() {
if (!this.data) return [];
const s = [];
for (let i = 0; i < this.data.frames.size(); i++) {
s.push(this.data.frames.get(i));
}
return s;
},
},
created() {
this.data = window.Module.get_frames(0);
this.$worker.onmessage = (e) => {
this.data = e.data;
this.isBusy = false;
}
this.$worker.postMessage([ 'get_frames', this.file, 0 ]);
},
methods: {
onPageChanged(page) {
this.data = window.Module.get_frames(this.perPage * (page - 1));
this.isBusy = true;
this.$worker.postMessage([ 'get_frames', this.file, this.perPage * (page - 1) ]);
window.scrollTo(0, 0);
},
}

View File

@@ -7,7 +7,7 @@
<b-table stacked :items="items"></b-table>
<h4>Streams</h4>
<b-table striped hover :items="streams"></b-table>
<b-table striped hover :items="info.streams"></b-table>
</div>
</template>
@@ -15,15 +15,6 @@
export default {
name: 'Overview',
props: ['info'],
computed: {
streams() {
const s = [];
for (let i = 0; i < this.info.streams.size(); i++) {
s.push(this.info.streams.get(i));
}
return s;
}
},
data() {
return {
items: [
@@ -38,9 +29,9 @@ export default {
],
versions: [
{
libavutil: window.Module.avutil_version(),
libavcodec: window.Module.avcodec_version(),
libavformat: window.Module.avformat_version(),
libavutil: this.info.versions.libavutil,
libavcodec: this.info.versions.libavcodec,
libavformat: this.info.versions.libavformat,
}
]
}

View File

@@ -5,6 +5,9 @@ import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
const worker = new Worker('ffprobe-worker.js');
Vue.prototype.$worker = worker;
Vue.use(BootstrapVue);
Vue.config.productionTip = false