本文主要用于个人记忆,大量引用了别人的文章内容,不一定正确,如有错误,欢迎指正
本文主要讲述解封装获得记录了流信息AVStream的解封转上下文后,如何从解封转上下文中获取AVPacket数据,并将AVPacket数据解码为AVFrame数据。
其中AVPacket数据是存放解码前的数据的,AVFrame是存放解码后的数据(PCM数据或YUV/RBG数据)的
对于视频来说一般一个AVPacket对应一个AVFrame(也就是一帧),音频一般一个AVPacket对应多个AVFrame
目录
解码用到的结构体
AVStream 存放视频/音频流信息的结构体
AVPacket 存放压缩编码数据相关的结构体
AVFrame 存放解码后数据的结构体
AVCodecContext 编/解码上下文,在解码全程都会用到
AVCodec 存放编/解码器信息的结构体
AVStream包含的重要变量:
int index:标识该视频/音频流
AVCodecContext *codec:指向该视频/音频流的AVCodecContext
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。
int64_t duration:该视频/音频流长度
AVDictionary *metadata:元数据信息
AVRational avg_frame_rate:帧率(不能直接使用需要用分子/分母)
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVPacket包含的重要变量:
uint8_t *data:压缩编码的数据。
int size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流。
AVFrame包含的当前用到的重要变量:
uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)对于是否是packed格式有不同的存放方式
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
int width, height:视频帧宽和高(1920x1080,1280x720...)
int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
int format:解码后原始数据类型(YUV420,YUV422,RGB24...)
int key_frame:是否是关键帧
AVRational sample_aspect_ratio:宽高比(16:9,4:3...)
int64_t pts:显示时间戳
AVCodec包含的当前用到的重要变量:
const char *name:编解码器的名字,比较短
const char *long_name:编解码器的名字,全称,比较长
enum AVMediaType type:指明了类型,是视频,音频,还是字幕
enum AVCodecID id:ID,不重复
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小
AVCodecContext包含的当前用到的重要变量:
enum AVMediaType codec_type:编解码器的类型(视频,音频...)
struct AVCodec *codec:采用的解码器AVCodec(H.264,MPEG2...)
int bit_rate:平均比特率
AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)
int width, height:如果是视频的话,代表宽和高
int sample_rate:采样率(音频)
int channels:声道数(音频)
enum AVSampleFormat sample_fmt:采样格式
解码用到的函数
在解码之前首先需要初始化解码相关的结构体
1、通过解封转上下文找到需要的音频或视频流信息,返回对应的流编号
int av_find_best_stream(AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
const AVCodec **decoder_ret,
int flags);
2、通过流中的解码器id找到当前流最适合的解码器
const AVCodec *avcodec_find_decoder(enum AVCodecID id);
3、初始化解码器上下文并传入解码器
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
4、从流中拷贝参数到解码器上下文
int avcodec_parameters_to_context(AVCodecContext *codec,
const AVCodecParameters *par);
5、打开解码器
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
6、从解封装上下文中读取AVPacket
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
7、解码和解封装最好在不同的线程执行,将AVPacket按照音频包和视频包发送到各自的队列中,等待音频解码线程和视频解码线程进行解码
视频解码:
视频解码需要在另外的线程进行,通过锁和队列取得AVPacket
1、将AVPacket发送给解码器
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
2、释放AVPacket包避免内存泄漏
void av_packet_unref(AVPacket *pkt);
3、初始化AVFrame
AVFrame *av_frame_alloc(void);
4、从解码器中读取帧数据(由于可能不止一帧所以要用whlie直到读到AVERROR(EAGAIN) 或AVERROR_EOF)(注意该函数在调用时会先释放AVFrame中data的数据)
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
音频解码:
音频解码需要在另外的线程进行,通过锁和队列取得AVPacket
1、 将AVPacket发送给解码器
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
2、 释放AVPacket包避免内存泄漏
void av_packet_unref(AVPacket *pkt);
3、初始化AVFrame
AVFrame *av_frame_alloc(void);
4、从解码器中读取帧数据(由于可能不止一帧所以要用whlie直到读到AVERROR(EAGAIN) 或AVERROR_EOF)
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
解码完成后需要释放解码所用到的变量:
1、释放解码上下文:
void avcodec_free_context(AVCodecContext **avctx);
2、释放帧数据
void av_frame_free(AVFrame **frame);
参考文章
FFMPEG结构体分析:AVStream_雷霄骅的博客-优快云博客 FFMPEG结构体分析:AVStream
FFMPEG结构体分析:AVCodec_雷霄骅的博客-优快云博客_ffmpeg雷霄骅 FFMPEG结构体分析:AVCodec
FFMPEG结构体分析:AVFrame_雷霄骅的博客-优快云博客_avcodec是什么意思 FFMPEG结构体分析:AVFrame
FFMPEG结构体分析:AVCodecContext_雷霄骅的博客-优快云博客_avcodeccontext FFMPEG结构体分析:AVCodecContext
FFMPEG结构体分析:AVPacket_雷霄骅的博客-优快云博客 FFMPEG结构体分析:AVPacket