转化前准备

视频、图片格式介绍

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
#define YUV_FILE "400_300_25.yuv"
#define RGBA_FILE "800_600_25.rgba"

// YUV
int width = 400, height = 300;

// RGBA
int rgb_width = 800, rgb_height = 600;

unsigned char *yuv[3] = { 0 };
int yuv_linesize[3] = { width, width / 2, width / 2 }; // Y、U、V的每行数量
yuv[0] = new unsigned char[width * height]; // Y的数量
yuv[1] = new unsigned char[width * height / 4]; // U的数量
yuv[2] = new unsigned char[width * height / 4]; // V的数量

// RGBA存储 rgba rgba
unsigned char *rgba = new unsigned char[rgb_width * rgb_height * 4];
int rgba_linesize = rgb_width * 4;

// 读取YUV_FILE数据
ifstream ifs;
ifs.open(YUV_FILE, ios::binary);

// 写入RGBA_FILE数据
ofstream ofs;
ofs.open(RGBA_FILE, ios::binary);

SwsContext *yuvTorgb = nullptr;

SwsContext *yuvTorgb = nullptr;

SwsContext 是 FFmpeg 中 libswscale 库的一部分。libswscale 是一个用于图像缩放和像素格式转换的库。

SwsContext 保存了图像转换操作的上下文信息,包括输入输出格式、大小、缩放选项等。

YUV转RGBA

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
for (;;)
{
// 读取YUV帧,将其存储到yuv[]中
ifs.read((char*)yuv[0], width * height);
ifs.read((char*)yuv[1], width * height / 4);
ifs.read((char*)yuv[2], width * height / 4);

// 当 gcount() 返回 0 时,表示已经没有更多的数据可供读取,或者读取操作遇到了文件末尾(EOF)。
if (ifs.gcount() == 0) break;

// YUV 转 RGBA
// 上下文件创建和获取
yuvTorgb = sws_getCachedContext(
yuvTorgb, // 转换上下文、NULL表示新创建,非NULL则判断与现有参数是否一致,
// 一致直接返回,不一致先清理当前然后再次创建
width, height, // 输入宽高
AV_PIX_FMT_YUV420P, // 输入像素格式
rgb_width, rgb_height, // 输出的宽高
AV_PIX_FMT_RGBA, // 输出的像素格式
SWS_BILINEAR, // 选择尺寸变换的算法,双线性插值算法
0, 0, 0 // 过滤器参数
);

if (!yuvTorgb)
{
cerr << "sws_getCachedContext failed!" << endl;
return 1;
}

unsigned char* data[1];
data[0] = rgba;
int lines[1] = { rgba_linesize };

// sws_scale()返回转换的行数,如果失败则会返回负数
int re = sws_scale(
yuvTorgb,
yuv, // 输入数据
yuv_linesize, // 输入数据行字节数,上面定义的yuv_linesize[3]
0, // 转换的起始行
height, // 输入高度
data, // 输出的数据的存储位置
lines // 输出的数据的字节数
);
std::cout << re << " " << flush;

// 将rgba数据写入ofs中
ofs.write((char*)rgba, rgb_width * rgb_height * 4);
}

ifs.close(); // 关闭文件流,释放相关资源
ofs.close(); // 将缓冲区中的所有数据写入800_600_25.rgba中,之后释放相关资源

重要函数

sws_getCachedContext

sws_getCachedContext是一个非常有用的函数,可以简化和优化图像缩放格式转换过程中的上下文管理。它允许你在不同的参数设置之间动态调整 SwsContext,而无需手动管理内存,同时提供了对图像缩放算法的灵活选择。

函数原型

1
2
3
4
5
6
7
struct SwsContext *sws_getCachedContext(
struct SwsContext *context,
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter, SwsFilter *dstFilter,
const double *param
);

参数解释

  1. context: 之前创建的 SwsContext 上下文指针。如果是 NULL,函数会创建一个新的上下文。
  2. srcW: 源图像的宽度。
  3. srcH: 源图像的高度。
  4. srcFormat: 源图像的像素格式(如 AV_PIX_FMT_YUV420P 等)。
  5. dstW: 目标图像的宽度。
  6. dstH: 目标图像的高度。
  7. dstFormat: 目标图像的像素格式。
  8. flags: 用于指定缩放和转换的算法,比如 SWS_BILINEAR(双线性插值)或 SWS_BICUBIC(三次插值)等。
  9. srcFilter: 源图像的过滤器,通常可以是 NULL。
  10. dstFilter: 目标图像的过滤器,通常可以是 NULL。
  11. param: 一个指向双精度浮点数组的指针,用于设置特定的缩放参数,可以为 NULL。
1
2
3
4
5
6
7
8
9
10
yuvTorgb = sws_getCachedContext(
yuvTorgb, // SwsContext 指针、NULL表示新创建,非NULL则判断与现有参数是否一致,
// 一致直接返回,不一致先清理当前然后再次创建
width, height, // 输入宽高
AV_PIX_FMT_YUV420P, // 输入像素格式
rgb_width, rgb_height, // 输出的宽高
AV_PIX_FMT_RGBA, // 输出的像素格式
SWS_BILINEAR, // 选择尺寸变换的算法,双线性插值算法
0, 0, 0 // 过滤器参数
);

sws_scale

