Search code examples
cffmpeglibavlibavcodec

FFmpeg does not detect bitstream filter parameter


I have tried to make a bitstream filter for FFmpeg that drops the nth keyframe for positive numbers and allows the first n keyframes and drops the rest for nonpositive numbers, called "datamosh":

ffmpeg -i input.webm -bsf:v datamosh=target=20 -c copy output.webm

outputs the error:

Option 'target' not found
[vost#0:0/copy @ 0x54edc40] Error parsing bitstream filter sequence 'datamosh=target=20': Option not found

Here is the code for the "datamosh" bitstream filter:

#include "bsf.h"
#include "bsf_internal.h"

#include "libavutil/opt.h"

typedef struct {
    const AVClass *class;

    int target, i;
} DatamoshContext;

static int datamosh_init(AVBSFContext *ctx)
{
    DatamoshContext *s = ctx->priv_data;

    s->i = 0;

    return 0;
}

static int datamosh(AVBSFContext *ctx, AVPacket *pkt)
{
    DatamoshContext *s = ctx->priv_data;
    int ret;

    ret = ff_bsf_get_packet_ref(ctx, pkt);
    if (ret < 0)
        return ret;

    if (s->target < 1) {
        if (s->i <= -s->target)
            return 0;
        else {
            av_packet_unref(pkt);
            return AVERROR(EAGAIN);
        }
    }

    if (pkt->flags & AV_PKT_FLAG_KEY)
        ++s->i;

    if (s->i == s->target) {
        av_packet_unref(pkt);
        return AVERROR(EAGAIN);
    }

    return 0;
}


#define OFFSET(x) offsetof(DatamoshContext, x)
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)

static const AVOption options[] = {
    { "target", NULL, OFFSET(target), AV_OPT_TYPE_INT, { .i64 = 0 }, -INT_MAX, INT_MAX, FLAGS },
    { NULL },
};

static const AVClass datamosh_class = {
    .class_name = "datamosh",
    .item_name = av_default_item_name,
    .option = options,
    .version = LIBAVUTIL_VERSION_INT,
};

const FFBitStreamFilter ff_datamosh_bsf = {
    .p.name = "datamosh",
    .p.priv_class = &datamosh_class,
    .priv_data_size = sizeof(DatamoshContext),
    .init = datamosh_init,
    .filter = datamosh,
};

n is called "target" in the code.

Weirdly, ffmpeg -h bsf=datamosh prints:

Bit stream filter datamosh
datamosh AVOptions:
  -target            <int>        ...VA...B.. (from -2.14748e+09 to INT_MAX) (default 0)

Solution

  • I have fixed my code. There were problems with dropping packets properly. Here is the fix:

    #include <stdbool.h>
    
    #include "bsf.h"
    #include "bsf_internal.h"
    
    #include "libavutil/opt.h"
    
    typedef struct {
        const AVClass *class;
    
        int target, i;
    } DatamoshContext;
    
    static int datamosh_init(AVBSFContext *ctx)
    {
        DatamoshContext *s = ctx->priv_data;
    
        s->i = 0;
    
        return 0;
    }
    
    static int datamosh(AVBSFContext *ctx, AVPacket *pkt)
    {
        DatamoshContext *s = ctx->priv_data;
        bool key;
        int ret;
    
        ret = ff_bsf_get_packet_ref(ctx, pkt);
        if (ret < 0)
            return ret;
    
        key = pkt->flags & AV_PKT_FLAG_KEY;
        if (key)
            ++s->i;
    
        if (s->target < 1) {
            if (s->i <= -s->target)
                return 0;
            else if (key) {
                av_packet_unref(pkt);
                return AVERROR(EAGAIN);
            }
        }
    
        if (key && s->i == s->target) {
            av_packet_unref(pkt);
            return AVERROR(EAGAIN);
        }
    
        return 0;
    }
    
    
    #define OFFSET(x) offsetof(DatamoshContext, x)
    #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)
    
    static const AVOption options[] = {
        { "target", NULL, OFFSET(target), AV_OPT_TYPE_INT, { .i64 = 0 }, -INT_MAX, INT_MAX, FLAGS },
        { NULL },
    };
    
    static const AVClass datamosh_class = {
        .class_name = "datamosh",
        .item_name = av_default_item_name,
        .option = options,
        .version = LIBAVUTIL_VERSION_INT,
    };
    
    const FFBitStreamFilter ff_datamosh_bsf = {
        .p.name = "datamosh",
        .p.priv_class = &datamosh_class,
        .priv_data_size = sizeof(DatamoshContext),
        .init = datamosh_init,
        .filter = datamosh,
    };