FFmpeg编解码器如何

FFmpeg编解码器如何
这页是FFmpeg中内部编解码器API的一个介绍。它还将展示编解码器怎样和demuxers连接。这不是一个完整的指南但足够明白怎样加一个编解码器到FFmpeg。Cook被用作一个完全例子。

注册编解码器
libavcodec/avcodec.h
第一件事是看AVCodec结构。
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;
在这我们能看到我们有一些元素来命名编解码器、它是什么类型(音频/视频)、被支持的像素格式和一些初始化/编码/解码和关闭的函数指针。现在让我们看它怎样被用。
Libavcodec/cook.c
如果我们看这个文件的末尾,我们看到这个代码:
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,
};
首先我们得到一个被命名cook_decoder的AVCodec结构。然后我们设置cook_decoder的变量。注意我们仅设置需要的变量。当前没有编码器所以我们没有设置。如果我们看id变量我们能看到CODEC_ID_COOK在libavcodec/cook.c中没有被定义。它在avcodec.h中被声明。
libavcodec/avcodec.h
在这我们能找到CodecID枚举。
enum CodecID {
...
CODEC_ID_GSM,
CODEC_ID_QDM2,
CODEC_ID_COOK,
CODEC_ID_TRUESPEECH,
CODEC_ID_TTA,
...
};
CODEC_ID_COOK在列表中。这是FFmpeg种所有被支持的编解码器的列表,清单是固定的和被内部用到每个编解码器的id。改变顺序会破坏二进制兼容性。
这一切都足以声明一个编解码器。现在我们必须为内部使用注册它们。这在运行时被做。
libavcodec/allcodecs.c
在这个文件中我们有avcodec_register_all()函数,它为所有编解码器有象这样的条目。
...
    REGISTER_DECODER(COOK, cook);
...
此宏扩展到为内部使用注册一个编解码器的register_avcodec()调用。注意register_avcodec()将仅当CONFIG_COOK_DECODER被定义时被调用。这允许为一个特殊编解码器不编译解码器代码。但它被定义在哪?这通过此命令行的配置被提取:
sed -n 's/^[^#]*DEC.*, *\(.*\)).*/\1_decoder/p' libavcodec/allcodecs.c
所以在allcodecs.c中增加一个REGISTER_DECODER(NEW, new)条和重新配置足够增加需要的定义。现在我们有连接一个编解码器的一切。
libavcodec/Makefile
在这个文件中我们定义一个编解码器依赖的对象。例如,cook使用fft和mdct代码所以它依赖mdct.o和fft.o对象文件以及cook.o对象文件。
...
OBJS-$(CONFIG_COOK_DECODER)            += cook.o mdct.o fft.o
...
FFmpeg demuxer连接
libavformat/rm.c
如果我们认为ffmpeg将处理一个虚构的rm文件则发生的第一件事是它作为一个rm文件被识别。它被传递到rm demuxer上(rmdec.c)。rm demuxer审查该文件和发现它是一个cook文件。
...
} else if (!strcmp(buf, "cook")) {
st->codec->codec_id = CODEC_ID_COOK;
...
现在ffmpeg了解要初始化什么编解码器和发送来自容器的负载到哪。所以回到cook.c和初始化过程。

编解码器代码
libavcodec/cook.c Init
ffmpeg知道要使用什么编解码器后,它调用在编解码器AVCodec结构中声明的被声明的初始化函数指针。在cook.c中它被叫做cook_decode_init。在这在我们开始解码前我们做我们能做的设置。下面事项应在init中被处理,vlc表初始化、表生成、内存分配和额外数据分析。
libavcodec/cook.c Close
cook_decode_close函数是编解码器清除调用。所有内存、vlc表等应在这被释放。
libavcodec/cook.c Decode
在cook.c中解码调用的名字是cook_decode_frame。
static int cook_decode_frame(AVCodecContext *avctx,
            void *data, int *data_size,
            uint8_t *buf, int buf_size) {
...
本函数有5个参数:
 avctx是一个到AVCodecContext的指针
 data是到输出缓存的指针
 data_size是一个应被设成输出缓存字节尺寸的变量(这实际是解码的采样数*通道数*一个样本的字节尺寸)
 buf是到输入缓存的指针
 buf_size是输入缓存的字节尺寸
解码函数将返回输入缓存消耗的字节数或在一个错误的情况下返回-1。如果解码期间没有错误则返回值通常是buf_size as buf应仅包含一帧数据。比特流分析器分解比特流成被用为编解码器部分的“帧”所以一个到解码函数的调用消耗不少于来自buf的buf_size。现在鼓励独立的码流分析器。
这就是它怎样工作没有太多细节。

Glue编解码器模板
虚拟Glue音频编解码器将充当一个展示比特流读、vlc解码和其它东西的基础。该代码是纯属虚构,有时纯粹是为了例子的缘故被写。不要尝试以防止无效的数据操作。
Glue编解码器如下。
/* 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         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         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,
};

你可能感兴趣的:(FFmpeg编解码器如何)