Featured image of post 【雷霄骅课程笔记】1 FFmpeg 视频解码器

【雷霄骅课程笔记】1 FFmpeg 视频解码器

跟着雷神的课程学习写的一些笔记

|
4818 字
|

视频解码器

视频链接

FFmpeg 介绍

FFmpeg 库

FFmpeg 一共包含 8 个库:

  • avcodec: 编解码(最重要的库)*
  • avformat: 封装格式处理 *
  • avfilter: 滤镜特效处理
  • avdevice: 各种设备的输入输出
  • avutil: 工具库(大部分库都需要这个库的支持)*
  • postproc: 后加工
  • swresample: 音频采样数据格式转换
  • swscale: 视频像素数据格式转换 *

其中 * 表示本次课程中会涉及到的库

FFmpeg 解码流程

流程图

函数介绍

函数名 功能描述
avformat_open_input 打开输入文件并创建 AVFormatContext
avformat_find_stream_info 获取流信息
avcodec_find_decoder 查找解码器
avcodec_alloc_context3 分配解码器上下文
avcodec_parameters_to_context 复制编解码器参数
avcodec_open2 打开解码器
av_read_frame 读取数据包
avcodec_send_packet 发送数据包到解码器
avcodec_receive_frame 接收解码后的帧
sws_scale 像素格式转换
fwrite 写入转换后的帧数据
av_packet_unref 释放数据包
avcodec_free_context 释放解码器上下文
avformat_close_input 关闭输入流

FFmpeg 解码的数据结构

AV 表示 Audio Video

AVFormatContext: 用于处理封装格式的上下文,包含视频最外层的信息
AVInputFormat: 输入格式
AVStream: 是一个数组,包含多个流,但是一般就包含视频流和音频流,第 0 个是视频流,第 1 个是音频流
AVCodecContext: 编解码器上下文
AVCodec: 编解码器,指明编码器的类型(h.264之类的)

AVPacket: 压缩编码后的数据包
AVFrame: 解码后的数据包

AVPacket 解码完为 AVFrame

AVFormatContext

用于处理封装格式的上下文,包含视频最外层的信息

  • iformat: 输入视频的 AVInputFormat
  • nb_streams: 输入视频的 AVStream 个数
  • streams: 输入视频的 AVStream 数组
  • duration: 输入视频的时长(以微秒为单位)
  • bit_rate: 输入视频的码率

AVInputFormat

输入格式

  • name: 输入视频的格式名称
  • long_name: 输入视频格式的长名称
  • extensions: 输入视频格式的扩展名
  • id: 输入视频格式的 ID
  • 一些封装格式处理的接口函数

AVStream

是一个数组,包含多个流,但是一般就包含视频流和音频流,第 0 个是视频流,第 1 个是音频流

  • id: 输入视频流的 ID
  • codecpar: 输入视频流的 AVCodecContext
  • time_base: 输入视频流的时间基
  • r_frame_rate: 输入视频流的帧率

time_base 是一个分数,表示时间基,用于将时间戳转换为实际时间。 r_frame_rate 是一个分数,表示帧率,用于计算帧间隔时间。

AVCodecContext

编解码器上下文

  • codec:编解码器的 AVCodec
  • width, height: 图像的宽高
  • pix_fmt: 图像的像素格式
  • sample_rate: 音频的采样率
  • channels: 音频的声道数
  • sample_fmt: 音频的采样格式
  • codec_type: 编解码器的类型(视频、音频等)
  • codec_id: 编解码器的 ID

AVCodec

编解码器,指明编码器的类型(h.264之类的)

  • name: 编解码器的名称
  • long_name: 编解码器的全称
  • id: 编解码器的 ID
  • 一些编解码的接口函数

AVPacket

压缩编码后的数据包,理解成装 h264 数据的盒子

  • pts: 显示时间戳
  • dts: 解码时间戳
  • data: 压缩编码的数据
  • size: 数据的大小
  • stream_index: 所属的 AVStream (音频流还是视频流)

AVFrame

解码后的数据包,理解成装 yuv 数据的盒子

  • data: 解码后的图像数据(音频采样数据)
  • linesize: 对视频来说是图像中的一行像素的大小;对音频来说是整个音频帧的大小
  • width, height: 视频帧的宽和高
  • key_frame: 是否是关键帧
  • pict_type: 帧类型(I, B, P 帧)

补充小知识

解码后的数据为什么要经过 sws_scale 转换? 解码后 YUV 数据格式的视频像素数据保存在 AVFrame 的 data[0],data[1],data[2],但是这些像素值并不是连续存储的,每行有效像素之后存储的是无效像素。 以亮度 Y 数据为例,data[0] 中一共包含了 linesize[0] * height 个数据。但是出于优化等方面考虑,linesize[0] 可能大于 width 。因此需要使用 sws_scale 进行转换。
转换后去除了无效数据,width 和 linesize[0] 就相等了。

