3. 视频格式转换
任务目标
构建一个最小GStreamer管道,从本地文件读取RGB原始视频数据,通过videoconvert完成色彩空间与像素格式转换,输出I420格式的原始视频文件,并验证转换结果正确。
环境与依赖
- GStreamer核心库及
videoconvert插件(通常包含在gst-plugins-base中) - 用于生成测试输入RGB文件的工具(如Python或
dd) - 用于验证I420输出的工具(如
ffprobe或Python脚本)
实现方案
使用filesrc读取原始RGB帧数据,通过videoconvert将像素格式从RGB转换为I420,最后用filesink写入输出文件。管道中需显式指定输入视频的宽度、高度和帧率,因为原始RGB数据不包含任何头部信息。
关键完整代码
// convert_rgb_to_i420.c
#include <gst/gst.h>
int main(int argc, char *argv[]) {
GstElement *pipeline, *filesrc, *videoconvert, *filesink;
GstCaps *src_caps;
GstBus *bus;
GstMessage *msg;
GError *error = NULL;
gst_init(&argc, &argv);
// 创建管道元素
pipeline = gst_pipeline_new("rgb-to-i420-pipeline");
filesrc = gst_element_factory_make("filesrc", "source");
videoconvert = gst_element_factory_make("videoconvert", "converter");
filesink = gst_element_factory_make("filesink", "sink");
if (!pipeline || !filesrc || !videoconvert || !filesink) {
g_printerr("创建元素失败\n");
return -1;
}
// 设置输入输出文件路径
g_object_set(filesrc, "location", "input_rgb.raw", NULL);
g_object_set(filesink, "location", "output_i420.raw", NULL);
// 构建管道
gst_bin_add_many(GST_BIN(pipeline), filesrc, videoconvert, filesink, NULL);
if (!gst_element_link(filesrc, videoconvert)) {
g_printerr("filesrc 与 videoconvert 连接失败\n");
gst_object_unref(pipeline);
return -1;
}
// 设置输入caps:RGB格式,分辨率320x240,帧率25fps
src_caps = gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, "RGB",
"width", G_TYPE_INT, 320,
"height", G_TYPE_INT, 240,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL);
if (!gst_element_link_filtered(videoconvert, filesink, src_caps)) {
g_printerr("videoconvert 与 filesink 连接失败\n");
gst_caps_unref(src_caps);
gst_object_unref(pipeline);
return -1;
}
gst_caps_unref(src_caps);
// 注意:此处src_caps实际是设置给filesink的sink pad,
// 用于声明期望的输出格式。videoconvert会自动协商。
// 启动管道
gst_element_set_state(pipeline, GST_STATE_PLAYING);
// 等待结束或错误
bus = gst_element_get_bus(pipeline);
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
gst_message_parse_error(msg, &error, NULL);
g_printerr("运行错误: %s\n", error->message);
g_error_free(error);
}
// 清理
gst_message_unref(msg);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
编译与运行
# 编译
gcc convert_rgb_to_i420.c -o convert_rgb_to_i420 $(pkg-config --cflags --libs gstreamer-1.0)
# 生成测试输入:320x240 RGB帧(每帧320*240*3字节),生成10帧
python3 -c "
import struct
width, height, frames = 320, 240, 10
with open('input_rgb.raw', 'wb') as f:
for frame in range(frames):
for y in range(height):
for x in range(width):
# 生成渐变彩色图案
f.write(bytes([x % 256, y % 256, (x+y) % 256]))
"
# 运行转换
./convert_rgb_to_i420
# 预期输出文件大小:320*240*1.5*10 = 1,152,000 字节
ls -l output_i420.raw
验证方法
通过以下两种方式验证转换正确性:
- 文件大小检查:I420格式每像素占1.5字节(Y平面1字节+U/V平面各0.25字节)。320×240分辨率10帧的预期文件大小为
320×240×1.5×10 = 1,152,000字节。若文件大小匹配,说明管道正确完成了格式转换。 - 像素内容验证:使用Python读取输出文件,检查YUV数据是否符合RGB到I420的标准转换公式。对于RGB值(r,g,b),Y = 0.299r + 0.587g + 0.114b。读取输出文件第一帧的第一个像素Y值,与输入RGB第一个像素计算值对比,误差应在±2范围内(因整数舍入)。
# 验证脚本(Python)
import struct
# 读取输入RGB第一个像素
with open('input_rgb.raw', 'rb') as f:
r, g, b = f.read(3)
# 计算预期Y值
y_expected = int(0.299*r + 0.587*g + 0.114*b)
# 读取输出I420第一个像素的Y值
with open('output_i420.raw', 'rb') as f:
y_actual = f.read(1)[0]
print(f"预期Y={y_expected}, 实际Y={y_actual}")
assert abs(y_expected - y_actual) <= 2, "Y值偏差过大"
print("验证通过")
常见问题
- 管道启动失败:确认
videoconvert插件已安装(gst-inspect-1.0 videoconvert可检查)。 - 输出文件大小为0:检查输入文件路径是否正确,以及输入RGB数据是否与声明的分辨率匹配。
- 格式协商失败:确保
src_caps中设置的参数与输入文件实际数据一致,特别是format字符串必须为"RGB"(大端RGB)或"BGR"(小端RGB),根据生成输入文件的字节序选择。