More work

This commit is contained in:
Vanilagy
2026-02-12 12:47:52 +01:00
parent 18d64ffb33
commit b2d00f84d8
12 changed files with 490 additions and 2 deletions
+46
View File
@@ -0,0 +1,46 @@
```bash
emmake make distclean
emconfigure ./configure \
--target-os=none \
--arch=x86_32 \
--enable-cross-compile \
--disable-asm \
--disable-x86asm \
--disable-inline-asm \
--disable-stripping \
--disable-programs \
--disable-doc \
--disable-debug \
--disable-all \
--disable-everything \
--disable-pthreads \
--enable-avcodec \
--enable-decoder=prores \
--cc="emcc" \
--cxx=em++ \
--ar=emar \
--ranlib=emranlib \
--extra-cflags="-DNDEBUG -O3 -msimd128"
emmake make
```
```bash
export FFMPEG_PATH=/path/to/ffmpeg
emcc src/bridge.c \
$FFMPEG_PATH/libavcodec/libavcodec.a \
$FFMPEG_PATH/libavutil/libavutil.a \
-I$FFMPEG_PATH \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-s SINGLE_FILE=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ENVIRONMENT=web,worker \
-s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \
-s EXPORTED_FUNCTIONS=_malloc,_free \
-msimd128 \
-O3 \
-o build/prores.js
```
Binary file not shown.
+52
View File
@@ -0,0 +1,52 @@
{
"name": "@mediabunny/prores-decoder",
"author": "Vanilagy",
"version": "1.25.8",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"types": "./dist/modules/src/index.d.ts",
"exports": {
"types": "./dist/modules/src/index.d.ts",
"import": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"require": "./dist/bundles/mediabunny-mp3-encoder.mjs"
},
"files": [
"README.md",
"package.json",
"LICENSE",
"dist",
"src"
],
"sideEffects": false,
"license": "MPL-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/Vanilagy/mediabunny.git",
"directory": "packages/mp3-encoder"
},
"bugs": {
"url": "https://github.com/Vanilagy/mediabunny/issues"
},
"homepage": "https://mediabunny.dev/guide/extensions/mp3-encoder",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/Vanilagy"
},
"peerDependencies": {
"mediabunny": "^1.0.0"
},
"devDependencies": {
"@types/emscripten": "^1.40.1"
},
"keywords": [
"mp3",
"encoding",
"codec",
"mediabunny",
"lame",
"browser",
"wasm",
"polyfill"
]
}
+182
View File
@@ -0,0 +1,182 @@
#include <emscripten.h>
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavutil/pixdesc.h"
#include "libavutil/imgutils.h"
typedef struct {
AVCodecContext *codec_ctx;
AVPacket *packet;
AVFrame *frame;
uint8_t *buffer;
int buffer_size;
} DecoderContext;
EMSCRIPTEN_KEEPALIVE
DecoderContext *init_decoder() {
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_PRORES);
if (!codec) return NULL;
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) return NULL;
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
avcodec_free_context(&codec_ctx);
return NULL;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
avcodec_free_context(&codec_ctx);
return NULL;
}
AVFrame *frame = av_frame_alloc();
if (!frame) {
av_packet_free(&packet);
avcodec_free_context(&codec_ctx);
return NULL;
}
DecoderContext *ctx = malloc(sizeof(DecoderContext));
if (!ctx) {
av_frame_free(&frame);
av_packet_free(&packet);
avcodec_free_context(&codec_ctx);
return NULL;
}
ctx->codec_ctx = codec_ctx;
ctx->packet = packet;
ctx->frame = frame;
ctx->buffer = NULL;
ctx->buffer_size = 0;
return ctx;
}
EMSCRIPTEN_KEEPALIVE
uint8_t *configure_packet(DecoderContext *ctx, int size) {
if (av_new_packet(ctx->packet, size) < 0) {
return NULL;
}
ctx->packet->pts = 0;
ctx->packet->dts = AV_NOPTS_VALUE;
ctx->packet->time_base.num = 1;
ctx->packet->time_base.den = 1;
ctx->packet->flags = AV_PKT_FLAG_KEY;
return ctx->packet->data;
}
EMSCRIPTEN_KEEPALIVE
int decode_packet(DecoderContext *ctx) {
double start = emscripten_get_now();
int ret = avcodec_send_packet(ctx->codec_ctx, ctx->packet);
if (ret < 0) {
return ret;
}
double after_send = emscripten_get_now();
ret = avcodec_receive_frame(ctx->codec_ctx, ctx->frame);
if (ret < 0) {
return ret;
}
double after_receive = emscripten_get_now();
// Calculate required buffer size
int required_size = av_image_get_buffer_size(
ctx->frame->format,
ctx->frame->width,
ctx->frame->height,
1
);
if (required_size < 0) {
return required_size;
}
// Reallocate buffer if needed
if (ctx->buffer_size < required_size) {
free(ctx->buffer);
ctx->buffer = malloc(required_size);
if (!ctx->buffer) {
ctx->buffer_size = 0;
return AVERROR(ENOMEM);
}
ctx->buffer_size = required_size;
}
double after_alloc = emscripten_get_now();
// Copy frame data to contiguous buffer
ret = av_image_copy_to_buffer(
ctx->buffer,
ctx->buffer_size,
(const uint8_t * const *)ctx->frame->data,
ctx->frame->linesize,
ctx->frame->format,
ctx->frame->width,
ctx->frame->height,
1
);
if (ret < 0) {
return ret;
}
double after_copy = emscripten_get_now();
printf("Timing: send=%.2fms receive=%.2fms alloc=%.2fms copy=%.2fms total=%.2fms\n",
after_send - start,
after_receive - after_send,
after_alloc - after_receive,
after_copy - after_alloc,
after_copy - start);
return 0;
}
EMSCRIPTEN_KEEPALIVE
int get_frame_width(DecoderContext *ctx) {
return ctx->frame->width;
}
EMSCRIPTEN_KEEPALIVE
int get_frame_height(DecoderContext *ctx) {
return ctx->frame->height;
}
EMSCRIPTEN_KEEPALIVE
int get_frame_format(DecoderContext *ctx) {
return ctx->frame->format;
}
EMSCRIPTEN_KEEPALIVE
int get_frame_num_planes(DecoderContext *ctx) {
return av_pix_fmt_count_planes(ctx->frame->format);
}
EMSCRIPTEN_KEEPALIVE
int get_frame_linesize(DecoderContext *ctx, int n) {
return ctx->frame->linesize[n];
}
EMSCRIPTEN_KEEPALIVE
uint8_t *get_frame_data(DecoderContext *ctx, int n) {
return ctx->frame->data[n];
}
EMSCRIPTEN_KEEPALIVE
uint8_t *get_frame_data_ptr(DecoderContext *ctx) {
return ctx->buffer;
}
EMSCRIPTEN_KEEPALIVE
int get_frame_data_size(DecoderContext *ctx) {
return ctx->buffer_size;
}
+90
View File
@@ -0,0 +1,90 @@
import { CustomVideoDecoder, EncodedPacket, MaybePromise, VideoCodec } from 'mediabunny';
import createModule from '../build/prores';
import { VideoSample } from 'mediabunny';
type ExtendedEmscriptenModule = EmscriptenModule & {
cwrap: typeof cwrap;
};
export class ProResDecoder extends CustomVideoDecoder {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static override supports(codec: VideoCodec, config: VideoDecoderConfig): boolean {
return codec === 'prores';
}
module!: ExtendedEmscriptenModule;
initDecoder!: () => number;
configurePacket!: (ctx: number, size: number) => number;
decodePacket!: (ctx: number) => number;
getFrameWidth!: (ctx: number) => number;
getFrameHeight!: (ctx: number) => number;
getFrameFormat!: (ctx: number) => number;
getFrameNumPlanes!: (ctx: number) => number;
getFrameLinesize!: (ctx: number, n: number) => number;
getFrameData!: (ctx: number, n: number) => number;
getFrameDataPtr!: (ctx: number) => number;
getFrameDataSize!: (ctx: number) => number;
decoderContextPtr!: number;
override async init() {
console.log('THIS IS BEING CALLED');
this.module = (await createModule()) as ExtendedEmscriptenModule;
// Set up the functions
this.initDecoder = this.module.cwrap('init_decoder', 'number', []);
this.configurePacket = this.module.cwrap('configure_packet', 'number', ['number', 'number']);
this.decodePacket = this.module.cwrap('decode_packet', 'number', ['number']);
this.getFrameWidth = this.module.cwrap('get_frame_width', 'number', ['number']);
this.getFrameHeight = this.module.cwrap('get_frame_height', 'number', ['number']);
this.getFrameFormat = this.module.cwrap('get_frame_format', 'number', ['number']);
this.getFrameNumPlanes = this.module.cwrap('get_frame_num_planes', 'number', ['number']);
this.getFrameLinesize = this.module.cwrap('get_frame_linesize', 'number', ['number', 'number']);
this.getFrameData = this.module.cwrap('get_frame_data', 'number', ['number', 'number']);
this.getFrameDataPtr = this.module.cwrap('get_frame_data_ptr', 'number', ['number']);
this.getFrameDataSize = this.module.cwrap('get_frame_data_size', 'number', ['number']);
this.decoderContextPtr = this.initDecoder();
}
override decode(packet: EncodedPacket) {
const dataPtr = this.configurePacket(this.decoderContextPtr, packet.byteLength);
if (dataPtr === 0) {
throw new Error('todo');
}
this.module.HEAPU8.set(packet.data, dataPtr);
console.time();
const ret = this.decodePacket(this.decoderContextPtr);
console.timeEnd();
if (ret === 0) {
const width = this.getFrameWidth(this.decoderContextPtr);
const height = this.getFrameHeight(this.decoderContextPtr);
const format = this.getFrameFormat(this.decoderContextPtr);
const dataPtr = this.getFrameDataPtr(this.decoderContextPtr);
const dataSize = this.getFrameDataSize(this.decoderContextPtr);
const data = this.module.HEAPU8.subarray(dataPtr, dataPtr + dataSize);
const sample = new VideoSample(data, {
format: 'I422P10' as VideoPixelFormat,
codedWidth: width,
codedHeight: height,
timestamp: packet.timestamp,
duration: packet.duration,
});
this.onSample(sample);
}
}
override flush(): MaybePromise<void> {
// nada
}
override close(): MaybePromise<void> {
// nada
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../dist/modules",
"declaration": true,
"declarationMap": true,
"stripInternal": true,
"composite": true,
"noEmit": false,
"moduleResolution": "nodenext",
"module": "nodenext",
"allowJs": true,
"paths": {
"mediabunny": ["../../../src/index.ts"],
},
},
"include": [
"**/*",
],
"references": [
{ "path": "../../../src" }
]
}