代码运行

雷神给的这些代码,在资源释放上有些问题(资源的释放都集中在最后,如果程序提前终止,那就导致资源没有释放),但这里先不考虑这些。

代码

头文件 lib 和库文件 include 的配置在前面博客说到,可以复制到这个项目中(上一个博客我讲的是放在 usr/local/ffmpeg ) 这里代码和雷神视频中的代码略有不同,修改了很多:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include <stdio.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
// Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libswscale/swscale.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
#endif

int main(int argc, char *argv[])
{
    AVFormatContext *pFormatCtx = NULL;
    int videoindex = -1;
    AVCodecContext *pCodecCtx = NULL;
    const AVCodec *pCodec = NULL;
    AVFrame *pFrame = NULL, *pFrameYUV = NULL;
    unsigned char *out_buffer = NULL;
    AVPacket *packet = NULL;
    int ret = 0;
    struct SwsContext *img_convert_ctx = NULL;

    char filepath[] = "../video/input.mkv";

    FILE *fp_yuv = fopen("output.yuv", "wb+");

    // 初始化FFmpeg库
    avformat_network_init();

    // 打开输入文件
    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)
    {
        printf("Couldn't open input stream.\n");
        return -1;
    }

    // 获取流信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    printf("时长:%ld\n", pFormatCtx->duration);

    // 查找视频流
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            break;
        }
    }

    if (videoindex == -1)
    {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    // 获取解码器
    pCodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
    if (pCodec == NULL)
    {
        printf("Codec not found.\n");
        return -1;
    }

    // 创建解码器上下文
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx)
    {
        printf("Could not allocate video codec context\n");
        return -1;
    }

    // 复制流参数到解码器上下文
    if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar) < 0)
    {
        printf("Could not copy codec parameters to context\n");
        return -1;
    }

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        return -1;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    out_buffer = (unsigned char *)av_malloc(
        av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P,
                         pCodecCtx->width, pCodecCtx->height, 1);

    packet = av_packet_alloc();

    // 输出文件信息
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");

    img_convert_ctx =
        sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
                       pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    while (av_read_frame(pFormatCtx, packet) >= 0)
    {
        if (packet->stream_index == videoindex)
        {
            ret = avcodec_send_packet(pCodecCtx, packet);
            if (ret < 0)
            {
                printf("Error sending a packet for decoding\n");
                return -1;
            }

            while (ret >= 0)
            {
                ret = avcodec_receive_frame(pCodecCtx, pFrame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    break;
                else if (ret < 0)
                {
                    printf("Error during decoding\n");
                    return -1;
                }

                sws_scale(img_convert_ctx, (const unsigned char *const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
                          pFrameYUV->linesize);

                int y_size = pCodecCtx->width * pCodecCtx->height;
                // U V 是分量,宽高各压缩一半,所以大小是 Y 的 1/4
                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);     // Y
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V
                printf("Succeed to decode 1 frame!\n");
            }
        }
        av_packet_unref(packet);
    }

    // 刷新解码器
    avcodec_send_packet(pCodecCtx, NULL);
    while (ret >= 0)
    {
        ret = avcodec_receive_frame(pCodecCtx, pFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0)
        {
            printf("Error during decoding\n");
            return -1;
        }

        sws_scale(img_convert_ctx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
                  pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

        int y_size = pCodecCtx->width * pCodecCtx->height;
        fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);     // Y
        fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
        fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V

        printf("Flush Decoder: Succeed to decode 1 frame!\n");
    }

    sws_freeContext(img_convert_ctx);

    fclose(fp_yuv);

    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    av_packet_free(&packet);
    avcodec_free_context(&pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}

CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 3.10)

project(MyProject VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 添加头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 添加库文件路径
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 添加可执行文件
add_executable(main src/decoder.cpp)

# 链接 FFmpeg 库
target_link_libraries(main
    avcodec
    avformat
    avutil
    swscale
)

手动编译一遍:

1
2
3
4
5
mkdir build
cd build
cmake ..
make
./main

如果成功输出,则说明配置成功
没成功的话要注意看一下源码的路径和播放视频的路径是否正确
我这里源码放在 src/decoder.cpp,播放视频放在video/input.mkv

调试

在左侧工具栏找到运行和调试工具

点击创建 launch.json 文件,选择C++(GDB/LLDB)
launch.json文件中添加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "version": "0.2.0",
    "configurations": [

        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/main",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}/build",
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "cmake-build-debug"
        }
    ]
}

按下 f5 即可调试

