代码

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
/*
这段代码的作用是使用 FFmpeg 库解码 H.264 视频文件。
它读取 H.264 编码的视频文件 test.h264,将数据分割成帧,并将每一帧数据传递给解码器,解码后输出帧格式。
*/

#include <iostream>
#include <fstream>
#include <cstring>

using namespace std;

extern "C" { // 指定函数是C语言函数,函数名不包含重载标注
// 引入ffmpeg头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}

// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")


int main(int argc, char *argv[])
{
// 分割h264 存入AVPacket
string filename = "test.h264";
ifstream ifs(filename, ios::binary);
if (!ifs) return -1;

// 缓冲区,用于存储从文件读取的原始数据块。
unsigned char inbuf[4096] = {0};

AVCodecID codec_id = AV_CODEC_ID_H264;

// ========(一)========
// 找到指定的解码器(如 H.264 解码器)。
auto codec = avcodec_find_decoder(codec_id);

// 分配并初始化一个 AVCodecContext。
auto c = avcodec_alloc_context3(codec);

// 打开这个解码器,并将其与上下文 c 关联,准备好解码操作。
avcodec_open2(c, NULL, NULL);



// 分割上下文

// ========(二)========
// 初始化一个解析器(parser)
auto parser = av_parser_init(codec_id);

// 创建并分配一个 AVPacket,用于存储原始数据分割后的单帧数据。
auto pkt = av_packet_alloc();

// 创建并分配一个 AVFrame,用于存储解码后的帧数据。
auto frame = av_frame_alloc();

while (!ifs.eof())
{
// 每次读取 4096 字节的数据到 inbuf 缓冲区中。
ifs.read((char*)inbuf, sizeof(inbuf));
int data_size = ifs.gcount(); // 读取的字节数
if (data_size <= 0) break;

auto data = inbuf;
while (data_size > 0) // 一次有多帧数据
{
// =======(三)=======
// 函数将读取到的数据解析成一个个完整的帧,存储在 pkt 中。
// data 和 data_size 用来跟踪剩余未处理的数据。
int ret = av_parser_parse2(parser, c,
&pkt->data, &pkt->size, // 输出
data, data_size, // 输入
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
);

data += ret;
data_size -= ret; // 已经处理的数据

// 处理已经存储的数据pkt->size
if (pkt->size)
{
// cout << pkt->size << " " << flush;
// 发送packet到解码线程
ret = avcodec_send_packet(c, pkt);
if (ret < 0) break;

// 获取多帧解码数据
while (ret >= 0)
{
// 每次会调用av_frame_unref
ret = avcodec_receive_frame(c, frame);
if (ret < 0) break;
// =======(四)=======
cout << frame->format << " " << flush;
}
}
}
}

// 通过发送一个 NULL 数据包,通知编码器或解码器当前已没有更多的数据需要处理,
// 编码器或解码器可以开始《处理并输出剩余的缓冲数据》。
int ret = avcodec_send_packet(c, NULL);
while (ret >= 0)
{
ret = avcodec_receive_frame(c, frame);
if (ret < 0) break;
cout << frame->format << "-" << flush;
}

av_parser_close(parser);
av_frame_free(&frame);
avcodec_free_context(&c);
av_packet_free(&pkt);

return 0;
}

运行结果

image-20240817153459901

(一)

1
2
3
4
auto codec = avcodec_find_decoder(codec_id);

// 函数签名
const AVCodec* avcodec_find_decoder(enum AVCodecID id);

作用】:这行代码的作用是通过给定的编解码器 ID 来查找并返回一个指向相应解码器的指针。它是 FFmpeg 中用于初始化解码操作的第一步。

参数 codec_id

  • codec_id 是一个枚举类型 AVCodecID,用于标识你想要使用的具体编解码器。例如,AV_CODEC_ID_H264 是 H.264 解码器的标识符。
  • 在这段代码中,codec_id 是通过提前定义或通过某种方式传递进来的,代表需要使用的解码器类型。

返回值 codec

  • avcodec_find_decoder(codec_id) 返回一个 const AVCodec* 类型的指针,指向找到的解码器(AVCodec)结构体。
  • 如果成功找到解码器,返回一个有效的指针;如果没有找到对应的解码器,返回 NULL

1
2
3
4
auto c = avcodec_alloc_context3(codec);

// 函数签名
AVCodecContext* avcodec_alloc_context3(const AVCodec *codec);

作用】:这行代码的作用是为指定的编解码器(编码器或解码器)分配并初始化一个编解码器上下文(AVCodecContext)。这个上下文将包含编解码操作所需的所有配置信息和状态。

参数 codec

  • codec 是一个指向 AVCodec 结构体的指针,表示你将要使用的具体编解码器(如 H.264、H.265 编解码器)。
  • 通过 avcodec_find_decoder()avcodec_find_encoder() 找到的 AVCodec 指针会作为参数传递给 avcodec_alloc_context3()
  • 如果 codec 参数为 NULL,则函数会创建一个通用的 AVCodecContext,稍后可以通过其他方式设置编码器或解码器。

返回值 c

  • avcodec_alloc_context3() 返回一个指向 AVCodecContext 结构体的指针,即 c
  • 这个返回的 AVCodecContext 是新分配的,并初始化为默认值的上下文,它可以在后续操作中进行配置和使用。

1
2
3
4
avcodec_open2(c, NULL, NULL);

// 函数签名
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

作用】:用于打开一个编解码器(编码器或解码器),并将其与指定的编解码器上下文(AVCodecContext关联起来,使其准备好进行编码或解码操作。

参数解释

  1. AVCodecContext *avctx
    • 这是一个指向 AVCodecContext 的指针,表示编解码器的上下文。上下文包含了编解码器运行所需的所有配置信息,例如比特率、分辨率、帧率等。
    • 在调用 avcodec_open2 之前,AVCodecContext 通常已经通过 avcodec_alloc_context3() 函数分配并初始化。
  2. const AVCodec *codec
    • 这是一个指向 AVCodec 的指针,表示要使用的具体编解码器(如 H.264、H.265 等)。
    • 在这段代码中,codec 参数被传递为 NULL,表示使用上下文 avctx 中已经设置的编解码器。这意味着 AVCodecContext 必须在调用之前已经正确设置了编解码器。
  3. AVDictionary **options
    • 这是一个指向 AVDictionary 的指针,用于传递给编解码器的额外选项,可以设置一些特殊的编码或解码参数。
    • 在这段代码中,options 被传递为 NULL,表示不传递任何额外选项。

返回值

  • int函数的返回值类型为 int
  • 如果返回值为 0,表示编解码器成功打开并准备好进行操作。
    • 如果返回负值,则表示出现错误。具体错误原因可以通过返回的错误代码进行判断。

(二)

1
2
3
4
auto parser = av_parser_init(codec_id);

// 函数签名
AVCodecParserContext* av_parser_init(int codec_id);

作用】:

  1. 使用 av_parser_init(codec_id) 初始化一个解析器,用于解析特定编码格式的数据流。解析器的类型由 codec_id 决定,如 AV_CODEC_ID_H264 对应 H.264 格式。

  2. 初始化完成后,parser 可以与其他函数配合使用,比如 av_parser_parse2(),来处理输入的编码数据流,并将其分割成一个个完整的帧或其他数据包。

参数 codec_id

  • codec_id 是一个 AVCodecID 枚举值,用于标识你希望解析的具体编解码器类型(如 H.264、H.265 等)。
  • 这个参数告诉解析器它即将处理的是哪种格式的数据流,从而选择适当的解析方式。

返回值 parser

  • av_parser_init(codec_id) 返回一个指向 AVCodecParserContext 的指针,即 parser
  • 如果初始化成功,parser 是一个有效的指针,表示解析器已经准备好使用。如果初始化失败,返回值为 NULL

AVCodecParserContext 结构体

  • AVCodecParserContext 是 FFmpeg 中用于解析原始数据流的结构体。
  • 它负责将连续的字节流解析成独立的帧或包,供解码器进一步处理。这个过程对于处理实时流媒体或分割数据包非常重要。

(三)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 【注】data数据为unsigned char,data_size数据为int

int ret = av_parser_parse2(
parser, c,
&pkt->data, &pkt->size, // 输出
data, data_size, // 输入
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
);

// 函数签名
int av_parser_parse2(
AVCodecParserContext *s, AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts, int64_t pos
);

作用】:使用 FFmpeg 的解析器 (parser) 对原始的编码数据流(如 H.264 数据)进行解析,并从中提取出一帧或多个帧的数据包。这个函数会处理输入的数据,并将解析出的帧数据存储在 AVPacket 结构中,以便后续的解码使用。

参数解释

  • AVCodecParserContext *s(parser)
    • 这是解析器上下文,parser 是通过 av_parser_init() 初始化的。它持有解析器的状态信息,并在多次调用过程中保持。
  • AVCodecContext *avctx (c)
    • 这是编解码器上下文,包含了解析器关联的编解码器信息(如 H.264 解码器)。这个上下文用于配置和管理解码器的状态和参数。
  • uint8_t poutbuf(&pkt->data)int poutbuf_size (&pkt->size)
    • 这些是输出参数,用于存储解析出的帧数据。poutbuf 指向数据输出缓冲区的指针poutbuf_size 是该缓冲区的大小
    • pkt->datapkt->size 分别存储了解析后的一帧或部分帧的数据和大小。
  • const uint8_t *buf (data)int buf_size (data_size)
    • 这些是输入参数,指向输入数据缓冲区(具体指向输入数据缓冲区的起始位置,如从文件或网络流中读取的数据)及其大小data 是指向数据缓冲区的指针,data_size 是该缓冲区的大小。
  • int64_t pts (AV_NOPTS_VALUE), int64_t dts (AV_NOPTS_VALUE), int64_t pos (0)
    • 这些是时间戳和位置信息,通常用于解码过程中同步音视频流。这里传入 AV_NOPTS_VALUE0 表示没有特定的时间戳或位置需要处理。

返回值 ret

  • 函数返回值 ret 表示处理的数据量(以字节为单位)。ret 值告诉你在当前调用中解析了多少字节的数据。
  • 如果 ret 为负值,则表示解析过程中出现了错误。

(四)

解码后的操作

调用这个函数ret = avcodec_receive_frame(c, frame);

意味着将一帧的数据通过函数处理后,传递给了frame这个参数,此时的frame就有这一帧的所有信息

比如:formatheightwidth

之后就可以通过XSDL进行窗口初始化,以及Draw()函数的处理,之后调用XVideoView中的DrawFrame()函数将这一帧数据显示在窗口上