XVideoView.h

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
#ifndef XVIDEO_VIEW_H
#define XVIDEO_VIEW_H
#include <mutex>
#include <fstream>
#include "xtools.h"

struct AVFrame;

class XVideoView
{
public:
enum Format
{
YUV420P = 0,
NV12 = 23,
ARGB = 25,
RGBA = 26,
BGRA = 28
};

enum RenderType
{
SDL = 0
};

static XVideoView* Create(RenderType type=SDL);

virtual bool Init(int w, int h,
Format fmt = RGBA) = 0;

virtual bool Init(AVCodecParameters *para);

virtual bool Draw(const unsigned char* data, int linesize = 0) = 0;
virtual bool Draw(const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
) = 0;

virtual void Close() = 0;

virtual bool IsExit() = 0;

void Scale(int w, int h)
{
scale_w_ = w;
scale_h_ = h;
}

bool DrawFrame(AVFrame* frame);

int render_fps() { return render_fps_; }

bool Open(std::string filepath);

AVFrame *Read();

void set_win_id(void*win) { win_id_ = win; }

virtual ~XVideoView();

protected:
void *win_id_ = nullptr; // 窗口句柄

int render_fps_ = 0; // 显示帧率
int width_ = 0; // 材质宽度
int height_ = 0; // 材质高度
Format fmt_ = RGBA; // 像素格式
std::mutex mtx_; // 确保线程安全

int scale_w_ = 0; // 显示大小
int scale_h_ = 0;

long long beg_ms_ = 0; // 计时开始时间
int count_ = 0; // 统计显示次数

private:
std::ifstream ifs_;
AVFrame *frame_ = nullptr; // 视频帧
unsigned char* cache_ = nullptr; // 用于复制nv12缓冲
};

#endif

参数介绍

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
protected:
void *win_id_ = nullptr; // 窗口句柄
// 窗口句柄是一个指向SDL窗口对象的指针,用于标识和操作该窗口。
// 窗口句柄在创建窗口时生成,并在需要对窗口进行操作时使用,
// 如设置窗口标题、调整窗口大小、获取窗口的表面等。

int render_fps_ = 0; // 显示帧率
int width_ = 0; // 材质宽度(文件原本的宽高)
int height_ = 0; // 材质高度
Format fmt_ = RGBA; // 像素格式
std::mutex mtx_; // 确保线程安全

int scale_w_ = 0; // 显示大小(显示在屏幕面前的宽高)
int scale_h_ = 0;

long long beg_ms_ = 0; // 计时开始时间
int count_ = 0; // 统计显示次数

private:
std::ifstream ifs_; // 用于从文件中读取数据。
AVFrame *frame_ = nullptr; // 视频帧
/*
AVFrame包含:
* uint8_t = *data:保存图像的实际像素数据
int linesize:每个图像的行宽,单位字节
int width、int height:图像宽高,单位像素
enum AVPixelFormat format:像素格式,例如 YUV420P,RGB24 等。
int64_t pts:解码时间戳(Presentation Timestamp),用于同步音视频。
*/

unsigned char* cache_ = nullptr; // 用于复制nv12缓冲

XVideoView.cpp

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
#include "xsdl.h"
#include "xvideo_view.h"
#include <iostream>

using namespace std;

extern "C"
{
#include <libavcodec/avcodec.h>
}

#pragma comment(lib, "avutil.lib")

bool XVideoView::Init(AVCodecParameters *para)
{
if (!para) return false;
auto fmt = (Format)para->format;
switch (para->format)
{
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
fmt = YUV420P;
break;
default:
break;
}
return Init(para->width, para->height, fmt);
}

AVFrame* XVideoView::Read()
{
if (width_ <= 0 || height_ <= 0 || !ifs_) return nullptr;

if (frame_)
{
if (frame_->width != width_ ||
frame_->height != height_ ||
frame_->format != fmt_)
{
av_frame_free(&frame_);
}
}

if (!frame_)
{
frame_ = av_frame_alloc();
frame_->width = width_;
frame_->height = height_;
frame_->format = fmt_;
frame_->linesize[0] = width_ * 4;
if (frame_->format == AV_PIX_FMT_YUV420P)
{
frame_->linesize[0] = width_; // Y
frame_->linesize[1] = width_ / 2; // U
frame_->linesize[2] = width_ / 2; // V
}

auto re = av_frame_get_buffer(frame_, 0);

if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << buf << endl;
av_frame_free(&frame_);
return NULL;
}
}

if (!frame_) return NULL;

if (frame_->format == AV_PIX_FMT_YUV420P)
{
ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);
ifs_.read((char*)frame_->data[1], frame_->linesize[1] * height_ / 2);
ifs_.read((char*)frame_->data[2], frame_->linesize[2] * height_ / 2);
}
else
{
ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);
}

if (ifs_.gcount() == 0)
return NULL;
return frame_;
}

