FFmpeg codec HOWTO

From MultimediaWiki
Jump to navigation Jump to search

This page is meant as an introduction to the internal codec API in FFmpeg. It will also show how the codecs are connected with the demuxers. This is by no means a complete guide but enough to understand how to add a codec to FFmpeg. Cook is used as an example throughout.

registering the codec

libavcodec/avcodec.h

The first thing to look at is the AVCodec struct.

typedef struct AVCodec {
    const char *name;
    enum CodecType type;
    enum CodecID id;
    int priv_data_size;
    int (*init)(AVCodecContext *);
    int (*encode)(AVCodecContext *, uint8_t *buf, int buf_size, void *data);
    int (*close)(AVCodecContext *);
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size,
                  uint8_t *buf, int buf_size);
    int capabilities;
    struct AVCodec *next;
    void (*flush)(AVCodecContext *);
    const AVRational *supported_framerates; ///array of supported framerates, or NULL if any, array is terminated by {0,0}
    const enum PixelFormat *pix_fmts;       ///array of supported pixel formats, or NULL if unknown, array is terminanted by -1
} AVCodec;

Here we can see that we have some elements to name the codec, what type it is (audio/video), the supported pixel formats and some function pointers for init/encode/decode and close. Now lets see how it is used.

libavcodec/cook.c

If we look in this file at the bottom we can see this code:

AVCodec cook_decoder =
{
    .name           = "cook",
    .type           = CODEC_TYPE_AUDIO,
    .id             = CODEC_ID_COOK,
    .priv_data_size = sizeof(COOKContext),
    .init           = cook_decode_init,
    .close          = cook_decode_close,
    .decode         = cook_decode_frame,
};

First we get an AVCodec struct named cook_decoder. And then we set the variables of cook_decoder. Note that we only set the variables that are needed. Currently there is no encoder so we don't set any. If we now look at the id variable we can see that CODEC_ID_COOK isn't defined in libavcodec/cook.c. It is declared in avcodec.h.

libavcodec/avcodec.h

Here we will find the CodecID enumeration.

enum CodecID {
...
CODEC_ID_GSM,
CODEC_ID_QDM2,
CODEC_ID_COOK,
CODEC_ID_TRUESPEECH,
CODEC_ID_TTA,
...
};

CODEC_ID_COOK is there in the list. This is the list of all supported codecs in FFmpeg, the list is fixed and used internally to id every codec. Changing the order would break binary compatibility.

This is all enough to declare a codec. Now we must register them for internal use also. This is done at runtime.

libavcodec/allcodecs.c

In this file we have the avcodec_register_all() function, it has entries like this for all codecs.

...
   extern AVCodec cook_decoder; // must end with _decoder
...

This is expanded at configure time into a REGISTER_CODEC(...)

configure accomplishes this by running something like:

sed -n 's/^[^#]*DEC.*, *\(.*\)).*/\1_decoder/p' libavcodec/allcodecs.c

So adding an entry in allcodecs.c and reconfigure is enough to add the needed define. Now we have everything to hookup a codec.

libavcodec/Makefile

In this file we define the objects on which a codec depends. For example, cook uses fft and mdct code so it depends on the mdct.o and fft.o object files as well as the cook.o object file.

...
OBJS-$(CONFIG_COOK_DECODER)            += cook.o mdct.o fft.o
...

FFmpeg demuxer connection

FFmpeg demuxer howto

libavformat/rm.c

If we think of an imaginary rm file that ffmpeg is about to process, the first thing that happens is that it is identified as a rm file. It is passed on to the rm demuxer (rmdec.c). The rm demuxer looks through the file and finds out that it is a cook file.

