音视频开发(五十一)调试分析FFmpeg (解封装部分的)常用结构体

目录

  1. ffplay的断点调试

  2. (解封装部分)常用结构体以及之间的关系分析

  3. 断点分析ffplay解码流程及关键结构体

  4. (解码部分)常用结构体以及之间的关系分析

一、ffplay的断点调试

首先下载和编译ffmpeg,具体可以参考音视频开发之旅(33) -交叉编译android使用的FFmpeg(3.x和4.x)
区别在于,我们这次不是交叉编译,而是在Mac上编译安装调试。

./configure --enable-static --disable-shared --enable-debug --disable-doc --disable-x86asm --enable-nonfree  --enable-libvpx --enable-gpl  --enable-opengl --enable-libx264  --enable-libx265 --enable-libvmaf
make -j8
sudo make install

编译成功之后我们会看到几个重要的可执行文件ffmpeg_g、ffprobe_g以及ffplay_g,而接下来的运行和调试就会用到他们。
如何在Xcode下配置调试ffmpeg源码请参考:https://www.jianshu.com/p/27a90b113413

我们在ffplay.c的main函数打断点进行进行分析ffplay解封装(read_thread)流程中用的的结构体。

打开媒体流

VideoState *stream_open(const char *filename,const AVInputFormat *iformat)

涉及到结构体:AVInputFormat

启动readthread开始读取

    is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);

分配AVFormatContext内存

 AVFormatContext   ic = avformat_alloc_context();

打开流媒体文件

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        const AVInputFormat *fmt, AVDictionary **options)

涉及到结构体:AVFormatContext、AVInputFormat、AVDictionary

获取流信息

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)

涉及到结构体:AVStream AVCodecParameters AVRational

循环读取frame数据

 for (;;) {
     ...
     int av_read_frame(AVFormatContext *s, AVPacket *pkt)
     ...
}

涉及到结构体:AVFormatContext、AVPacket等

解封装的流程先到这里,可见如果想学习ffplay的源码,首先要搞清楚主要流程以及过程中涉及的关键结构体。
下一节我们来具体分析这些结构体。

三、(解封装部分)常用结构体以及之间的关系分析常用结构体以及之间的关系分析

3.1 常用结构体以及之间的关系

FFMPEG中结构体很多。最关键的结构体可以分成以下几类:
a)        解协议(http,rtsp,rtmp,mms)

AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”)

b)        解封装(flv,avi,rmvb,mp4)

AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。

c)        解码(h264,mpeg2,aac,mp3)

每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。

d) 存数据

视频的话,每个结构一般是存一帧;音频可能有好几帧

解码前数据:AVPacket

解码后数据:AVFrame

他们之间的关系如下:

3.2。AVFormatContext
该结构体定义在libavformat/Avformat.h中,它是一个贯穿始终的数据结构,很多函数都要用到它作为参数。几个主要变量的作用如下:

struct AVInputFormat *iformat:输入数据的封装格式
struct AVOutputFormat *oformat:输出数据的封装格式

AVIOContext *pb:输入数据的缓存

unsigned int nb_streams:视音频流的个数

AVStream **streams:视音频流

char filename[1024]:文件名

int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)

int bit_rate:比特率(单位bps,转换为kbps需要除以1000)

AVDictionary *metadata:元数据

 3.3 AVInputFormat
该结构体定义也在libavformat/Avformat.h中,是解封装器对象主要的变量的作用如下

const char *name: 格式的名称
const char *mime_type:mime类型如 video/avc video/hevc audio/aac等

以及一系列函数指针
int (*read_probe)(const AVProbeData *);
int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
int (*read_close)(struct AVFormatContext *);
int (*read_seek)(struct AVFormatContext *,
                     int stream_index, int64_t timestamp, int flags);
int (*read_play)(struct AVFormatContext *);

int (*read_pause)(struct AVFormatContext *);
int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);

3.4 AVStream
每个AVStream存储一个视频/音频流的相关数据;是解封装器分离出来的流对象,即解封装的产物,它保存在AVFormatcontext中。

该结构体定义也在libavformat/Avformat.h中, 主要变量如下:

int index;  流索引
int id; 流id
void *priv_data; 流数据
AVRational time_base; 时间基,通过该值可以把PTS,DTS转化为真正的时间;PTS*time_base=真正的时间
int64_t duration:流长度
AVRational sample_aspect_ratio;采样率
AVRational avg_frame_rate:帧率
AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)