bool XVideoView::Open(std::string filepath)
{
if (ifs_.is_open())
{
ifs_.close();
}
ifs_.open(filepath, ios::binary);

return ifs_.is_open();
}

XVideoView* XVideoView::Create(RenderType type)
{
switch (type)
{
case XVideoView::SDL:
return new XSDL();
break;
default:
break;
}

return nullptr;
}

XVideoView::~XVideoView()
{
if (cache_)
delete cache_;
cache_ = nullptr;
}


bool XVideoView::DrawFrame(AVFrame* frame)
{
if (!frame || !frame->data[0]) return false;
count_++;
if (beg_ms_ <= 0)
{
beg_ms_ = clock();
}

else if((clock() - beg_ms_) / (CLOCKS_PER_SEC / 1000) >= 1000)
{
render_fps_ = count_;
count_ = 0;
beg_ms_ = clock();
}

int linesize = 0;
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
return Draw(frame->data[0], frame->linesize[0], // Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
case AV_PIX_FMT_YUVJ420P:
return Draw(frame->data[0], frame->linesize[0], // Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
case AV_PIX_FMT_NV12:
if (!cache_)
{
cache_ = new unsigned char[4092 * 2160 * 1.5];
}
linesize = frame->width;

if (frame->linesize[0] == frame->width)
{
memcpy(cache_, frame->data[0], frame->linesize[0] * frame->height);
memcpy(cache_ + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2);
}
else
{
for (int i = 0; i < frame->height; i++) // Y
{
memcpy(cache_ + i * frame->width,
frame->data[0] + i * frame->linesize[0],
frame->width
);
}
for (int i = 0; i < frame->height; i++) // UV
{
auto p = cache_ + frame->height * frame->width; // 移位Y
memcpy(p + i * frame->width,
frame->data[1] + i * frame->linesize[1],
frame->width
);
}
}

return Draw(cache_, linesize);

break;
case AV_PIX_FMT_BGRA:
return Draw(frame->data[0], frame->linesize[0]);
case AV_PIX_FMT_ARGB:
return Draw(frame->data[0], frame->linesize[0]);
case AV_PIX_FMT_RGBA:
return Draw(frame->data[0], frame->linesize[0]);
default:
break;
}
return false;
}

Init()

作用:用于给AVCodecParameters传入的参数做转换,并传递给SDL能够处理的Init()函数,此函数在XVideoView基类XSDL中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// AVCodecParameters:这是 FFmpeg 中的一个结构体
// 包含了编解码器相关的参数,例如视频的宽度、高度、像素格式等。
bool XVideoView::Init(AVCodecParameters *para)
{
if (!para) return false;
auto fmt = (Format)para->format;
switch (para->format)
{
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
fmt = YUV420P; // 有些录像机的format为YUVJ420P
break;
default:
break;
}
return Init(para->width, para->height, fmt);
}

YUVJ420P与YUV420P的区别

  • YUV420P:Y分量的值范围是16到235,U和V分量的值范围是16到240。这是为了兼容标准的广播电视信号。
  • YUVJ420P:Y、U、V分量的值范围都是0到255,这意味着它没有剪裁值,可以表示更宽的颜色范围。这通常用于JPEG图像编码和一些视频处理应用中,以提高图像质量。

Read()

作用:读取一帧的数据,返回为AVFrame

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
AVFrame* XVideoView::Read()
{
// 在读取之前,需要设置材质宽高与读取文件的参数
if (width_ <= 0 || height_ <= 0 || !ifs_) return nullptr;

// AVFrame空间已经申请,如果参数发生变化,需要释放空间
if (frame_)
{
if (frame_->width != width_ ||
frame_->height != height_ ||
frame_->format != fmt_)
{
// 释放AVFrame对象空间,和buf引用计数减一
av_frame_free(&frame_);
}
}

if (!frame_)
{
// 分配对象空间和像素空间
frame_ = av_frame_alloc();
frame_->width = width_;
frame_->height = height_;
frame_->format = fmt_;
frame_->linesize[0] = width_ * 4; // 处理RGB与RGBA格式
if (frame_->format == AV_PIX_FMT_YUV420P || frame_->format == AV_PIX_FMT_YUVJ420P) // 处理YUV格式
{
frame_->linesize[0] = width_; // Y
frame_->linesize[1] = width_ / 2; // U
frame_->linesize[2] = width_ / 2; // V
}

// 生成AVFrame空间,使用默认对齐
auto re = av_frame_get_buffer(frame_, 0);

if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << buf << endl;
av_frame_free(&frame_);
return NULL;
}
}

if (!frame_) return NULL;

// 读取一帧数据
if (frame_->format == AV_PIX_FMT_YUV420P || frame_->format == AV_PIX_FMT_YUVJ420P)
{
ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_); // 读取Y
ifs_.read((char*)frame_->data[1], frame_->linesize[1] * height_ / 2); // 读取U
ifs_.read((char*)frame_->data[2], frame_->linesize[2] * height_ / 2); // 读取V
}
else // RGBA ARGB BGRA 32
{
ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);
}

