文章目录

  • 一、音频流参数解析
  • 1、音频流类型判定
  • 2、获取音频流索引编号
  • 3、获取音频流采样率
  • 4、获取音频流的采样格式
  • 5、获取音频流的通道数
  • 6、获取音频流的压缩编码 id
  • 7、获取音频流的播放时长
  • 8、计算音频流的播放时长
  • 二、视频流参数解析
  • 1、视频流类型判定
  • 2、获取视频流索引编号
  • 3、获取视频流帧率
  • 4、获取视频流压缩编码格式
  • 5、获取视频流压缩编码格式
  • 6、获取视频流播放时长
  • 三、数据包解析
  • 1、遍历数据包
  • 2、获取数据包解码时间戳 - DTS
  • 3、获取数据包显示时间戳 - PTS
  • 4、获取数据包大小
  • 5、获取数据包字节位置
  • 四、完整代码示例







一、音频流参数解析




1、音频流类型判定



AVFormatContext 结构体 的 AVStream **streams 成员 就是 视频文件中的 所有的媒体流 , 在遍历 媒体流时 , 通过 AVStream 结构体中的 codecpar->codec_type 成员的成员参数 确定媒体流的类型 ,

如果 媒体流类型是 AVMEDIA_TYPE_AUDIO , 说明这是 音频流 ;



音频流判定代码如下 :

for (uint32_t i = 0; i < fmt_ctx->nb_streams; i++)
    {
        // 获取第 i 个媒体流 , 该媒体流可以是 音频流、视频流、字幕流
        AVStream *in_stream = fmt_ctx->streams[i];

        if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type) // 解析音频流参数
        {
        }   
    }



2、获取音频流索引编号



AVStream 结构体的 int index 成员是 媒体流 的索引编号 , 这是一个通用的字段 , 所有类型的媒体流都有该属性 ;

// 音频流 索引 编号
printf("index : %d\n", in_stream->index);



3、获取音频流采样率



要从 AVStream 中获取音频流的采样格式,可以通过关联的 AVCodecParameters 获取相关信息 ;

音频流 的 采样率 封装在 编解码器参数 AVCodecParameters *codecpar 结构体成员 中 , 是 int sample_rate 参数 , 如 : 44100Hz , 48000Hz ;

// 音频流 采样率 一般是 44100Hz 48000Hz
printf("sample_rate : %dHz\n", in_stream->codecpar->sample_rate);



4、获取音频流的采样格式



要从 AVStream 中获取音频流的采样格式 , 可以通过关联的 AVCodecParameters 获取相关信息 ;

获取的是 AVStream 结构体的 AVCodecParameters *codecpar 结构体成员的 int sample_rate 成员 ;

// 音频流 采样格式 , 也就是采样位数 , 如 : 
printf("format : %d \n", in_stream->codecpar->format);



采样格式 定义在 libavutil/samplefmt.h 头文件中 的 AVSampleFormat 枚举类型中 , 如下代码所示 :

// 定义音频采样格式的枚举类型
enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,   // 未指定的采样格式
    AV_SAMPLE_FMT_U8 = 0,      // 无符号8位整数
    AV_SAMPLE_FMT_S16,         // 有符号16位整数 (1)
    AV_SAMPLE_FMT_S32,         // 有符号32位整数 (2)
    AV_SAMPLE_FMT_FLT,         // 浮点型 (3)
    AV_SAMPLE_FMT_DBL,         // 双精度浮点型 (4)

    AV_SAMPLE_FMT_U8P,         // 无符号8位整数,平面格式 (5)
    AV_SAMPLE_FMT_S16P,        // 有符号16位整数,平面格式 (6)
    AV_SAMPLE_FMT_S32P,        // 有符号32位整数,平面格式 (7)
    AV_SAMPLE_FMT_FLTP,        // 浮点型,平面格式 (8)
    AV_SAMPLE_FMT_DBLP,        // 双精度浮点型,平面格式 (9)
    AV_SAMPLE_FMT_S64,         // 有符号64位整数 (10)
    AV_SAMPLE_FMT_S64P,        // 有符号64位整数,平面格式 (11)

    AV_SAMPLE_FMT_NB           // 采样格式的数量。动态链接时不要使用 (12)
};

