为掌握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。
调用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);
}
由于有诸多的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。
完成注册后,接下来就是使用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的工作参数的设置。