FFmpeg: AVCodec结构体
AVCodec
AVCodec
是FFmpeg比较重要的结构体之一,主要用于存储编解码器的信息。
结构定义及成员解读
typedef struct AVCodec {
/**
* 编解码器实现的名称。
* 该名称是全局唯一的(但编码器和解码器可以共享名称)。
* 这是从用户角度查找编解码器的主要方式。
*/
const char *name;
/**
* 编解码器的描述性名称,比前面的名称更具可读性。
* 您应该使用NULL_IF_CONFIG_SMALL()宏来定义它。
*/
const char *long_name;
enum AVMediaType type;//编解码器类型,视频,音频,或者字幕
enum AVCodecID id;//全局唯一的编解码器ID
/**
* Codec capabilities.
* see AV_CODEC_CAP_*
*/
int capabilities;
const AVRational *supported_framerates; ///支持帧率的数组,用于视频
const enum AVPixelFormat *pix_fmts; ///< 支持的像素格式数组,或者如果未知,则为NULL,数组以-1结尾。用于视频
const int *supported_samplerates; ///< 支持的音频采样率数组,或者如果未知,则为NULL,数组以0结尾。用于音频
const enum AVSampleFormat *sample_fmts; ///<支持的采样数组,或者如果未知,则为NULL,数组以-1结尾。用于音频
const uint64_t *channel_layouts; ///< 支持声道数组,如果未知,则为NULL。 数组以0结尾,用于音频
uint8_t max_lowres; ///< maximum value for lowres supported by the decoder
const AVClass *priv_class; ///< 私有上下文的AVClass
const AVProfile *profiles; ///< 已识别配置文件的数组,或者如果未知,则为NULL,数组以{FF_PROFILE_UNKNOWN}结尾
/*****************************************************************
* 以下所有的字段都不是公共API,不可在libavcodec以外使用。以后新增字段都会放在上面。
*****************************************************************
*/
int priv_data_size;//私有数据大小
struct AVCodec *next;
/**
* @name Frame-level threading support functions
* @{
*/
/**
* 如果已定义,则在创建线程上下文时调用它们。
* 如果编解码器在init()中分配可写表,请在此处重新分配它们。
* priv_data将被设置为原件的副本。
*/
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);
/** @} */
/**
* 私有编解码器默认值。
*/
const AVCodecDefault *defaults;
/**
* 初始化时从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 *);
/**
* Decode/encode API with decoupled packet/frame dataflow. The API is the
* same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except
* that:
* - never called if the codec is closed or the wrong type,
* - AVPacket parameter change side data is applied right before calling
* AVCodec->send_packet,
* - if AV_CODEC_CAP_DELAY is not set, drain packets or frames are never sent,
* - only one drain packet is ever passed down (until the next flush()),
* - a drain AVPacket is always NULL (no need to check for avpkt->size).
*/
int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
int (*send_packet)(AVCodecContext *avctx, const AVPacket *avpkt);
int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
/**
* Flush buffers.
* Will be called when seeking
*/
void (*flush)(AVCodecContext *);
/**
* Internal codec capabilities.
* See FF_CODEC_CAP_* in internal.h
*/
int caps_internal;
} AVCodec;
AVMediaType
前面的AVCodec结构体解析中,已经说明AVMediaType表示的是编解码器类型,一般为视频、音频或者字幕。
定义如下:
enum AVMediaType {
AVMEDIA_TYPE_UNKNOWN = -1, ///< 通常视为AVMEDIA_TYPE_DATA类型
AVMEDIA_TYPE_VIDEO,//视频
AVMEDIA_TYPE_AUDIO,//音频
AVMEDIA_TYPE_DATA, ///< 不透明的数据信息,通常是连续的。
AVMEDIA_TYPE_SUBTITLE,//字幕
AVMEDIA_TYPE_ATTACHMENT, ///< 不透明的数据信息,通常很不连续
AVMEDIA_TYPE_NB//牛逼(NB)类型?无法理解,谁知道的告诉我啊
};
AVCodecID
定义:超长代码,拣极少一部分贴,见名知意
enum AVCodecID {
AV_CODEC_ID_NONE,
/* 视频编解码器 */
AV_CODEC_ID_MPEG1VIDEO,//用于MPEG1
AV_CODEC_ID_MPEG2VIDEO, ///< 用于MPEG-1/2视频解码的首选ID
#if FF_API_XVMC
AV_CODEC_ID_MPEG2VIDEO_XVMC,
#endif /* FF_API_XVMC */
AV_CODEC_ID_H261,
AV_CODEC_ID_H263,
AV_CODEC_ID_MJPEG,
AV_CODEC_ID_MPEG4,
AV_CODEC_ID_WMV1,
AV_CODEC_ID_H264,
AV_CODEC_ID_VP3,
AV_CODEC_ID_VP5,
AV_CODEC_ID_VP6,
AV_CODEC_ID_GIF,
/* PCM 编解码器 */
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
/……
};
AVPixelFormat
enum AVPixelFormat {
AV_PIX_FMT_NONE = -1,
AV_PIX_FMT_YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
AV_PIX_FMT_YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB...
AV_PIX_FMT_BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR...
AV_PIX_FMT_YUV422P, ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
AV_PIX_FMT_YUV444P, ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
AV_PIX_FMT_YUV410P, ///< planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples)
AV_PIX_FMT_YUV411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y
/*
* 略
*/
AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions
};
AVSampleFormat
enum AVSampleFormat {
AV_SAMPLE_FMT_NONE = -1,
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically
};
参考链接 雷大神:https://blog.csdn.net/leixiaohua1020/article/details/14215833
⾳频解码过程
FFmpeg流程
关键函数
关键函数说明:
- avcodec_find_decoder:根据指定的AVCodecID查找注册的解码器。
- av_parser_init:初始化AVCodecParserContext。
- avcodec_alloc_context3:为AVCodecContext分配内存。
- avcodec_open2:打开解码器。
- av_parser_parse2:解析获得⼀个Packet。
- avcodec_send_packet:将AVPacket压缩数据给解码器。
- avcodec_receive_frame:获取到解码后的AVFrame数据。
- av_get_bytes_per_sample: 获取每个sample中的字节数。
avcodec编解码API介绍
avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加⼊的。为了正确
的使⽤它们,有必要阅读FFmpeg的⽂档说明(请点击链接)。
FFmpeg提供了两组函数,分别⽤于编码和解码:
- 解码:avcodec_send_packet()、avcodec_receive_frame()。
- 解码:avcodec_send_frame()、avcodec_receive_packet()。
API的设计与编解码的流程⾮常贴切
建议的使⽤流程如下:
像以前⼀样设置并打开AVCodecContext。
-
输⼊有效的数据:
解码:调⽤avcodec_send_packet()给解码器传⼊包含原始的压缩数据的AVPacket对象。
编码:调⽤ avcodec_send_frame()给编码器传⼊包含解压数据的AVFrame对象。
两种情况下推荐AVPacket和AVFrame都使⽤refcounted(引⽤计数)的模式,否则libavcodec可能不得不对输⼊的数据进⾏拷⻉。
-
在⼀个循环体内去接收codec的输出,即周期性地调⽤avcodec_receive_*()来接收codec输出的数据:
解码:调⽤avcodec_receive_frame(),如果成功会返回⼀个包含未压缩数据的AVFrame。
编码:调⽤avcodec_receive_packet(),如果成功会返回⼀个包含压缩数据的AVPacket。
反复地调⽤
avcodec_receive_packet()
直到返回AVERROR(EAGAIN)
或其他错误。返回 AVERROR(EAGAIN)错误表示codec需要新的输⼊来输出更多的数据。对于每个输⼊的packet或frame,codec⼀般会输出⼀个frame或packet,但是也有可能输出0个或者多于1个 -
流处理结束的时候需要flush(冲刷) codec。因为codec可能在内部缓冲多个frame或 packet,出于性能或其他必要的情况(如考虑B帧的情况)。 处理流程如下:
调⽤avcodec_send_*()传⼊的AVFrame或AVPacket指针设置为NULL。 这将进⼊ draining mode(排⽔模式)。
反复地调⽤avcodec_receive_*()直到返回AVERROR_EOF,该⽅法在draining mode 时不会返回AVERROR(EAGAIN)的错误,除⾮你没有进⼊draining mode。
当重新开启codec时,需要先调⽤ avcodec_flush_buffers()来重置codec。
说明:
- 编码或者解码刚开始的时候,codec可能接收了多个输⼊的frame或packet后还没有输出 数据,直到内部的buffer被填充满。上⾯的使⽤流程可以处理这种情况。
- 理论上,只有在输出数据没有被完全接收的情况调⽤
avcodec_send_*()
的时候才可能会发 ⽣AVERROR(EAGAIN)的错误。你可以依赖这个机制来实现区别于上⾯建议流程的处理⽅ 式,⽐如每次循环都调⽤avcodec_send_*()
,在出现AVERROR(EAGAIN)错误的时候再去调⽤avcodec_receive_*()
。 - 并不是所有的codec都遵循⼀个严格、可预测的数据处理流程,唯⼀可以保证的是调⽤ avcodec_send_()/avcodec_receive_()返回AVERROR(EAGAIN)的时候去 avcodec_receive_()/avcodec_send_()会成功,否则不应该返回AVERROR(EAGAIN) 的错误。**⼀般来说,任何codec都不允许⽆限制地缓存输⼊或者输出。
- 在同⼀个
AVCodecContext
上混合使⽤新旧API是不允许的,这将导致未定义的⾏为。
avcodec_send_packet
函数:int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
作⽤:⽀持将裸流数据包送给解码器
警告:
- 输⼊的avpkt-data缓冲区必须⼤于AV_INPUT_PADDING_SIZE,因为优化的字节流读取 器必须⼀次读取32或者64⽐特的数据
- 不能跟之前的API(例如avcodec_decode_video2)混⽤,否则会返回不可预知的错误
备注:
- 在将包发送给解码器的时候,AVCodecContext必须已经通过
avcodec_open2
打开
参数:
- avctx:解码上下⽂
- avpkt:输⼊AVPakcet.通常情况下,输⼊数据是⼀个单⼀的视频帧或者⼏个完整的⾳频帧。调⽤者保留包的原有属性,解码器不会修改包的内容。解码器可能创建对包的引⽤。 如果包没有引⽤计数将拷⻉⼀份。跟以往的API不⼀样,输⼊的包的数据将被完全地消耗, 如果包含有多个帧,要求多次调⽤avcodec_recvive_frame,直到avcodec_recvive_frame返回
VERROR(EAGAIN)
或AVERROR_EOF
。输⼊参数可以为 NULL,或者AVPacket的data域设置为NULL或者size域设置为0,表示将刷新所有的包,意味着数据流已经结束了。第⼀次发送刷新会总会成功,第⼆次发送刷新包是没有必要的,并且返回AVERROR_EOF
,如果×××缓存了⼀些帧,返回⼀个刷新包,将会返回所有的解码包
返回值:
- 0: 表示成功
- AVERROR(EAGAIN):当前状态不接受输⼊,⽤户必须先使⽤avcodec_receive_frame() 读 取数据帧;
- AVERROR_EOF:解码器已刷新,不能再向其发送新包;
- AVERROR(EINVAL):没有打开解码器,或者这是⼀个编码器,或者要求刷新;
- AVERRO(ENOMEN):⽆法将数据包添加到内部队列。
avcodec_receive_frame
函数:int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
作⽤:从解码器返回已解码的输出数据。
参数:
- avctx: 编解码器上下⽂
- frame: 获取使⽤reference-counted机制的audio或者video帧(取决于解码器类型)。请注意,在执⾏其他操作之前,函数内部将始终先调⽤av_frame_unref(frame)。
返回值:
- 0: 成功,返回⼀个帧
- AVERROR(EAGAIN): 该状态下没有帧输出,需要使⽤avcodec_send_packet发送新的packet到解码器
- AVERROR_EOF: 解码器已经被完全刷新,不再有输出帧
- AVERROR(EINVAL): 编解码器没打开
- 其他<0的值: 具体查看对应的错误码
AAC,MP3解码实战
#include
#include
#include
#include
#include
#include
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
static char err_buf[128] = {0};
static char *av_get_err(int errnum) {
av_strerror(errnum, err_buf, 128);
return err_buf;
}
static void print_sample_format(const AVFrame *frame) {
printf("ar-samplerate: %uHz\n", frame->sample_rate);
printf("ac-channel: %u\n", frame->channels);
printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
FILE *outfile) {
int i, ch;
int ret, data_size;
/* send the packet with the compressed data to the decoder */
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
} else if (ret < 0) {
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
av_get_err(ret), pkt->size);
// exit(1);
return;
}
/* read all the output frames (infile general there may be any number of them */
while (ret >= 0) {
// 对于frame, avcodec_receive_frame内部每次都先调用/函数内部将始终先调⽤av_frame_unref(frame)
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
if (data_size < 0) {
/* This should not occur, checking just for paranoia */
fprintf(stderr, "Failed to calculate data size\n");
exit(1);
}
static int s_print_format = 0;
if (s_print_format == 0) {
s_print_format = 1;
print_sample_format(frame);
}
/**
P表示Planar(平面),其数据格式排列方式为 :
LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
而不带P的数据格式(即交错排列)排列方式为:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
*/
for (i = 0; i < frame->nb_samples; i++) {
for (ch = 0; ch < dec_ctx->channels; ch++) // 交错的方式写入, 大部分float的格式输出
fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
}
}
}
// 播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
int main(int argc, char **argv) {
const char *outfilename;
const char *filename;
const AVCodec *codec;
AVCodecContext *codec_ctx = NULL;
AVCodecParserContext *parser = NULL;
int len = 0;
int ret = 0;
FILE *infile = NULL;
FILE *outfile = NULL;
uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = NULL;
size_t data_size = 0;
AVPacket *pkt = NULL;
AVFrame *decoded_frame = NULL;
if (argc <= 2) {
fprintf(stderr, "Usage: %s
运行输入参数
if (argc <= 2) {
fprintf(stderr, "Usage: %s
解码文件类型,必须是mp3,aac格式
查找解码器 AV_CODEC_ID_MP3|AV_CODEC_ID_AAC
codec = avcodec_find_decoder(audio_codec_id);
根据codec->id
初始化裸流解析器 AVCodecParserContext(数据) + AVCodecParser(方法)
parser = av_parser_init(codec->id);
分配解码器codec上下文
odec_ctx = avcodec_alloc_context3(codec);
打开解码器和解码器上下文进行关联
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
读取原始裸流
data = inbuf;
data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
解析出一个完整的数据包
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
将数据包发送给解码器解码
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
} else if (ret < 0) {
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
av_get_err(ret), pkt->size);
// exit(1);
return;
}
接收解码后的帧数据
while (ret >= 0) {
// 对于frame, avcodec_receive_frame内部每次都先调用/函数内部将始终先调⽤av_frame_unref(frame)
ret = avcodec_receive_frame(dec_ctx, frame);
。。。。。。
}
while 循环读帧 根据
ret == AVERROR(EAGAIN) || ret == AVERROR_EOF
判断
获取单个sample占用的字节
data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
将一个sample的PCM数据写入文件
for (i = 0; i < frame->nb_samples; i++) {
for (ch = 0; ch < dec_ctx->channels; ch++) // 交错的方式写入, 大部分float的格式输出
fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
}
P表示Planar(平面),其数据格式排列方式为 :
LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
而不带P的数据格式(即交错排列)排列方式为:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
ffplay -ar 48000 -ac 2 -f f32le huiguniang.pcm
[f32le @ 0x7fd188822e00] Estimating duration from bitrate, this may be inaccurate
Input #0, f32le, from 'believe.pcm':
Duration: 00:03:42.53, bitrate: 3071 kb/s
Stream #0:0: Audio: pcm_f32le, 48000 Hz, 2 channels, flt, 3072 kb/s
23.82 M-A: 0.000 fd= 0 aq= 369KB vq= 0KB sq= 0B f=0/0