...
} else if (!strcmp(buf, "cook")) {
st->codec->codec_id = CODEC_ID_COOK;
...

Now ffmpeg knows what codec to init and where to send the payload from the container. So back to cook.c and the initialization process.

codec code

libavcodec/cook.c Init

After ffmpeg knows what codec to use, it calls the declared initialization function pointer declared in the codecs AVCodec struct. In cook.c it is called cook_decode_init. Here we setup as much as we can before we start decoding. The following things should be handled in the init, vlc table initialization, table generation, memory allocation and extradata parsing.

libavcodec/cook.c Close

The cook_decode_close function is the codec clean-up call. All memory, vlc tables, etc. should be freed here.

libavcodec/cook.c Decode

In cook.c the name of the decode call is cook_decode_frame.

static int cook_decode_frame(AVCodecContext *avctx,
            void *data, int *data_size,
            uint8_t *buf, int buf_size) {
...

The function has 5 arguments:

  • avctx is a pointer to an AVCodecContext
  • data is the pointer to the output buffer
  • data_size is a variable that should be set to the output buffer size in bytes (this is usually the number of samples decoded * the number of channels * the byte size of a sample)
  • buf is the pointer to the input buffer
  • buf_size is the byte size of the input buffer

The decode function shall return the number of bytes consumed from the input buffer or -1 in case of an error. If there is no error during decoding, the return value is usually buf_size as buf should only contain one 'frame' of data. Bitstream parsers to split the bitstream into 'frames' used to be part of the codec so a call to the decode function could have consumed less than buf_size bytes from buf. It is now encouraged that bitstream parsers be separate.


That's how it works without too much detail.

The Glue codec template

The imaginary Glue audio codec will serve as a base to exhibit bitstream reading, vlc decoding and other things. The code is purely fictional and is sometimes written purely for the sake of example. No attempt is made to prevent invalid data manipulation.

The Glue codec follows.

non-colored version

/* The following includes have the bitstream reader, various dsp functions and the various defaults */
#define ALT_BITSTREAM_READER
#include "avcodec.h"
#include "bitstream.h"
#include "dsputil.h"

/* This includes the tables needed for the Glue codec template */
#include "gluedata.h"


/* Here we declare the struct used for the codec private data */
typedef struct {
    GetBitContext       gb;
    FFTContext          fft_ctx;
    VLC                 vlc_table;
    MDCTContext         mdct_ctx;
    float*              sample_buffer;
} GLUEContext;


/* The init function */
static int glue_decode_init(AVCodecContext *avctx)
{
    GLUEContext *q = avctx->priv_data;

    /* This imaginary codec uses one fft, one mdct and one vlc table. */
    ff_mdct_init(&q->mdct_ctx, 10, 1);    // 2^10 == size of mdct, 1 == inverse mdct
    ff_fft_init(&q->fft_ctx, 9, 1);       // 2^9 == size of fft, 0 == inverse fft
    init_vlc (&q->vlc_table, 9, 24,
           vlctable_huffbits, 1, 1,
           vlctable_huffcodes, 2, 2, 0);  // look in bitstream.h for the meaning of the arguments

    /* We also need to allocate a sample buffer */
    q->sample_buffer = av_mallocz(sizeof(float)*1024);  // here we used av_mallocz instead of av_malloc
                                                        // av_mallocz memsets the whole buffer to 0

    /* Check if the allocation was successful */
    if(q->sample_buffer == NULL)
        return -1;

    /* return 0 for a successful init, -1 for failure */
    return 0;
}


/* This is the main decode function */
static int glue_decode_frame(AVCodecContext *avctx,
           void *data, int *data_size,
           uint8_t *buf, int buf_size)
{
    GLUEContext *q = avctx->priv_data;
    int16_t *outbuffer = data;

    /* We know what the arguments for this function are from above
       now we just have to decode this imaginary codec, the made up
       bitstream format is as follows:
       12 bits representing the amount of samples
       1 bit fft or mdct coded coeffs, 0 for fft/1 for mdct
         read 13 bits representing the amount of vlc coded fft data coeffs
         read 10 bits representing the amount of vlc coded mdct data coeffs
       (...bits representing the coeffs...)
       5 bits of dummy data that should be ignored
       32 bits the hex value 0x12345678, used for integrity check
    */

    /* Declare the needed variables */
    int samples, coeffs, i, fft;
    float mdct_tmp[1024];

    /* Now we init the bitstream reader, we start at the beginning of the inbuffer */
    init_get_bits(&q->gb, buf, buf_size*8);  //the buf_size is in bytes but we need bits

    /* Now we take 12 bits to get the amount of samples the current frame has */
    samples = get_bits(&q->gb, 12);
    
    /* Now we check if we have fft or mdct coeffs */
    fft = get_bits1(&q->gb);
    if (fft) {
        //fft coeffs, get how many
        coeffs = get_bits(&q->gb, 13);
    } else {
        //mdct coeffs, get how many
        coeffs = get_bits(&q->gb, 10);
    }

    /* Now decode the vlc coded coeffs to the sample_buffer */
    for (i=0 ; i<coeffs ; i++)
        q->sample_buffer[i] = get_vlc2(&q->gb, q->vlc_table.table, vlc_table.bits, 3);  //read about the arguments in bitstream.h

    /* Now we need to transform the coeffs to samples */
    if (fft) {
        //The fft is done inplace
        ff_fft_permute(&q->fft_ctx, (FFTComplex *) q->sample_buffer);
        ff_fft_calc(&q->fft_ctx, (FFTComplex *) q->sample_buffer);
    } else {
        //And we pretend that the mdct is also inplace
        ff_imdct_calc(&q->mdct_ctx, q->sample_buffer, q->sample_buffer, mdct_tmp);
    }

    /* To make it easy the stream can only be 16 bits mono, so let's convert it to that */
    for (i=0 ; i<samples ; i++)
        outbuffer[i] = (int16_t)q->sample_buffer[i];

    /* Report how many samples we got */
    *data_size = samples;

    /* Skip the dummy data bits */
    skip_bits(&q->gb, 5);

    /* Check if the buffer was consumed ok */
    if (get_bits(&q->gb,32) != 0x12345678) {
        av_log(avctx,AV_LOG_ERROR,"Stream error, integrity check failed!\n");
        return -1;
    }

    /* The decision between erroring out or not in case of unexpected data
       should be made so that the output quality is maximized.
       This means that if undamaged data is assumed then unused/resereved values
       should lead to warnings but not failure. (assumption of slightly non compliant
       file)
       OTOH if possibly damaged data is assumed and it is assumed that the original
       did contain specific values in reserved/unused fields then finding unexpected
       values should trigger error concealment code and the decoder/demuxer should
       attempt to resync.
       The decision between these 2 should be made by using 
       AVCodecContext.error_recognition unless its a clear case where only one of
       the 2 makes sense.
    */


    /* Return the amount of bytes consumed if everything was ok */
    return *data_size*sizeof(int16_t);
}


/* the uninit function, here we just do the inverse of the init */ 
static int glue_decode_close(AVCodecContext *avctx)
{
    GLUEContext *q = avctx->priv_data;

    /* Free allocated memory buffer */
    av_free(q->sample_buffer);

    /* Free the fft transform */
    ff_fft_end(&q->fft_ctx);

    /* Free the mdct transform */
    ff_mdct_end(&q->mdct_ctx);

    /* Free the vlc table */
    free_vlc(&q->vlc_table);

    /* Return 0 if everything is ok, -1 if not */
    return 0;
}


AVCodec glue_decoder =
{
    .name           = "glue",
    .type           = CODEC_TYPE_AUDIO,
    .id             = CODEC_ID_GLUE,
    .priv_data_size = sizeof(GLUEContext),
    .init           = glue_decode_init,
    .close          = glue_decode_close,
    .decode         = glue_decode_frame,
};

trouble shooting

Invalid pixel format '(null)' Output pad "default" with type video of the filter instance "Parsed_null_0" of null not connected to any destination Error opening filters!

meant encoder's description .pix_fmts needs to end with AV_PIX_FMT_NONE

Assertion *(const AVClass **)avctx->priv_data == codec->priv_class failed at libavcodec/utils.c:1554

meant "your first member of your Context struct needs to be an AVClass *"

if it works but just says ""0 packets muxed"" you need to set the *got_packet return value to "1" or the muxer won't mux what you're giving it

see also