AVStream是解封装环节的输出,同时也是解码环节的输入,每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
解码部分的数据结构分析我们下一篇再来分析学习。

3.5 AVPacket
存储压缩编码数据相关信息的结构体,保存了解封装之后,解码之前的数据以及PTS、DTS、Duration以及streamId等信息
该结构体定义位于libavcodec/Packet.h中,主要变量如下:

  uint8_t *data; 对于H.264来说。1个AVPacket的data通常对应一个NAL。
int   size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
AVPacketSideData *side_data;附加信息

私信我,领取2022最新最全学习提升资料,内容包括(C/C++LinuxFFmpeg webRTC rtmp hls rtsp ffplay srs

 

四、断点分析ffplay解码流程及关键结构体

还是从read_thread进行分析

        stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
        ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);

stream_component_open 打开指定的流

static int stream_component_open(VideoState *is, int stream_index)

AVFormatContext *ic = is->ic;
AVCodecContext *avctx;
const AVCodec *codec;

其中的关键函数如下 
avctx = avcodec_alloc_context3(NULL);
int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)
codec = avcodec_find_decoder(avctx->codec_id);
codec = avcodec_find_decoder_by_name(forced_codec_name);
//打开codec
avcodec_open2(avctx, codec, &opts)

//音频解码
decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread)
decoder_start(&is->auddec, audio_thread, "audio_decoder", is)

//视频解码
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread)
decoder_start(&is->viddec, video_thread, "video_decoder", is)

avcodec_free_context(&avctx);

涉及到结构体:AVFormatContext、AVCodecContext、AVCodec

avcodec_parameters_to_context

int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)

涉及到结构体:AVCodecContext、AVCodecParameters

avcodec_find_decoder通过codecid查找解码器

const AVCodec *avcodec_find_decoder(enum AVCodecID id)

涉及到结构体:AVCodec

avcodec_open2

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)

涉及到结构体:AVCodecContext、AVCodec

解码线程

static int audio_thread(void *arg)
static int video_thread(void *arg)

解码线程中以AVFrame作为解压缩后的结构体

涉及到结构体:AVFrame

然后分别开启音频和视频的解码线程开始解码。我们可以看到涉及的主要结构体有AVCodecContext 、AVCodecParameters 、AVCodec 、AVFrame

作为解码的输入AVPacket(存储压缩编码数据相关信息的结构体)以及AVStream(每个AVStream存储一个视频/音频流的相关数据;是解封装器分离出来的流对象)我们上一篇已经介绍过,下面我们来主要分析解码相关的几个结构体AVCodecContext 、AVCodec 、AVFrame

五、(解码部分)常用结构体以及之间的关系分析

2.1 常用结构体以及之间的关系(再引用一次,雷神总结梳理的太好了)

FFMPEG中结构体很多。最关键的结构体可以分成以下几类:

a)        解协议(http,rtsp,rtmp,mms)

AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”)

b)        解封装(flv,avi,rmvb,mp4)

AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。

c)        解码(h264,mpeg2,aac,mp3)

每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。

d) 存数据

视频的话,每个结构一般是存一帧;音频可能有好几帧

解码前数据:AVPacket

解码后数据:AVFrame

他们之间的关系如下: 

FFMPEG中最关键的结构体之间的关系

2.2 AVCodecContext
AVCodecContext是一个描述解码器上下文的数据结构,包含了很多编码器需要的参数信息。
该结构体定义位于libavcodec/AVcodec.h中,主要变量如下:

enum AVMediaType codec_type; // 该枚举定义在libavutil/Avutil.h中,编码的类型,音频、视频、字母等 
const struct AVCodec  *codec;//采用的解码器AVCodec,下面单独分析
enum AVCodecID     codec_id; // 该枚举定义在libavcodec/Codec_id.h中,定了一了所有的编解码器id 
void *priv_data;
struct AVCodecInternal *internal;//内部使用的上下文环境
void *opaque;
int64_t bit_rate;//平均码率
uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