上述 AVSampleFormat 枚举中 , 第一个枚举值是 -1 , 之后的值依次递增 , 因此枚举对应的实际值为 :

AV_SAMPLE_FMT_NONE = -1
AV_SAMPLE_FMT_U8 = 0
AV_SAMPLE_FMT_S16 = 1
AV_SAMPLE_FMT_S32 = 2
AV_SAMPLE_FMT_FLT = 3
AV_SAMPLE_FMT_DBL = 4
AV_SAMPLE_FMT_U8P = 5
AV_SAMPLE_FMT_S16P = 6
AV_SAMPLE_FMT_S32P = 7
AV_SAMPLE_FMT_FLTP = 8
AV_SAMPLE_FMT_DBLP = 9
AV_SAMPLE_FMT_S64 = 10
AV_SAMPLE_FMT_S64P = 11
AV_SAMPLE_FMT_NB = 12



获取采样格式的代码运行后 , 得到的结果是 :

format : 8

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音视频



命令行 打印出的 采样格式 值为 8 , 对应上述 enum AVSampleFormat 枚举 中的值就是 AV_SAMPLE_FMT_FLTP

  • 采样位数 : 是 float 单精度浮点数 , 每个采样点使用 32 位 占 4 字节 ;
  • 存储布局 : 该采样的 存储布局 是 平面格式 , 也就是说 每个声道的数据存储在单独的缓冲区中 ;

与 " 平面格式 “ 存储布局相对的是 ” 交错格式 " 存储布局 , 交错格式 是 将所有声道的数据交错存储在一个缓冲区中 ;



5、获取音频流的通道数



要从 AVStream 结构体 中获取音频流的通道数 , 可以通过 与之关联的 AVCodecParameters 的 channels 字段获取 ;

每个 AVStream 都包含一个 AVCodecParameters *codecpar 结构体成员

av_stream->codecpar->channels 就可以 获取 音频流的通道数

  • 单声道为 1
  • 立体声为 2
  • 5.1 声道为 6


代码示例如下 :

// 音频流 声道数 , 单声道有 1 个 , 立体声有 2 个                                
printf("channel num : %d \n", in_stream->codecpar->channels);

执行后 , 得到 如下结果 , 有 2 个声道 , 是 双声道 立体声 ;

channel num : 2

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音视频_02



6、获取音频流的压缩编码 id



通过 AVStream 媒体流 结构体的 AVCodecParameters *codecpar 成员 的 enum AVCodecID codec_id 字段 , 可以获取 音频流的压缩编码器 ID ;

音视频编解码器 ID 都定义在 enum AVCodecID 枚举 中 , 该枚举 定义在 libavcodec/avcodec.h 头文件中 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_ffmpeg_03

使用如下代码 , 可打印出 编解码器 ID ;

// 音频流 压缩编码 ID                                            
printf("codec_id : %d \n", in_stream->codecpar->codec_id);

最终打印出的结果 : 86018 ;

codec_id : 86018

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_解封装_04

十进制 86018 对应的 十六进制数为 0x15002 ,

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_ffmpeg_05

到 libavcodec/avcodec.h 头文件 中查找 enum AVCodecID 枚举 , 可以看到 音频编解码器 从 0x15000 开始 , 第三个就是 AV_CODEC_ID_AAC ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音频流_06

在 MediaInfo 中打开 该 test.flv 文件 , 可以看到音频流的 编码格式是 AAC 格式 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音视频_07



7、获取音频流的播放时长



通过 AVStream 媒体流 结构体的 int64_t duration 成员 , 可以获取 音频流的播放时长 ;

这个 播放时长 的 时间单位 不是固定的 , 还需要 结合 AVStream 中的 时间基 AVRational time_base 成员进行计算 ;

AVStream->duration 播放时长 是以 time_base 为单位表示的 ;

如果 AVStream->duration 的值为 AV_NOPTS_VALUE , 说明没有得到有效的播放时长 , FLV 格式 或者 网络流 会得到该 无效时间 ;

#define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))

十六进制的 0x8000000000000000 值 , 对应 十进制值为 -9223372036854775808 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_FFmpeg_08



打印 音频流 时长 代码示例 : 下面的代码中打印出的时长是 -9223372036854775808 ( 十进制 ) , 对应的十六进制数为 0x8000000000000000 值 , 因此没有得到有效的 音频播放时长 ;