这里可能会遇到两个问题:

  1. 在源码位置按下 f5 后,会提示找不到找不到你链接的库
    这里可能原因是没执行你的 CMakeLists.txt 文件,换成点击左侧工具栏的启动按键就行

  2. 提示找不到输入文件,可以试试去掉 jsoncwd 字段的 /build

1
"cwd": "${workspaceFolder}"

后续博客就不讲调试的文件书写了,基本都差不多

练习

获取解码前的 h264 文件

注意这里只获取 MPEG-TS ,AVI 格式的文件,如果是别的文件,无法直接获取

这样重新编码后,就可以获取到解码前的 h264 文件了。

代码分析

  1. 打开文件
1
FILE *fp_h264 = fopen("test264.h264", "wb+");
  1. 循环从媒体文件中读取一帧数据,并将其存储在 AVPacket 结构体中。
1
while (av_read_frame(pFormatCtx, packet) >= 0)
  • 这个函数会读取下一个可用的数据包,无论是音频、视频还是其他类型的流
  1. 判断是否为视频帧
1
if (packet->stream_index == videoindex)
  1. 获取数据
1
fwrite(packet->data, 1, packet->size, fp_h264);
  1. 关闭文件,释放资源

注意事项

  1. 输入文件一定要是 tsavi 格式!
    因为 mp4flv 格式需要解析 moov 结构,而 tsavi 格式可以直接解析 h264 数据
    我就是一开始没注意,导致浪费了很多时间

  2. 获取完数据记得释放资源和关闭文件

获取解码后的 yuv 文件

  1. 打开文件
1
FILE *fp_yuv = fopen("testyuv.yuv", "wb+");
  1. 循环从媒体文件中读取一帧数据,并将其存储在 AVPacket 结构体中。
1
while (av_read_frame(pFormatCtx, packet) >= 0)
  1. 判断当前帧是否为视频流
1
if (packet->stream_index == video_index)
  1. 解码一帧视频数据
1
avcodec_send_packet(pCodecCtx, packet);
  • 这个函数会将 AVPacket 中的压缩数据发送给解码器进行解码
  1. 接收解码后的数据
1
2
ret == avcodec_receive_frame(pCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
  • 这个函数会从解码器中接收解码后的原始数据,并存储在 AVFrame 结构体中
  • AVERROR(EAGAIN) 是FFmpeg库中的一个错误码,表示当前没有足够的数据可供解码,需要等待更多数据到来才能继续解码。这种情况通常发生在数据流尚未准备好或缓冲区为空时
  • AVERROR_EOF 表示已经到达数据流的末尾(End of File),没有更多的数据可供解码
  1. 处理解码后的数据
1
2
3
4
5
6
7
img_convert_ctx = 
        sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
                       pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

sws_scale(img_convert_ctx, (const unsigned char *const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
                          pFrameYUV->linesize);

前面补充知识有提到,解码后的数据格式的视频像素值不是连续存储,而是按行存储的,会多出一些无效像素,导致像素的 width 和 linesize 不一致,要用 sws_scale 函数进行转换
sws_getContext 函数

  • 用途:创建图像转换上下文 (SwsContext)
  • 参数说明
    • pCodecCtx->width, pCodecCtx->height原始视频帧的宽度和高度
    • pCodecCtx->pix_fmt原始视频帧的像素格式
    • AV_PIX_FMT_YUV420P:目标像素格式,此处为 YUV420P
    • SWS_BICUBIC:缩放算法,使用双三次插值

sws_scale 函数

  • 用途:执行实际的图像格式转换
  • 参数说明
    • img_convert_ctx:之前创建的图像转换上下文
    • (const unsigned char *const *)pFrame->data:原始视频帧的数据
    • pFrame->linesize原始视频帧的每行字节数
    • 0:从原始帧的第 0 行开始转换
    • pCodecCtx->height原始视频帧的高度
    • pFrameYUV->data目标帧的数据缓冲区,用于存储转换后的 YUV420P 数据
    • pFrameYUV->linesize目标帧的每行字节数
  1. 保存 YUV 数据到文件
1
2
3
4
5
int y_size = pCodecCtx->width * pCodecCtx->height;
                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); // Y
                // U V 是分量,宽高各压缩一半,所以大小是 Y 的 1/4
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V
  1. 关闭文件,释放资源

完整代码

代码优化了一些,也把一些要求输出的信息写在里面了

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#include <stdio.h>

#define __STDC_CONSTANT_MACROS

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
}
#endif

