ffmpeg工作流程分析-3

为掌握ffmpeg的工作流程,现以libavcodec下的ffmpeg/doc/examples/decoding_encoding.c中实现视频编码为例展示其工作过程。

libavcodec库是实现音频或视频的编码或解码,所有编解码CODEC的调用均有统一的格式,ffmpeg以公共的函数指针注册、调用和销毁
CODEC。

video_encode_example例程实现给定视频数据的编码,将编码后的码流写文件。所有avcodec的调用均使用统一的过程,首先是初始化avcodec_init(),所有CODEC库在使用前均需要调用该函数;然后是注册所有的CODEC,包括注册编码器REGISTER_ENCODER、解码器REGISTER_DECODER、编解码器REGISTER_ENCDEC等;最后使用CODEC,如video_encode_example。

1 初始化CODEC

调用ffmpeg的函数avcodec_init实现CODEC的初始化。

static av_cold void avcodec_init(void)
{
    static int initialized = 0; //静态变量标注初始化,只调用一次

    if (initialized != 0)
        return;
    initialized = 1;

    if (CONFIG_ME_CMP)
        ff_me_cmp_init_static();
}

为确保该初始化仅被调用一次,因此使用局部静态变量,记录以前的动作。初始化的任务是将“数字信号处理应用”中的静态表格初始化。其实就是一个数组,初始化其为0。
初始化的函数会在这里被调用:

av_cold void avcodec_register(AVCodec *codec)
{
    AVCodec **p;
    avcodec_init();
    p = last_avcodec;
    codec->next = NULL;

    while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, codec))
        p = &(*p)->next;
    last_avcodec = &codec->next;

    if (codec->init_static_data)
        codec->init_static_data(codec);
}

2 注册CODEC

由于有诸多的CODEC,所以ffmpeg以公共的函数指针的方式,以统一的接口操作CODEC。首先AVCodec结构体定义了函数指针,某一个CODEC实例填充AVCodec结构的字段,然后用REGISTER_ENCODER注册该CODEC。


struct AVSubtitle;

/**
 * AVCodec.
 */
typedef struct AVCodec {
    /**
     * Name of the codec implementation.
     * The name is globally unique among encoders and among decoders (but an
     * encoder and a decoder can share the same name).
     * This is the primary way to find a codec from the user perspective.
     */
    const char *name;
    /**
     * Descriptive name for the codec, meant to be more human readable than name.
     * You should use the NULL_IF_CONFIG_SMALL() macro to define it.
     */
    const char *long_name;
    enum AVMediaType type;
    enum AVCodecID id;
    /**
     * Codec capabilities.
     * see AV_CODEC_CAP_*
     */
    int capabilities;
    const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
    const enum AVPixelFormat *pix_fmts;     ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
    const int *supported_samplerates;       ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
    const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
    const uint64_t *channel_layouts;         ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
    uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoder, no direct access, use av_codec_get_max_lowres()
    const AVClass *priv_class;              ///< AVClass for the private context
    const AVProfile *profiles;              ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN}

    /*****************************************************************
     * No fields below this line are part of the public API. They
     * may not be used outside of libavcodec and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */
    int priv_data_size;
    struct AVCodec *next;
    /**
     * @name Frame-level threading support functions
     * @{
     */
    /**
     * If defined, called on thread contexts when they are created.
     * If the codec allocates writable tables in init(), re-allocate them here.
     * priv_data will be set to a copy of the original.
     */
    int (*init_thread_copy)(AVCodecContext *);
    /**
     * Copy necessary context variables from a previous thread context to the current one.
     * If not defined, the next thread will start automatically; otherwise, the codec
     * must call ff_thread_finish_setup().
     *
     * dst and src will (rarely) point to the same context, in which case memcpy should be skipped.
     */
    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
    /** @} */

    /**
     * Private codec-specific defaults.
     */
    const AVCodecDefault *defaults;

    /**
     * Initialize codec static data, called from avcodec_register().
     */
    void (*init_static_data)(struct AVCodec *codec);

    int (*init)(AVCodecContext *);
    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
                      const struct AVSubtitle *sub);
    /**
     * Encode data to an AVPacket.
     *
     * @param      avctx          codec context
     * @param      avpkt          output AVPacket (may contain a user-provided buffer)
     * @param[in]  frame          AVFrame containing the raw data to be encoded
     * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a
     *                            non-empty packet was returned in avpkt.
     * @return 0 on success, negative error code on failure
     */
    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
                   int *got_packet_ptr);
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
    int (*close)(AVCodecContext *);
    /**
     * Flush buffers.
     * Will be called when seeking
     */
    void (*flush)(AVCodecContext *);
    /**
     * Internal codec capabilities.
     * See FF_CODEC_CAP_* in internal.h
     */
    int caps_internal;
} AVCodec;