// 音频流 播放时长
            printf("audio duration : %lld\n", in_stream->duration);

            // 音频流 播放时长 , 获取的是总的秒数
            if(in_stream->duration == AV_NOPTS_VALUE)
            {
                // #define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))
                // 得到一个无效的播放时长
                printf("audio duration unknown");
            }

执行结果 :

audio duration : -9223372036854775808
duration_audio unknown

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_FFmpeg_09



8、计算音频流的播放时长



AVFormatContext 结构体 中的 int64_t duration 成员 表示 的 播放时长 , 单位是 微秒 ;

#define AV_TIME_BASE            1000000

计算该时长的时候 , 只需要 除以 AV_TIME_BASE 值 即可 得到 播放时长 对应的 秒数 ;

// 计算总的秒数
    total_seconds = (fmt_ctx->duration) / AV_TIME_BASE;



FFmpeg 中 需要保存 精确值 的时候 , 一般会保存分数 , 尽量将精确的数值展现给开发者 ;

AVStream 中的其单位是 AVStream 中的 AVRational time_base 成员 ;

AVRational 是一个分数结构体 , int num 成员是 分数的 分子 , int den 成员是 分数的 分母 ;

typedef struct AVRational {
    int num; // 分子
    int den; // 分母
} AVRational;

48000Hz 采样率的音频的 分子是 1 , 分母是 48000 ;



av_q2d 函数 用于将 AVRational 分数 类型 转换为 双精度浮点数 ;

av_q2d 函数原型 :

static inline double av_q2d(AVRational a);



计算 音频播放时长 代码示例 :

// 计算出实际播放时长 , 单位秒                                                        
int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
printf("audio duration : %d\n", duration_audio);

执行结果 :

audio duration : 222 , num : 1 , den : 48000

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_FFmpeg_10

使用 MediaInfo 打开该 test.mp4 视频文件 , 可以看到该视频的 时长是 3分42秒 , 正好是 222 秒 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音视频_11






二、视频流参数解析




1、视频流类型判定



遍历 AVFormatContext 结构体 的 AVStream **streams 指针数组

判定 AVStream 结构体中的 AVCodecParameters *codecpar 成员 的 enum AVMediaType codec_type 成员 , 该枚举成员是 媒体流的类型 , 如果 媒体流类型是 AVMEDIA_TYPE_VIDEO , 说明这是 视频流 ;



音频流判定代码如下 :

if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //解析视频流参数
{
}



2、获取视频流索引编号



AVStream 结构体 的 int index 成员 , 就是 媒体流 的索引编号

// 视频流 索引 编号                              
printf("index : %d\n", in_stream->index);



3、获取视频流帧率



在 FFmpeg 中 , 可以通过 AVStream 结构体 的 AVRational avg_frame_rate 成员 或 AVRational r_frame_rate 成员 获取 视频流 的帧率 , 帧率 表示 视频 每秒显示的帧数 , 单位 fps ( Frame per Second ) ;

  • avg_frame_rate 表示 视频流 的 平均帧率
  • r_frame_rate 表示 视频流 中 帧率估计值


代码示例 : 平均帧率是 14.464607 FPS , 帧率估值是 15 FPS ;

// 视频流 帧率 , avg_frame_rate 单位 fps , frame per second 每秒多少帧                                                                                                     
// 平均帧率 , 适用于可变帧率的视频                                                                                                                                           
printf("avg_frame_rate fps : %lffps , num : %d , den ; %d\n", av_q2d(in_stream->avg_frame_rate), in_stream->avg_frame_rate.num, in_stream->avg_frame_rate.den);
                                                                                                                                                               
// 帧率的估计值                                                                                                                                                      
printf("r_frame_rate fps : %lffps , num : %d , den ; %d\n", av_q2d(in_stream->r_frame_rate), in_stream->r_frame_rate.num, in_stream->r_frame_rate.den);

执行结果 :

avg_frame_rate fps : 14.464607fps , num : 48225 , den ; 3334
r_frame_rate fps : 15.000000fps , num : 15 , den ; 1

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音频流_12



4、获取视频流压缩编码格式



