blob: f3782e84e422ea0d788d7d346cc468b4b86d6301 [file]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2023-2026
* All rights reserved
*
* This file is part of GPAC / WebCodec encoder filter
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/media_dev.h>
#include <gpac/constants.h>
#ifndef GPAC_DISABLE_WEBCODEC
typedef struct
{
char *c;
GF_Fraction fintra;
Bool all_intra;
u32 b;
u32 queued;
GF_Filter *filter;
u32 codecid, dsi_crc, timescale;
u32 width, height, pfmt, stride, stride_uv, nb_planes, uv_height;
Bool codec_init;
u32 pf, out_size, in_flush;
u32 afmt, sample_rate, num_channels, bytes_per_sample;
GF_FilterPid *ipid, *opid;
GF_List *src_pcks;
GF_Err error, enc_error;
Bool fintra_setup;
u64 orig_ts;
u32 nb_forced, nb_frames_in;
char *pname;
} GF_WCEncCtx;
#if defined(GPAC_CONFIG_EMSCRIPTEN)
GF_EXPORT
void wcenc_on_error(GF_WCEncCtx *ctx, int state, char *msg)
{
//decode error
if (state > 1)
ctx->enc_error = GF_BAD_PARAM;
else if (state==1)
ctx->error = GF_PROFILE_NOT_SUPPORTED;
else {
ctx->error = GF_OK;
ctx->codec_init = GF_TRUE;
}
}
EM_JS(int, wcenc_init, (int wc_ctx, int _codec_str, int bitrate, int width, int height, double FPS, int realtime, int sample_rate, int num_channels), {
let codec_str = _codec_str ? UTF8ToString(_codec_str) : null;
let config = {};
config.codec = codec_str;
let enc_class = null;
if (width) {
config.width = width;
config.height = height;
config.framerate = FPS;
config.latency = realtime ? "realtime" : "quality";
enc_class = VideoEncoder;
if (codec_str.startsWith("avc")) {
config.avc = { format: "annexb" };
}
else if (codec_str.startsWith("hvc1") || codec_str.startsWith("hev1")) {
config.hevc = { format: "annexb" };
}
} else {
config.sampleRate = sample_rate;
config.numberOfChannels = num_channels;
enc_class = AudioEncoder;
}
config.bitrate = bitrate;
if (typeof libgpac._to_webenc != 'function') {
libgpac._web_encs = [];
libgpac._to_webenc = (ctx) => {
for (let i=0; i<libgpac._web_encs.length; i++) {
if (libgpac._web_encs[i]._wc_ctx==ctx) return libgpac._web_encs[i];
}
return null;
};
libgpac._on_wcenc_error = cwrap('wcenc_on_error', null, ['number', 'number', 'string']);
libgpac._on_wcenc_config = cwrap('wcenc_on_config', null, ['number', 'number']);
libgpac._on_wcenc_frame = cwrap('wcenc_on_frame', null, ['number', 'bigint', 'number', 'number', 'number']);
libgpac._on_wcenc_flush = cwrap('wcenc_on_flush', null, ['number']);
}
enc_class.isConfigSupported(config).then( supported => {
if (supported.supported) {
let c = libgpac._to_webenc(wc_ctx);
if (!c) {
c = {_wc_ctx: wc_ctx, enc: null, _frame: null};
libgpac._web_encs.push(c);
}
if (!c.enc) {
let init_info = {
error: (e) => {
console.log(e.message);
}
};
init_info.output = (chunk, metadata) => {
let dsize = metadata.decoderConfig || null;
if (dsize) dsize = dsize.description || null;
if (dsize) dsize = dsize.byteLength || 0;
if (dsize) {
c.decoderConfig = metadata.decoderConfig.description;
libgpac._on_wcenc_config(c._wc_ctx, c.decoderConfig.byteLength);
c.decoderConfig = null;
}
c.chunk = chunk;
let sap = 1;
if (typeof chunk.type != 'undefined') sap = (chunk.type=="key") ? 1 : 0;
libgpac._on_wcenc_frame(c._wc_ctx, BigInt(chunk.timestamp), chunk.duration, chunk.byteLength, sap);
c.chunk = null;
};
c.enc = new enc_class(init_info);
}
c.enc.configure(config);
libgpac._on_wcenc_error(wc_ctx, 0, null);
} else {
libgpac._on_wcenc_error(wc_ctx, 1, "Not Supported");
}
}).catch( (e) => {
libgpac._on_wcenc_error(wc_ctx, 1, ""+e);
});
})
static GF_Err wcenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
const GF_PropertyValue *p;
u32 streamtype;
char *codec_par=NULL;
GF_WCEncCtx *ctx = gf_filter_get_udta(filter);
if (is_remove) {
if (ctx->opid) {
gf_filter_pid_remove(ctx->opid);
ctx->opid = NULL;
}
return GF_OK;
}
if (! gf_filter_pid_check_caps(pid)) {
return GF_NOT_SUPPORTED;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
if (!p || (p->value.uint!=GF_CODECID_RAW)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[WebEnc] Missing codecid, cannot initialize\n"));
return GF_NOT_SUPPORTED;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
if (!p) return GF_NOT_SUPPORTED;
streamtype = p->value.uint;
ctx->in_flush = 0;
ctx->ipid = pid;
ctx->codecid = gf_codecid_parse(ctx->c);
if (!ctx->codecid) {
char *sep = strchr(ctx->c, '.');
if (sep) {
sep[0] = 0;
ctx->codecid = gf_codecid_parse(ctx->c);
codec_par = sep;
sep[0] = '.';
}
}
if (!ctx->codecid) {
return GF_NOT_SUPPORTED;
}
if (!ctx->opid) {
ctx->opid = gf_filter_pid_new(filter);
gf_filter_pid_set_framing_mode(pid, GF_TRUE);
}
//copy properties at init or reconfig
gf_filter_pid_copy_properties(ctx->opid, pid);
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(ctx->codecid) );
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL );
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ISOM_STSD_TEMPLATE, NULL);
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ISOM_SUBTYPE, NULL);
switch (ctx->codecid) {
//codecs for whoch we will need a reframer (DSI rebuild, DTS recompute and metadata extraction)
case GF_CODECID_AVC:
case GF_CODECID_HEVC:
case GF_CODECID_VVC:
case GF_CODECID_AV1:
case GF_CODECID_VP8:
case GF_CODECID_VP9:
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) );
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED_FULL_AU, &PROP_BOOL(GF_TRUE) );
break;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
ctx->timescale = p ? p->value.uint : 1000;
Double fps=0;
if (streamtype==GF_STREAM_VISUAL) {
p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
if (!p) return GF_BAD_PARAM;
if (ctx->width != p->value.uint) {
ctx->width = p->value.uint;
ctx->codec_init = GF_FALSE;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
if (!p) return GF_BAD_PARAM;
if (ctx->height != p->value.uint) {
ctx->height = p->value.uint;
ctx->codec_init = GF_FALSE;
}
ctx->stride = ctx->stride_uv = 0;
p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
if (p) ctx->stride = p->value.uint;
p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV);
if (p) ctx->stride_uv = p->value.uint;
ctx->nb_planes = 0;
ctx->uv_height = 0;
p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
if (p) {
u32 ifmt = p->value.uint;
ctx->pfmt = ifmt;
ctx->pname = NULL;
switch (ifmt) {
case GF_PIXEL_YUV: ctx->pname = "I420"; break;
case GF_PIXEL_YUVA: ctx->pname = "I420A"; break;
case GF_PIXEL_YUV422: ctx->pname = "I422"; break;
case GF_PIXEL_YUV444: ctx->pname = "I444"; break;
case GF_PIXEL_NV12: ctx->pname = "NV12"; break;
case GF_PIXEL_RGBA: ctx->pname = "RGBA"; break;
case GF_PIXEL_RGBX: ctx->pname = "RGBX"; break;
case GF_PIXEL_BGRA: ctx->pname = "BGRA"; break;
case GF_PIXEL_BGRX: ctx->pname = "BGRX"; break;
default:
if (gf_pixel_fmt_is_yuv(ifmt)) {
ctx->pfmt = gf_pixel_fmt_is_transparent(ifmt) ? GF_PIXEL_YUVA : GF_PIXEL_YUV;
} else {
ctx->pfmt = gf_pixel_fmt_is_transparent(ifmt) ? GF_PIXEL_RGBA : GF_PIXEL_RGBX;
}
break;
}
if (ctx->pfmt != ifmt) {
gf_filter_pid_negotiate_property(ctx->ipid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pfmt) );
return GF_OK;
}
gf_pixel_get_size_info(ctx->pfmt, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height);
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS);
if (p) {
fps = p->value.frac.num;
fps /= p->value.frac.den;
}
} else {
p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
if (!p) return GF_BAD_PARAM;
if (ctx->sample_rate != p->value.uint) {
ctx->sample_rate = p->value.uint;
ctx->codec_init = GF_FALSE;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
u32 ch = p ? p->value.uint : 1;
if (ctx->num_channels != ch) {
ctx->num_channels = ch;
ctx->codec_init = GF_FALSE;
}
p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT);
if (p) {
u32 ifmt = p->value.uint;
ctx->afmt = ifmt;
ctx->pname = NULL;
switch (ifmt) {
case GF_AUDIO_FMT_U8: ctx->pname = "u8"; break;
case GF_AUDIO_FMT_S16: ctx->pname = "s16"; break;
case GF_AUDIO_FMT_S32: ctx->pname = "s32"; break;
case GF_AUDIO_FMT_FLT: ctx->pname = "f32"; break;
case GF_AUDIO_FMT_U8P: ctx->pname = "u8-planar"; break;
case GF_AUDIO_FMT_S16P: ctx->pname = "s16-planar"; break;
case GF_AUDIO_FMT_S32P: ctx->pname = "s32-planar"; break;
case GF_AUDIO_FMT_FLTP: ctx->pname = "f32-planar"; break;
default:
ctx->afmt = GF_AUDIO_FMT_S32;
break;
}
if (ctx->afmt != ifmt) {
gf_filter_pid_negotiate_property(ctx->ipid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->afmt) );
return GF_OK;
}
ctx->bytes_per_sample = gf_audio_fmt_bit_depth(ctx->afmt) * ctx->num_channels / 8;
}
}
if (ctx->codec_init) return GF_OK;
char szCodec[RFC6381_CODEC_NAME_SIZE_MAX];
gf_strcpy(szCodec, ctx->c);
u32 ctype = gf_codecid_4cc_type(ctx->codecid);
if (ctype) {
gf_strcpy(szCodec, gf_4cc_to_str(ctype));
if (codec_par) {
gf_strcat(szCodec, codec_par);
} else {
switch (ctx->codecid) {
case GF_CODECID_AVC:
gf_strcpy(szCodec, "avc1.640028");
break;
case GF_CODECID_HEVC:
gf_strcpy(szCodec, "hvc1.1.6.L153.0");
break;
case GF_CODECID_VVC:
gf_strcpy(szCodec, "vvc1.1.H102.CQA");
break;
case GF_CODECID_AV1:
gf_strcpy(szCodec, "av01.0.08M.08");
break;
case GF_CODECID_AAC_MPEG2_MP:
case GF_CODECID_AAC_MPEG2_LCP:
case GF_CODECID_AAC_MPEG2_SSRP:
case GF_CODECID_AAC_MPEG4:
gf_strcpy(szCodec, "mp4a.40.2");
break;
}
}
strlwr(szCodec);
}
ctx->error = GF_OK;
//create encoder
wcenc_init(EM_CAST_PTR ctx,
EM_CAST_PTR szCodec,
ctx->b ? ctx->b : (ctx->width ? 2000000 : 124000),
ctx->width,
ctx->height,
fps,
0,
ctx->sample_rate,
ctx->num_channels);
return ctx->error;
}
EM_JS(int, wcenc_encode_frame, (int wc_ctx, u32 w, u32 h, u32 uv_h, int _format, u64 ts, u32 dur, u32 planes, u32 stride1, u32 stride2, u32 sap, int buf, u32 buf_size), {
let c = libgpac._to_webenc(wc_ctx);
if (!c || !buf || !_format) return;
let format = UTF8ToString(_format);
//setup source frame
let ab = new Uint8Array(HEAPU8.buffer, buf, buf_size);
let vbinit = {
format: format,
layout: [],
codedWidth: w,
codedHeight: h,
timestamp: Number(ts),
duration: dur,
};
let offset=0;
for (let i=0; i<planes; i++) {
let l={};
let p_h=h;
l.offset = offset;
l.stride = stride1;
if (i==4) l.stride = stride1;
else if (i) {
l.stride = stride2;
p_h = uv_h;
}
offset += l.stride * p_h;
vbinit.layout.push(l);
}
let vframe = new VideoFrame(ab, vbinit);
c.enc.encode(vframe, {keyFrame: sap ? true : false} );
vframe.close();
})
EM_JS(int, wcenc_encode_audio, (int wc_ctx, u32 sr, u32 ch, u32 frames, int _format, u64 ts, int buf, u32 buf_size), {
let c = libgpac._to_webenc(wc_ctx);
if (!c || !buf || !_format) return;
let format = UTF8ToString(_format);
//setup source frame
let adinit = {
format: format,
sampleRate: sr,
numberOfChannels: ch,
numberOfFrames: frames,
timestamp: Number(ts),
data: new Uint8Array(HEAPU8.buffer, buf, buf_size)
};
let adata = new AudioData(adinit);
c.enc.encode(adata);
adata.close();
})
GF_EXPORT
void wcenc_on_flush(GF_WCEncCtx *ctx)
{
ctx->in_flush = 2;
gf_filter_pid_set_eos(ctx->opid);
}
EM_JS(int, wcenc_flush, (int wc_ctx), {
let c = libgpac._to_webenc(wc_ctx);
if (!c || !c.enc) return;
c.enc.flush().then( () => {libgpac._on_wcenc_flush(c._wc_ctx); }).catch ( (e) => { libgpac._on_wcenc_flush(c._wc_ctx); } );
})
static GF_Err wcenc_process(GF_Filter *filter)
{
u64 cts=0;
GF_Err e;
const u8 *in_buffer;
u32 in_buffer_size;
GF_WCEncCtx *ctx = gf_filter_get_udta(filter);
GF_FilterPacket *pck;
if (ctx->error)
return ctx->error;
if (!ctx->codec_init) return GF_OK;
while (!ctx->enc_error) {
pck = gf_filter_pid_get_packet(ctx->ipid);
if (!pck) {
if (gf_filter_pid_is_eos(ctx->ipid)) {
if (!ctx->in_flush) {
ctx->in_flush = 1;
wcenc_flush(EM_CAST_PTR ctx);
}
if (ctx->in_flush == 1) {
gf_filter_post_process_task(filter);
return GF_OK;
}
return GF_EOS;
}
return GF_OK;
}
ctx->in_flush = 0;
//don't push too fast
if (gf_list_count(ctx->src_pcks) > ctx->queued) {
//ask for later processing to trigger breaking of scheduler loop in case of threading
gf_filter_ask_rt_reschedule(filter, 500);
return GF_OK;
}
in_buffer = (u8 *) gf_filter_pck_get_data(pck, &in_buffer_size);
cts = gf_timestamp_rescale( gf_filter_pck_get_cts(pck), ctx->timescale, 1000000);
gf_filter_pck_ref_props(&pck);
gf_list_add(ctx->src_pcks, pck);
//queue input
if (ctx->width) {
int sap = 0;
const GF_PropertyValue *p;
//force picture type
if (ctx->all_intra)
sap = 1;
//if PCK_FILENUM is set on input, this is a file boundary, force IDR sync
if (pck && gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM)) {
sap = 1;
}
//if CUE_START is set on input, this is a segment boundary, force IDR sync
p = pck ? gf_filter_pck_get_property(pck, GF_PROP_PCK_CUE_START) : NULL;
if (p && p->value.boolean) {
sap = 1;
}
//check if we need to force a closed gop
if (pck && ctx->fintra.den && (ctx->fintra.num>0)) {
if (!ctx->fintra_setup) {
ctx->fintra_setup = GF_TRUE;
ctx->orig_ts = cts;
sap = 1;
ctx->nb_forced=1;
} else if (cts < ctx->orig_ts) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[WebEnc] timestamps not increasing monotonuously, resetting forced intra state !\n"));
ctx->orig_ts = cts;
sap = 1;
ctx->nb_forced=1;
} else {
u64 ts_diff = cts - ctx->orig_ts;
if (ts_diff * ctx->fintra.den >= ctx->nb_forced * ctx->fintra.num * 1000000) {
sap = 1;
ctx->nb_forced++;
GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[WebEnc] Forcing IDR at frame %d (CTS %d / 1000000)\n", ctx->nb_frames_in, cts));
}
}
}
wcenc_encode_frame(EM_CAST_PTR ctx, ctx->width, ctx->height, ctx->uv_height, EM_CAST_PTR ctx->pname, cts, gf_filter_pck_get_duration(pck), ctx->nb_planes, ctx->stride, ctx->stride_uv, sap, EM_CAST_PTR in_buffer, in_buffer_size);
ctx->nb_frames_in++;
} else {
u32 nb_samples = in_buffer_size / ctx->bytes_per_sample;
wcenc_encode_audio(EM_CAST_PTR ctx, ctx->sample_rate, ctx->num_channels, nb_samples, EM_CAST_PTR ctx->pname, cts, EM_CAST_PTR in_buffer, in_buffer_size);
}
gf_filter_pid_drop_packet(ctx->ipid);
}
e = ctx->enc_error;
ctx->enc_error = GF_OK;
return e;
}
EM_JS(int, wcenc_get_config, (int wc_ctx, int buf, int buf_size), {
let c = libgpac._to_webenc(wc_ctx);
if (!c || !c.decoderConfig) {
throw 'Bad Param';
return;
}
//setup dst
let dst = new Uint8Array(HEAPU8.buffer, buf, buf_size);
dst.set(c.decoderConfig);
})
GF_EXPORT
void wcenc_on_config(GF_WCEncCtx *ctx, int size)
{
u8 *buf = gf_malloc(size);
memset(buf, 0, size);
wcenc_get_config(EM_CAST_PTR ctx, EM_CAST_PTR buf, size);
u32 dsi_crc = gf_crc_32(buf, size);
if (dsi_crc != ctx->dsi_crc) {
gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(buf, size));
ctx->dsi_crc = dsi_crc;
} else {
gf_free(buf);
}
}
EM_JS(int, wcenc_get_frame, (int wc_ctx, int buf, int buf_size), {
let c = libgpac._to_webenc(wc_ctx);
if (!c || !c.chunk) {
throw 'Bad Param';
return;
}
//setup dst
let dst = new Uint8Array(HEAPU8.buffer, buf, buf_size);
c.chunk.copyTo(dst);
})
GF_EXPORT
void wcenc_on_frame(GF_WCEncCtx *ctx, u64 timestamp, u32 duration, u32 size, int is_key)
{
u8 *output;
u32 i, count;
GF_FilterPacket *src_pck=NULL;
GF_FilterPacket *dst = gf_filter_pck_new_alloc(ctx->opid, size, &output);
if (!dst) return;
wcenc_get_frame(EM_CAST_PTR ctx, EM_CAST_PTR output, size);
u64 cts = gf_timestamp_rescale(timestamp, 1000000, ctx->timescale);
count = gf_list_count(ctx->src_pcks);
for (i=0; i<count; i++) {
src_pck = gf_list_get(ctx->src_pcks, i);
s64 ts = gf_filter_pck_get_cts(src_pck);
if (ABS(ts - cts) <= 1) {
gf_list_rem(ctx->src_pcks, i);
break;
}
if (ctx->sample_rate && (cts>=ts)) {
gf_list_rem(ctx->src_pcks, i);
break;
}
src_pck = NULL;
}
gf_filter_pck_set_cts(dst, cts);
if (src_pck) {
gf_filter_pck_merge_properties(src_pck, dst);
gf_filter_pck_unref(src_pck);
gf_filter_pck_set_byte_offset(dst, GF_FILTER_NO_BO);
}
gf_filter_pck_set_sap(dst, is_key ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE);
gf_filter_pck_send(dst);
if (ctx->sample_rate) {
while (gf_list_count(ctx->src_pcks)) {
src_pck = gf_list_pop_back(ctx->src_pcks);
gf_filter_pck_unref(src_pck);
}
}
}
GF_Err wcenc_initialize(GF_Filter *filter)
{
GF_WCEncCtx *ctx = gf_filter_get_udta(filter);
ctx->filter = filter;
ctx->src_pcks = gf_list_new();
return GF_OK;
}
EM_JS(int, wcenc_del, (int wc_ctx), {
if (typeof libgpac._web_encs != 'array') return;
for (let i=0; i<libgpac._web_encs.length; i++) {
if (libgpac._web_encs[i]._wc_ctx == wc_ctx) {
libgpac._web_encs[i].enc.close();
libgpac._web_encs.splice(i, 1);
return;
}
}
})
void wcenc_finalize(GF_Filter *filter)
{
GF_WCEncCtx *ctx = gf_filter_get_udta(filter);
wcenc_del(EM_CAST_PTR ctx);
while (gf_list_count(ctx->src_pcks)) {
GF_FilterPacket *pck = gf_list_pop_back(ctx->src_pcks);
gf_filter_pck_unref(pck);
}
gf_list_del(ctx->src_pcks);
}
static GF_FilterCapability WCEncCapsV[] =
{
CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_TILE_BASE, GF_TRUE),
};
static GF_FilterCapability WCEncCapsA[] =
{
CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
};
#else
static GF_Err wcenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
return GF_NOT_SUPPORTED;
}
static GF_Err wcenc_process(GF_Filter *filter)
{
return GF_NOT_SUPPORTED;
}
#endif
static GF_FilterCapability WCEncCapsAV[] =
{
CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_TILE_BASE, GF_TRUE),
{0},
CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW),
};
#define OFFS(_n) #_n, offsetof(GF_WCEncCtx, _n)
static const GF_FilterArgs WCEncArgs[] =
{
{ OFFS(c), "codec identifier. Can be any supported GPAC codec name or ffmpeg codec name - updated to ffmpeg codec name after initialization", GF_PROP_STRING, NULL, NULL, 0},
{ OFFS(fintra), "force intra / IDR frames at the given period in sec, e.g. `fintra=2` will force an intra every 2 seconds and `fintra=1001/1000` will force an intra every 30 frames on 30000/1001=29.97 fps video; ignored for audio", GF_PROP_FRACTION, "-1/1", NULL, 0},
{ OFFS(all_intra), "only produce intra frames", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED},
{ OFFS(b), "bitrate in bits per seconds, defaults to 2M for video and 124K for audio", GF_PROP_UINT, "0", NULL, 0},
{ OFFS(queued), "maximum number of packets to queue in webcodec instance", GF_PROP_UINT, "10", NULL, 0},
{0}
};
GF_FilterRegister GF_WCEncCtxRegister = {
.name = "wcenc",
GF_FS_SET_DESCRIPTION("WebCodec encoder")
GF_FS_SET_HELP("This filter encodes video streams using WebCodec encoder of the browser")
.args = WCEncArgs,
SETCAPS(WCEncCapsAV),
.flags = GF_FS_REG_SINGLE_THREAD|GF_FS_REG_ASYNC_BLOCK,
#if defined(GPAC_CONFIG_EMSCRIPTEN)
.private_size = sizeof(GF_WCEncCtx),
.initialize = wcenc_initialize,
.finalize = wcenc_finalize,
#endif
.configure_pid = wcenc_configure_pid,
.process = wcenc_process,
.hint_class_type = GF_FS_CLASS_ENCODER
};
#endif //GPAC_DISABLE_WEBCODEC
const GF_FilterRegister *wcenc_register(GF_FilterSession *session)
{
#ifndef GPAC_DISABLE_WEBCODEC
#if defined(GPAC_CONFIG_EMSCRIPTEN)
int has_webv_encode = EM_ASM_INT({
if (typeof VideoEncoder == 'undefined') return 0;
return 1;
});
int has_weba_encode = EM_ASM_INT({
if (typeof AudioEncoder == 'undefined') return 0;
return 1;
});
if (!has_webv_encode && !has_weba_encode) {
GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[WebEnc] No WebEncoder support\n"));
return NULL;
}
if (!has_webv_encode) {
GF_WCEncCtxRegister.caps = WCEncCapsA;
GF_WCEncCtxRegister.nb_caps = sizeof(WCEncCapsA)/sizeof(GF_FilterCapability);
} else if (!has_weba_encode) {
GF_WCEncCtxRegister.caps = WCEncCapsV;
GF_WCEncCtxRegister.nb_caps = sizeof(WCEncCapsV)/sizeof(GF_FilterCapability);
}
GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[WebEnc] AudioEncoder %d - VideoEncoder %d\n", has_weba_encode, has_webv_encode));
#else
if (!gf_opts_get_bool("temp", "gendoc"))
return NULL;
GF_WCEncCtxRegister.version = "! Warning: WebCodec NOT AVAILABLE IN THIS BUILD !";
#endif
return &GF_WCEncCtxRegister;
#else
return NULL;
#endif
}