上述的AVCodec结构体定义了关于CODEC的一些标识信息、操作的函数指针,该结构是所有CODEC均具有的属性。CODEC的操作通过init、encode、close或decode函数指针实现某个CODEC的处理动作。下面就是一个CODEC结构体的初始化。

AVCodec ff_mpeg1video_encoder = {
    .name                 = "mpeg1video",
    .long_name            = NULL_IF_CONFIG_SMALL("MPEG-1 video"),
    .type                 = AVMEDIA_TYPE_VIDEO,
    .id                   = AV_CODEC_ID_MPEG1VIDEO,
    .priv_data_size       = sizeof(MpegEncContext),
    .init                 = encode_init,
    .encode2              = ff_mpv_encode_picture,
    .close                = ff_mpv_encode_end,
    .supported_framerates = ff_mpeg12_frame_rate_tab + 1,
    .pix_fmts             = (const enum AVPixelFormat[]) { AV_PIX_FMT_YUV420P,
                                                           AV_PIX_FMT_NONE },
    .capabilities         = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS,
    .priv_class           = &mpeg1_class,
};

上述初始化中的三个函数encode_init、MPV_encode_picture、MPV_encode_end实现具体的数据处理,即初始化CODEC,编码、关闭CODEC。定义CODEC之后,就需要注册该CODEC。即调用函数avcodec_register_all实现注册。

void avcodec_register_all(void)
{
    static int initialized;

    if (initialized)
        return;
    initialized = 1;

    /* hardware accelerators */
    REGISTER_HWACCEL(H263_VAAPI,        h263_vaapi);
    REGISTER_HWACCEL(H263_VIDEOTOOLBOX, h263_videotoolbox);
    REGISTER_HWACCEL(H264_D3D11VA,      h264_d3d11va);
    REGISTER_HWACCEL(H264_DXVA2,        h264_dxva2);
    REGISTER_HWACCEL(H264_MMAL,         h264_mmal);
    REGISTER_HWACCEL(H264_QSV,          h264_qsv);
    REGISTER_HWACCEL(H264_VAAPI,        h264_vaapi);
    REGISTER_HWACCEL(H264_VDA,          h264_vda);
    REGISTER_HWACCEL(H264_VDA_OLD,      h264_vda_old);
    REGISTER_HWACCEL(H264_VDPAU,        h264_vdpau);
    REGISTER_HWACCEL(H264_VIDEOTOOLBOX, h264_videotoolbox);
    REGISTER_HWACCEL(HEVC_D3D11VA,      hevc_d3d11va);
    REGISTER_HWACCEL(HEVC_DXVA2,        hevc_dxva2);
    REGISTER_HWACCEL(HEVC_QSV,          hevc_qsv);
    REGISTER_HWACCEL(HEVC_VAAPI,        hevc_vaapi);
    REGISTER_HWACCEL(HEVC_VDPAU,        hevc_vdpau);
    REGISTER_HWACCEL(MPEG1_XVMC,        mpeg1_xvmc);
    REGISTER_HWACCEL(MPEG1_VDPAU,       mpeg1_vdpau);
    REGISTER_HWACCEL(MPEG1_VIDEOTOOLBOX, mpeg1_videotoolbox);
    REGISTER_HWACCEL(MPEG2_XVMC,        mpeg2_xvmc);
    ...

上述的宏REGISTER_ENCDEC进一步使用子宏REGISTER_ENCODER、
REGISTER_DECODER,并实际调用avcodec_register()函数实现CODEC的注册。

av_cold void avcodec_register(AVCodec *codec)
{
    AVCodec **p;
    avcodec_init();
    p = last_avcodec;
    codec->next = NULL;

    while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, codec))
        p = &(*p)->next;
    last_avcodec = &codec->next;

    if (codec->init_static_data)
        codec->init_static_data(codec);
}

上述过程实现所有的CODEC通过next指针而连接起来,形成一个链表每个节点为一个CODEC。

3 使用CODEC

完成注册后,接下来就是使用CODEC了。下面以实现YUV420格式的Mpeg-1视频编码video_encode_example为例,说明使用CODEC的过程。

/*
 * Video encoding example
 */