/*
检查文件读取的字节数,如果读取字节数为 0,说明读取失败,返回 nullptr。
否则返回读取的 AVFrame 对象。
*/
if (ifs_.gcount() == 0)
return NULL;
return frame_;
}

读取YUV420P数据

  • frame_->data[0]:指向Y平面的数据。linesize[0]是每行Y数据的字节数,总共读取linesize[0] * height_字节。

  • frame_->data[1]:指向U平面的数据。U平面高度是原图像高度的一半,所以读取的字节数是linesize[1] * height_ / 2

  • frame_->data[2]:指向V平面的数据。V平面高度也是原图像高度的一半,所以读取的字节数是linesize[2] * height_ / 2

读取RGBA、RGB数据

  • 对于这些格式,整个图像的数据在一个平面上,即frame_->data[0]

  • linesize[0]是每行数据的字节数(通常是宽度的4倍,如果每个像素是4字节)。

  • 总共读取linesize[0] * height_字节来获取整幅图像的数据。

Open()

作用:将传入的文件打开,并返回是否成功

1
2
3
4
5
6
7
8
9
10
bool XVideoView::Open(std::string filepath)
{
if (ifs_.is_open())
{
ifs_.close();
}
ifs_.open(filepath, ios::binary);

return ifs_.is_open();
}

Create()

作用:根据传入的参数,返回XVideoView类,默认为XSDL

1
2
3
4
5
6
7
8
9
10
11
12
13
XVideoView* XVideoView::Create(RenderType type)
{
switch (type)
{
case XVideoView::SDL:
return new XSDL();
break;
default:
break;
}

return nullptr;
}

~XVideoView()

作用:析构函数,将nv12缓冲清空,防止内存泄漏

1
2
3
4
5
6
XVideoView::~XVideoView()
{
if (cache_)
delete cache_;
cache_ = nullptr;
}

DrawFrame()

作用:计算fps,根据format进行绘制,Draw()函数在基类XSDL中

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
bool XVideoView::DrawFrame(AVFrame* frame)
{
// !frame->data[0]代表传入的参数中没有数据
if (!frame || !frame->data[0]) return false;
count_++;
if (beg_ms_ <= 0)
{
beg_ms_ = clock(); // 获取开始时间
}
// 计算显示帧率
else if((clock() - beg_ms_) / (CLOCKS_PER_SEC / 1000) >= 1000) // 一秒计算一次fps
{
render_fps_ = count_;
count_ = 0;
beg_ms_ = clock();
}

int linesize = 0;
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
return Draw(frame->data[0], frame->linesize[0], // Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
case AV_PIX_FMT_YUVJ420P:
return Draw(frame->data[0], frame->linesize[0], // Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
case AV_PIX_FMT_NV12:
if (!cache_)
{
cache_ = new unsigned char[4092 * 2160 * 1.5];
}
linesize = frame->width; // 获取视频的宽度,用于处理RGBA RGB格式


/*
当 frame->linesize[0] 等于 frame->width 时,表示每行的数据是紧密排列的,
没有额外的填充字节,这样可以直接进行内存复制操作。
否则,需要逐行处理每行数据,跳过那些可能存在的填充字节。
*/
if (frame->linesize[0] == frame->width)
{
memcpy(cache_, frame->data[0], frame->linesize[0] * frame->height);
memcpy(cache_ + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2);
}
else
{
/*
memcpy(void *dest, const void *src, size_t n);
dest:目标地址的指针,即将数据复制到这个地址。
src:源地址的指针,即从这个地址读取数据。
n:要复制的字节数。
*/

// 需要逐行处理每行数据,跳过那些可能存在的填充字节。
for (int i = 0; i < frame->height; i++) // Y
{
memcpy(cache_ + i * frame->width, // 目标地址
frame->data[0] + i * frame->linesize[0], // 源地址
frame->width // 复制字节数
);
// 复制了 frame->height * frame->width的 Y数据 字节
}
for (int i = 0; i < frame->height; i++) // UV
{
auto p = cache_ + frame->height * frame->width; // 移位Y
memcpy(p + i * frame->width,
frame->data[1] + i * frame->linesize[1],
frame->width
);
// 复制了 frame->height * frame->width的 UV数据 字节
}
}

return Draw(cache_, linesize);

break;
case AV_PIX_FMT_BGRA:
return Draw(frame->data[0], frame->linesize[0]);
case AV_PIX_FMT_ARGB:
return Draw(frame->data[0], frame->linesize[0]);
case AV_PIX_FMT_RGBA:
return Draw(frame->data[0], frame->linesize[0]);
default:
break;
}
return false;
}