通过 AVStream 媒体流 结构体的 AVCodecParameters *codecpar 成员 的 enum AVCodecID codec_id 字段 , 可以获取 视频流的压缩编码器 ID ;

之前的音频流的压缩编码 ID 也是通过这个方式获取的 , 参考 一、音频流参数解析 6、获取音频流的压缩编码 id 章节 ;



代码示例 : 使用下面的代码可以打印 视频流的 编解码器 ID ;

// 视频流 的 压缩编码格式                                           
// AV_CODEC_ID_AAC                                        
printf("codec_id:%d\n", in_stream->codecpar->codec_id);

执行结果是 :

codec_id:27

在 enum AVCodecID 枚举字段中查找 27 , 是 AV_CODEC_ID_H264 , 这是 H.264 视频格式 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_ffmpeg_13



5、获取视频流压缩编码格式



在 FFmpeg 中 , 通过 AVStream 结构体 中的 AVCodecParameters *codecpar 成员 来 获取视频画面的 宽度 和 高度 ,

  • AVCodecParameters 结构体 中的 int width 成员 是 视频画面的 宽度
  • AVCodecParameters 结构体 中的 int height 成员 是 视频画面的 高度


代码示例 : 在下面的代码中 , 打印视频画面 宽高 ;

// 视频画面 宽度 和 高度                                                                                 
printf("width : %d ,  height : %d\n", in_stream->codecpar->width, in_stream->codecpar->height);

执行结果 :

width : 1920 ,  height : 1080

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_音视频_14

使用 MediaInfo 打开 test.mp4 视频文件 , 可以看到 该视频文件的宽高 , 就是 1920 x 1080 ;

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_ffmpeg_15



6、获取视频流播放时长



视频流 的 播放时长 与 音频流 的播放时长 获取方式 是一样的 ;

  • AVStream 结构体中的 int64_t duration 成员 就是视频流的播放时长 ;
  • 视频播放时长 的 单位 是 AVStream 中的 时间基 AVRational time_base 成员 ;

更多细节 参考 上面的 一、音频流参数解析 7、获取音频流的播放时长 章节 ;



代码示例 : 下面的代码 , 打印出了视频的播放时长 , 以及将其转为 秒 单位的值 ;

// 视频流 播放时长                                                                        
printf("vedio duration : %lld\n", in_stream->duration);                            
                                                                                   
// 视频流 播放时长 , 获取的是总的秒数                                                             
if(in_stream->duration == AV_NOPTS_VALUE)                                          
{                                                                                  
    // #define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))              
    // 得到一个无效的播放时长                                                                 
    printf("vedio duration unknown");                                              
}                                                                                  
else                                                                               
{                                                                                  
    // 计算出实际播放时长 , 单位秒                                                             
    int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);     
    printf("vedio duration : %d , num : %d , den : %d\n",                          
           duration_audio, in_stream->time_base.num, in_stream->time_base.den);    
}

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_FFmpeg_16






三、数据包解析




1、遍历数据包



遍历 AVPacket 数据包 , 首先要 初始化 AVPacket 结构体 , 之后 视频流 或 音频流 中的数据 填充到该结构体中 ;

启动一个循环 , 在循环体中 , 调用 av_read_frame 函数 , 读取 AVPacket 数据包 , 开始遍历 流 中的数据包 ;

// 开始解析 数据包
    AVPacket *pkt = av_packet_alloc();
    while (1)
    {
        // 读取数据包
        ret = av_read_frame(fmt_ctx, pkt);
        
		// 解析  AVPacket 数据包
    }



2、获取数据包解码时间戳 - DTS



DTS 解码时间戳 ( Decoding Timestamp ) 是指 数据帧 被解码的时间点 , 单位是 时间基 time_base , 用于确保解码器按正确的顺序解码帧 ;

DTS 主要用于控制帧的 解码顺序 , 特别是在包含 B 帧 的编码中 , B 帧可能需要先解码后面的帧 ;

  • B 帧 是 双向内插帧 ( Bi-directional Predicted Frames ) , 记录的是 本帧 B 帧 与 前后 I 帧 或 P 帧 的差别 ;


AVPacket 中的 int64_t pts 成员就是 数据包 的 解码时间戳 ;