int main(int argc, char *argv[])
{
    AVFormatContext *pFormatCtx = NULL;
    int videoindex = -1;
    AVCodecContext *pCodecCtx = NULL;
    const AVCodec *pCodec = NULL;
    AVFrame *pFrame = NULL, *pFrameYUV = NULL;
    unsigned char *out_buffer = NULL;
    AVPacket *packet = NULL;
    int ret = 0;
    struct SwsContext *img_convert_ctx = NULL;

    char filepath[] = "../video/Titanic.ts";   

    // 初始化FFmpeg库
    avformat_network_init();

    // 打开输入文件
    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)
    {
        printf("Couldn't open input stream.\n");
        return -1;
    }

    // 获取流信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    // 输出封装格式参数
    FILE *fp = fopen("output.txt", "wb+");
    fprintf(fp, "封装格式参数:\n");
    fprintf(fp, "  封装格式:%s\n  比特率:%ld\n  时长:%ld\n", pFormatCtx->iformat->name,
            pFormatCtx->bit_rate, pFormatCtx->duration);
    
    // 查找视频流
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            break;
        }
    }
    
    if (videoindex == -1)
    {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    // 输出视频编码参数
    fprintf(fp, "视频编码参数:\n");
    fprintf(fp,"  编码方式:%s\n  宽*高:%d * %d\n", avcodec_get_name(pFormatCtx->streams[videoindex]->codecpar->codec_id),pFormatCtx->streams[videoindex]->codecpar->width,pFormatCtx->streams[videoindex]->codecpar->height);

    // 获取解码器
    pCodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
    if (pCodec == NULL)
    {
        printf("Codec not found.\n");
        return -1;
    }

    // 创建解码器上下文
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx)
    {
        printf("Could not allocate video codec context\n");
        return -1;
    }

    // 复制流参数到解码器上下文
    if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar) < 0)
    {
        printf("Could not copy codec parameters to context\n");
        return -1;
    }

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.\n");
        return -1;
    }

    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();
    out_buffer = (unsigned char *)av_malloc(
        av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P,
                         pCodecCtx->width, pCodecCtx->height, 1);

    packet = av_packet_alloc();

    // 输出文件信息
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");

    img_convert_ctx =
        sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
                       pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    FILE *fp_h264 = fopen("1test264.h264", "wb+");
    FILE *fp_yuv = fopen("1testyuv.yuv", "wb+");
    fprintf(fp, "--------每一个解码前视频帧大小和解码后帧类型:-------------\n");
    while (av_read_frame(pFormatCtx, packet) >= 0)
    {        
        if (packet->stream_index == videoindex)
        {
            
            // 获取解码前的 H.264 码流数据
            fwrite(packet->data, 1, packet->size, fp_h264);

            // 获取解码前视频帧参数
            
            fprintf(fp, "帧大小:%d\n",packet->size);
            
            ret = avcodec_send_packet(pCodecCtx, packet);
            if (ret < 0)
            {
                printf("Error sending a packet for decoding\n");
                return -1;
            }

            while (ret >= 0)
            {
                ret = avcodec_receive_frame(pCodecCtx, pFrame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    break;
                else if (ret < 0)
                {
                    printf("Error during decoding\n");
                    return -1;
                }

                sws_scale(img_convert_ctx, (const unsigned char *const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
                          pFrameYUV->linesize);

                int y_size = pCodecCtx->width * pCodecCtx->height;
                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); // Y
                // U V 是分量,所以大小是 Y 的 1/4
                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V
                printf("Succeed to decode 1 frame!\n");
                if (pFrame->pict_type == AV_PICTURE_TYPE_I)
                {
                    fprintf(fp, "帧类型:I帧\n");
                }
                else if (pFrame->pict_type == AV_PICTURE_TYPE_P)
                {
                    fprintf(fp, "帧类型:P帧\n");
                }
                else if (pFrame->pict_type == AV_PICTURE_TYPE_B)
                {
                    fprintf(fp, "帧类型:B帧\n");
                }
                else
                {
                    fprintf(fp, "帧类型:未知帧\n");
                }
            }
        }
        av_packet_unref(packet);
    }
    
    fclose(fp);
    fclose(fp_h264);
    fclose(fp_yuv);
    // 刷新解码器
    avcodec_send_packet(pCodecCtx, NULL);
    while (ret >= 0)
    {
        ret = avcodec_receive_frame(pCodecCtx, pFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0)
        {
            printf("Error during decoding\n");
            return -1;
        }

        sws_scale(img_convert_ctx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
                  pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

        int y_size = pCodecCtx->width * pCodecCtx->height;
        fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);     // Y
        fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); // U
        fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); // V

        printf("Flush Decoder: Succeed to decode 1 frame!\n");
    }

    sws_freeContext(img_convert_ctx);

    // 这里的释放顺序要注意,先释放AVFrame,再释放AVPacket,最后释放AVCodecContext和AVFormatContext
    av_frame_free(&pFrameYUV);
    av_frame_free(&pFrame);
    av_packet_free(&packet);
    avcodec_free_context(&pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}

资源释放问题后面会再出博客写,因为涉及到各种细节

使用 Hugo 构建
主题 StackJimmy 设计