static void video_encode_example(const char *filename, int codec_id) //参数为待输出的码流文件和codec的id
{
    AVCodec *codec; //编码器
    AVCodecContext *c= NULL; //编码器上下文
    int i, ret, x, y, got_output;
    FILE *f;
    AVFrame *frame;
    AVPacket pkt;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };

    printf("Encode video file %s\n", filename);

    /* find the mpeg1 video encoder */
    codec = avcodec_find_encoder(codec_id);// 查找mpeg1视频编码器
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec); // 分配codec结构
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    /* put sample parameters */
    c->bit_rate = 400000;
    /* resolution must be a multiple of two */
    c->width = 352;
    c->height = 288;
    /* frames per second */
    c->time_base = (AVRational){1,25};
    /* emit one intra frame every ten frames
     * check frame pict_type before passing frame
     * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
     * then gop_size is ignored and the output of encoder
     * will always be I frame irrespective to gop_size
     */
    c->gop_size = 10;
    c->max_b_frames = 1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;

    if (codec_id == AV_CODEC_ID_H264)
        av_opt_set(c->priv_data, "preset", "slow", 0);

    /* open it */ //打开codec
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;

    /* the image can be allocated by any means and av_image_alloc() is
     * just the most convenient way if av_malloc() is to be used */
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height,
                         c->pix_fmt, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate raw picture buffer\n");
        exit(1);
    }

    /* encode 1 second of video */
    for (i = 0; i < 25; i++) {
        av_init_packet(&pkt);
        pkt.data = NULL;    // packet data will be allocated by the encoder
        pkt.size = 0;

        fflush(stdout);
        /* prepare a dummy image */
        /* Y */
        for (y = 0; y < c->height; y++) {
            for (x = 0; x < c->width; x++) {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        /* Cb and Cr */
        for (y = 0; y < c->height/2; y++) {
            for (x = 0; x < c->width/2; x++) {
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i;

        /* encode the image */
        ret = avcodec_encode_video2(c, &pkt, frame, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_packet_unref(&pkt);
        }
    }

    /* get the delayed frames */ // 获得延迟的帧,如B帧编码方式
    for (got_output = 1; got_output; i++) {
        fflush(stdout);

        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_packet_unref(&pkt);
        }
    }

    /* add sequence end code to have a real mpeg file */
    fwrite(endcode, 1, sizeof(endcode), f); //写入结束符
    fclose(f); // 关闭码流文件

    avcodec_close(c); // 关闭解码器
    av_free(c); // 释放解码器结构
    av_freep(&frame->data[0]); //?
    av_frame_free(&frame); //释放帧结构体
    printf("\n");
}

上述代码展示了使用CODEC的过程,首先用函数avcodec_find_encoder()查找编码器;然后用函数avcodec_alloc_context()申请CODEC,函数avcodec_alloc_frame()申请编码器中的图像帧空间;设置编码器参数,包括宽度、高度等;avcodec_open()打开编码器CODEC;获取图像数据;编码当前图像avcodec_encode_video();写入码流文件;编码完毕后,销毁各种资源,关闭编码器avcodec_close()等。其他CODEC的使用方法基本与上述的MPEG-1视频编码器的工作过程相同,区别主要在于CODEC的工作参数的设置。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FFmpeg 是一套功用十分强大的开源媒体框架,被广泛应用于媒体处理和转码等方面。该框架由 C 语言编写实现,其核心功能主要包括媒体解码、编码、过滤、封装等。下面我们主要从源码层面展开分析FFmpeg 作为开源软件,源码具有极高的可读性和可扩展性,但是难度也很大,不适合初学者。如果我们要逐层分析 FFmpeg 常用流程,我们可以先从 FFmpeg 的主要模块开始,例如 AVFormatContext、AVCodecContext、AVFrame 等数据结构。 在一个典型的流程中,FFmpeg 首先通过 AVFormatContext 处理输入文件,然后通过 AVCodecContext 解码处理后输出到 AVFrame,最后通过 AVFormatContext 实现封装输出成文件。 此外,FFmpeg 还可以通过多种输入视频流格式(例如 RTSP,HTTP,FLV 等)对视频进行采集和处理,并支持多种输出格式(例如 MP4,FLV,AVI 等)。同时,FFmpeg 还能够实现多幅图像的合并、重采样和音视频混合等功能。要实现这些功能,我们需要从源代码层面着手。 FFmpeg 的源代码分别包括 libavformat、libavcodec、libavutil 等库,实现不同的功能。其中,libavformat 库主要提供了媒体文件的输入输出、封装和解封装等功能,libavcodec 库主要提供了音视频编码解码的功能,libavutil 库则提供了一些公共的工具函数和数据类型定义。通过逐层深入分析,我们可以深入地了解 FFmpeg 的实现原理,以及如何使用 FFmpeg 库来完成多种媒体处理和转码任务。 总的来说,FFmpeg 是一款优秀的媒体处理和转码库,它的源码具有极高的可扩展性和自定义性,同时难度也较大。如果要深入了解和使用 FFmpeg,需要有扎实的编程背景和相关经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值