AVRational time_base;//时间基,根据该参数,可以把PTS转化为实际的时间(单位为秒s)
int width, height;//仅视频类型用
int gop_size;//关键帧间隔
int max_b_frames;//最大b帧数量
int has_b_frames;//是否有b帧,关系到视频的压缩比率,一般b帧越多压缩比越大
int slice_count;//片的总和,关于slice相关,可以[音视频开发之旅(56) -H264/AVC基本结构](https://mp.weixin.qq.com/s?__biz=MzU5NjkxMjE5Mg==&mid=2247484355&idx=1&sn=538378561c16b640a4ea42bc1f354044&chksm=fe5a32ecc92dbbfa1d6a2e83f22aece727badb99966b6e621322ed8bf6b0cd8f0b2d1c262013&token=778944351&lang=zh_CN#rd)
AVRational sample_aspect_ratio;//采样率


  /* audio only */
int sample_rate; ///< samples per second 音频采样率
int channels;    ///< number of audio channels 通道数
enum AVSampleFormat sample_fmt;  ///< sample format 采样格式,定义在libavutil/Samplefmt.h中

enum AVColorSpace colorspace;//颜色空间,定义在libavutil/Pixfmt.h 
AVRational framerate;//帧率
enum AVPixelFormat sw_pix_fmt;//像素格式,如yuv420pdeng ,定义在libavutil/Pixfmt.h 如果设置不对导致解码器无法正常解码会出现花屏的情况。

 这个结构体涉及的变量很多,并且很多是和编码相关,解码用的的其实并不算太多。

2.3 AVCodec
AVCodec是存储编码器信息的结构体。
该结构体定义位于libavcodec/Codec.h中,主要变量如下:

const char *name;//codec的名称
enum AVMediaType type;//codec的类型,AVCodecContext中已经介绍
enum AVCodecID id;//codec的id,AVCodecContext中已经介绍
int capabilities;//编解码的能力,见 AV_CODEC_CAP_
const AVRational *supported_framerates;//支持的帧率
const enum AVPixelFormat *pix_fmts;//支持的像素格式
const int *supported_samplerates; //支持的音频采样率
const enum AVSampleFormat *sample_fmts;//支持的采样格式

下面是一些函数指针
int (*init)(struct AVCodecContext *);//初始化
int (*encode2)(struct AVCodecContext *avctx, struct AVPacket *avpkt,const struct AVFrame *frame, int *got_packet_ptr);//编码
int (*decode)(struct AVCodecContext *avctx, void *outdata,
                  int *got_frame_ptr, struct AVPacket *avpkt);
int (*close)(struct AVCodecContext *);//解码
int (*receive_packet)(struct AVCodecContext *avctx, struct AVPacket *avpkt);//接收packet数据
int (*receive_frame)(struct AVCodecContext *avctx, struct AVFrame *frame);//接收frame数据
void (*flush)(struct AVCodecContext *);//刷新缓冲区

2.4 AVFrame
AVFrame一般用于存储原始数据(即非压缩数据,例如对于视频而言YUV、RGB,对于音频而言 PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。
该结构体定义位于libavutil/Frame.h中,主要变量如下:

#define AV_NUM_DATA_POINTERS 8
uint8_t *data[AV_NUM_DATA_POINTERS];//对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]...(YUV420P中data[0]存Y,data[1]存U,data[2]存V)
int width, height;//视频宽高
int nb_samples;//每个信道音频采样点的个数
int format;//帧的像素格式
int key_frame;//1 -> keyframe, 0-> not
enum AVPictureType pict_type;//定义在libavutil/AVutil.h中,该帧的类型,I、P、B等
AVRational sample_aspect_ratio;//宽高比(16:9,4:3...)FFMPEG中用AVRational表达分数:
int64_t pts;//显示时间戳
int64_t pkt_dts;//从packet复制的PTS
int quality;
void *opaque;
int coded_picture_number;//编码帧序号
int display_picture_number;//显示帧序号
int8_t *qscale_table;//QP表 QP表指向一块内存,里面存储的是每个宏块的QP值。宏块的标号是从左往右,一行一行的来的。每个宏块对应1个QP。
uint8_t *mbskip_table;//跳过宏块表
int16_t (*motion_val[2])[2];//运动矢量表
int8_t *ref_index[2];//运动估计参考帧列表
int interlaced_frame;//交错帧,表示图像内容是交错的,即是否是隔行扫描
int sample_rate;//音频采样率
uint8_t motion_subsample_log2;//一个宏块中的运动矢量采样个数,取log2的.1个运动矢量所能代表的画面大小(用宽或者高表示,单位是像素),注意,这里取了log2。

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/124250526