typedef struct AVPacket {
    /**
     * 表示时间戳,单位为 AVStream->time_base;即解压后的数据包将呈现给用户的时间。
     * 如果文件中未存储该值,可以为 AV_NOPTS_VALUE。
     * pts 必须大于或等于 dts,因为呈现不能发生在解压之前,除非是想查看十六进制转储。
     * 一些格式错误使用了 dts 和 pts/cts 的术语来表示不同的含义。
     * 在存储到 AVPacket 之前,必须将这些时间戳转换为真正的 pts/dts。
     */
    int64_t pts;
}



代码示例 : 直接获取 AVPacket 中的 pts 成员 ;

printf("audio pts: %lld\n", pkt->pts);



3、获取数据包显示时间戳 - PTS



PTS 显示时间戳 ( Presentation Timestamp ) 是 帧或音频样本 在播放过程中应该 显示或播放 的时间 , 单位是 时间基 time_base , 主要用于同步音视频的播放 ;



AVPacket 中的 int64_t pts 成员就是 数据包 的 解码时间戳 ;

typedef struct AVPacket {
    /**
     * 解压时间戳,单位为 AVStream->time_base;即数据包被解压的时间。
     * 如果文件中未存储该值,可以为 AV_NOPTS_VALUE。
     */
    int64_t dts;
}



代码示例 : 直接获取 AVPacket 中的 dts 成员 ;

printf("audio dts: %lld\n", pkt->dts);



4、获取数据包大小



在 AVPacket 结构体中 , size 字段的作用是表示数据包中实际存储的数据大小 , 单位 字节 , 这是 data 字段指向的缓冲区的大小 ;

printf("audio size: %d\n", pkt->size);



5、获取数据包字节位置



AVPacket 中的 int64_t pos 成员就是 数据包 在 媒体流 的 字节位置 ;

typedef struct AVPacket {
    int64_t pos;                            ///< byte position in stream, -1 if unknown
}



代码示例 :

printf("audio pos: %lld\n", pkt->pos);






四、完整代码示例



完整代码示例 :

#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>