sws_scale 是 FFmpeg 中 libswscale 库的核心函数之一,用于执行图像缩放和像素格式转换操作。它利用 SwsContext 上下文中的配置信息,将源图像数据转换为目标图像数据

函数原型

1
2
3
4
5
6
7
8
9
int sws_scale(
struct SwsContext *c,
const uint8_t *const srcSlice[],
const int srcStride[],
int srcSliceY,
int srcSliceH,
uint8_t *const dst[],
const int dstStride[]
);

参数解释

  1. c:
    • 类型: struct SwsContext *
    • 说明: 这是一个指向 SwsContext 的指针,用于图像缩放和色彩空间转换。SwsContext 包含了图像处理所需的所有参数和状态信息。可以使用 sws_getCachedContextsws_getContext 来创建这个上下文。
  2. srcSlice:
    • 类型: const uint8_t *const srcSlice[]
    • 说明: 源图像数据的数组,每个元素都是一个指向源图像某个平面的指针(例如,对于 YUV420P 格式,指向 Y、U 和 V 平面)。对于平面图像格式,数组的每个元素对应一个色彩分量(如 Y、U、V);对于非平面格式(如 RGB),通常只使用数组的第一个元素指向整个图像数据。
  3. srcStride:
    • 类型: const int srcStride[]
    • 说明: 源图像每一行的字节数的数组,表示每个平面(或通道)在每行的步幅(字节数)。步幅是指相邻行在内存中相距的字节数。对于 YUV420P 图像,srcStride[0] 通常是图像宽度,srcStride[1]srcStride[2] 是色度平面的宽度(通常是亮度平面宽度的一半)。
  4. srcSliceY:
    • 类型: int
    • 说明: 源图像的起始行的 Y 坐标,表示从图像的哪一行开始处理。通常情况下,值为 0 表示从图像的第一行开始。
  5. srcSliceH:
    • 类型: int
    • 说明: 要处理的源图像的行数。这决定了 sws_scale 函数在源图像中从 srcSliceY 开始读取多少行进行缩放和转换。
  6. dst:
    • 类型: uint8_t *const dst[]
    • 说明: 目标图像数据的数组,每个元素是一个指向目标图像某个平面的指针。类似于 srcSlice,对于平面图像格式,数组的每个元素对应一个色彩分量(例如 RGB 格式只有一个平面);对于非平面格式,通常只使用数组的第一个元素指向整个图像数据。
  7. dstStride:
    • 类型: const int dstStride[]
    • 说明: 目标图像每一行的字节数的数组,表示每个平面(或通道)在每行的步幅(字节数)。这个值决定了写入目标图像时每行数据在内存中占据的字节数。通常情况下,对于 RGB 图像,dstStride[0] 是图像的宽度乘以 3(因为 RGB 每个像素占用 3 个字节)。
1
2
3
4
5
6
7
8
9
int re = sws_scale(
yuvTorgb, // SwsContext 指针
yuv, // 输入数据
yuv_linesize, // 输入数据行字节数
0, // 转换的起始行
height, // 输入高度
data, // 输出的数据的存储位置
lines // 输出的数据的字节数
);

两者关系

  1. sws_getCachedContextsws_scale准备步骤。你必须先使用 sws_getCachedContext 获取一个 SwsContext,然后才能使用这个上下文来调用 sws_scale 进行图像转换。

  2. sws_getCachedContext 确保你得到一个正确配置的 SwsContext,其中包含了源图像和目标图像的所有必要信息(如尺寸、格式、缩放算法等)。

  3. sws_scale 使用 sws_getCachedContext 创建或获取的 SwsContext 进行具体的转换操作。

RGBA转YUV

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
ifs.open(RGBA_FILE, ios::binary);
ofs.open(YUV_FILE, ios::binary);
SwsContext *rgbToyuv = nullptr;
for (;;)
{
// 读取RGBA帧
ifs.read((char*)rgba, rgb_width * rgb_height * 4);

if (ifs.gcount() == 0) break;


// YUV 转 RGBA
// 上下文件创建和获取
rgbToyuv = sws_getCachedContext(
rgbToyuv, // 转换上下文、NULL表示新创建,非NULL则判断与现有参数是否一致,
// 一致直接返回,不一致先清理当前然后再次创建
rgb_width, rgb_height, // 输入宽高
AV_PIX_FMT_RGBA, // 输入像素格式
width, height, // 输出的宽高
AV_PIX_FMT_YUV420P, // 输出的像素格式
SWS_BILINEAR, // 选择尺寸变换的算法,双线性插值算法
0, 0, 0 // 过滤器参数
);

if (!rgbToyuv)
{
cerr << "sws_getCachedContext failed!" << endl;
return 1;
}

unsigned char*data[1];
data[0] = rgba;
int lines[1] = { rgba_linesize };
int re = sws_scale(
rgbToyuv,
data,
lines,
0,
rgb_height,
yuv,
yuv_linesize
);
std::cout << "(re)" << flush;

ofs.write((char*)yuv[0], width * height);
ofs.write((char*)yuv[1], width * height / 4);
ofs.write((char*)yuv[2], width * height / 4);
}

清理内存数据

1
2
3
4
5
6
delete yuv[0];
delete yuv[1];
delete yuv[2];
delete rgba;
ifs.close();
ofs.close();