int main(int argc, char **argv)
{
    // 打印 FFmpeg 版本号
    printf("FFmpeg version is %s\n", av_version_info());

    // 初始化网络环境
    // 如果只打开本地文件 , 则不需要调用该函数
    //avformat_network_init();


    // 1. 获取命令行参数

    // 要解封装的 视频文件 的 相对路径
    char *file_name = "test.ts";

    // 打印参数基本信息
    printf("argc = %d , argv[0] = %s\n", argc, argv[0]);

    // 检测 参数中 是否传入了 参数
    // 命令行中 可执行程序 是第 0 个参数 , 之后的附加参数是第 1 个参数
    if(argc >= 2 && argv[1] != NULL) {
        file_name = argv[1];
        // 打印传入的参数
        printf("argv[1] = %s\n", argv[1]);
    }


    // 2. 打开视频文件
    // 描述 媒体文件 或 媒体流 的构成和基本信息
    AVFormatContext *fmt_ctx = NULL;

    // 打开文件 , 探测协议类型 , 将获取的参数信息 填充到 AVFormatContext 结构体中
    int ret = avformat_open_input(&fmt_ctx, file_name, NULL, NULL);
    // avformat_open_input 函数 : 执行成功返回 0 , 执行失败返回负数 ( 错误码 )
    if (ret < 0)
    {
        // 用于接收错误信息的字符串
        char buf[1024] = { 0 };
        // 获取 负数错误码 对应的 错误日志信息
        av_strerror(ret, buf, sizeof(buf) - 1);
        // 打印错误信息
        printf("avformat_open_input failed : %s\n", file_name, buf);
        goto failed;
    }


    // 3. 获取 媒体流 详细信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    // 获取 码流 信息失败 , 返回 负数错误码
    if (ret < 0)
    {
        // 用于接收错误信息的字符串
        char buf[1024] = { 0 };
        // 获取 负数错误码 对应的 错误日志信息
        av_strerror(ret, buf, sizeof(buf) - 1);
        // 打印错误信息
        printf("avformat_find_stream_info failed:%s\n", file_name, buf);
        goto failed;
    }


    // 4. 打印 视频文件 的 参数信息 ☆☆☆
    printf_s("\n==== av_dump_format start ===\n\n");
    av_dump_format(fmt_ctx, 0, file_name, 0);
    printf_s("\n==== av_dump_format end ===\n");


    // 5. 获取文件路径或地址
    printf("url : %s\n", fmt_ctx->url);


    // 6. 获取码流个数
    printf("stream number : %d\n", fmt_ctx->nb_streams);


    // 7. 获取播放时长
    int total_seconds, hour, minute, second;
    // duration: 播放时长 , 单位 微妙 (us) , 1000us = 1ms , 1000ms = 1秒
    // 计算总的秒数
    total_seconds = (fmt_ctx->duration) / AV_TIME_BASE;
    // 计算小时数
    hour = total_seconds / 3600;
    // 计算分钟数
    minute = (total_seconds % 3600) / 60;
    // 计算秒数
    second = (total_seconds % 60);
    printf("duration: %02d:%02d:%02d\n\n", hour, minute, second);


    // 8. 获取视频文件的总比特率 , 单位为bps
    printf("\nbit_rate : %lld bps\n",(int64_t)(fmt_ctx->bit_rate));


    // 9. 遍历媒体流
    int video_index = -1;        // 视频索引
    int audio_index = -1;        // 音频索引
    for (uint32_t i = 0; i < fmt_ctx->nb_streams; i++)
    {
        // 获取第 i 个媒体流 , 该媒体流可以是 音频流、视频流、字幕流
        AVStream *in_stream = fmt_ctx->streams[i];

        if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type) // 解析音频流参数
        {
            printf("\n\n==== Audio info ===\n\n");

            // 设置 音频流 参数索引
            audio_index = i;

            // 音频流 索引 编号
            printf("index : %d\n", in_stream->index);

            // 音频流 采样率 一般是 44100Hz 48000Hz
            printf("sample_rate : %dHz\n", in_stream->codecpar->sample_rate);

            // 音频流 采样格式 , 也就是采样位数
            printf("format : %d \n", in_stream->codecpar->format);

            // 音频流 声道数 , 单声道有 1 个 , 立体声有 2 个
            printf("channel num : %d \n", in_stream->codecpar->channels);

            // 音频流 压缩编码 ID
            // AV_CODEC_ID_AAC
            printf("codec_id : %d \n", in_stream->codecpar->codec_id);

            // 音频流 播放时长
            printf("audio duration : %lld\n", in_stream->duration);

            // 音频流 播放时长 , 获取的是总的秒数
            if(in_stream->duration == AV_NOPTS_VALUE)
            {
                // #define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))
                // 得到一个无效的播放时长
                printf("audio duration unknown");
            }
            else
            {
                // 计算出实际播放时长 , 单位秒
                int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
                printf("audio duration : %d , num : %d , den : %d\n",
                       duration_audio, in_stream->time_base.num, in_stream->time_base.den);
            }

        }
        else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //解析视频流参数
        {
            printf("\n\n==== Vedio info ===\n\n");

            // 设置 视频流 参数索引
            video_index = i;

            // 视频流 索引 编号
            printf("index : %d\n", in_stream->index);

            // 视频流 帧率 , avg_frame_rate 单位 fps , frame per second 每秒多少帧
            // 平均帧率 , 适用于可变帧率的视频
            printf("avg_frame_rate fps : %lf FPS , num : %d , den ; %d\n", av_q2d(in_stream->avg_frame_rate), in_stream->avg_frame_rate.num, in_stream->avg_frame_rate.den);

            // 帧率的估计值
            printf("r_frame_rate fps : %lf FPS , num : %d , den ; %d\n", av_q2d(in_stream->r_frame_rate), in_stream->r_frame_rate.num, in_stream->r_frame_rate.den);

            // 视频流 的 压缩编码格式
            // AV_CODEC_ID_AAC
            printf("codec_id:%d\n", in_stream->codecpar->codec_id);

            // 视频画面 宽度 和 高度
            printf("width : %d ,  height : %d\n", in_stream->codecpar->width, in_stream->codecpar->height);

            // 视频流 播放时长
            printf("vedio duration : %lld\n", in_stream->duration);

            // 视频流 播放时长 , 获取的是总的秒数
            if(in_stream->duration == AV_NOPTS_VALUE)
            {
                // #define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))
                // 得到一个无效的播放时长
                printf("vedio duration unknown");
            }
            else
            {
                // 计算出实际播放时长 , 单位秒
                int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);
                printf("vedio duration : %d , num : %d , den : %d\n",
                       duration_audio, in_stream->time_base.num, in_stream->time_base.den);
            }
        }
    }

    // 开始解析 数据包
    AVPacket *pkt = av_packet_alloc();

    // 统计数据包个数
    int pkt_count = 0;

    printf("\n\n==== Packet info ===\n\n");

    while (1)
    {
        // 读取数据包
        ret = av_read_frame(fmt_ctx, pkt);
        // 数据包读取失败处理
        if (ret < 0)
        {
            printf("av_read_frame failed\n");
            break;
        }

        // 只解析 5 个数据包
        if(pkt_count++ < 5)
        {
            if (pkt->stream_index == audio_index)   // 音频包
            {
                printf("audio pts: %lld\n", pkt->pts);
                printf("audio dts: %lld\n", pkt->dts);
                printf("audio size: %d\n", pkt->size);
                printf("audio pos: %lld\n", pkt->pos);
                printf("audio duration: %lf\n\n",
                       pkt->duration * av_q2d(fmt_ctx->streams[audio_index]->time_base));
            }
            else if (pkt->stream_index == video_index)  // 视频包
            {
                printf("video pts: %lld\n", pkt->pts);
                printf("video dts: %lld\n", pkt->dts);
                printf("video size: %d\n", pkt->size);
                printf("video pos: %lld\n", pkt->pos);
                printf("video duration: %lf\n\n",
                       pkt->duration * av_q2d(fmt_ctx->streams[video_index]->time_base));
            }
            else
            {
                printf("unknown stream_index:\n", pkt->stream_index);
            }
        }

        // 解析玩后 解除 AVPacket 引用
        av_packet_unref(pkt);
    }

    if(pkt)
    {
        // 释放 AVPacket
        av_packet_free(&pkt);
    }

failed: // FFmpeg 出错 , 函数执行失败后 释放内存
    if(fmt_ctx){
        // 与 avformat_open_input 配对使用 , 防止内存泄漏
        avformat_close_input(&fmt_ctx);
    }

    // 执行结束
    printf("\nFFmpeg End\n");

    // 防止 命令行终端 打印完信息 后 马上退出
    //getchar();
    return 0;
}

执行结果 :

FFmpeg version is 4.2.1
argc = 2 , argv[0] = D:\002_Project\008_Qt\build-FFmpegC-Desktop_Qt_5_14_2_MSVC2015_32bit-Debug\debug\FFmpegC.exe
argv[1] = test.mp4

==== av_dump_format start ===

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf56.38.102
    comment         : www.ieway.cn
  Duration: 00:03:42.53, start: 0.000000, bitrate: 281 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 15360 tbn, 30 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

==== av_dump_format end ===
url : test.mp4
stream number : 2
duration: 00:03:42


bit_rate : 281340 bps


==== Vedio info ===

index : 0
avg_frame_rate fps : 14.464607 FPS , num : 48225 , den ; 3334
r_frame_rate fps : 15.000000 FPS , num : 15 , den ; 1
codec_id:27
width : 1920 ,  height : 1080
vedio duration : 3414016
vedio duration : 222 , num : 1 , den : 15360


==== Audio info ===

index : 1
sample_rate : 48000Hz
format : 8
channel num : 2
codec_id : 86018
audio duration : 10680624
audio duration : 222 , num : 1 , den : 48000


==== Packet info ===

audio pts: -678
audio dts: -678
audio size: 341
audio pos: 48
audio duration: 0.021333

video pts: 0
video dts: 0
video size: 66736
video pos: 389
video duration: 0.066667

audio pts: 346
audio dts: 346
audio size: 341
audio pos: 67125
audio duration: 0.021333

audio pts: 1370
audio dts: 1370
audio size: 342
audio pos: 67466
audio duration: 0.021333

audio pts: 2394
audio dts: 2394
audio size: 341
audio pos: 67808
audio duration: 0.021333

av_read_frame failed

FFmpeg End

【FFmpeg】解封装 ④ ( 解封装代码实战 | 音频流参数解析 | 视频流参数解析 | 数据包解析 )_解封装_17