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

验证方法

通过以下两种方式验证转换正确性:

  1. 文件大小检查:I420格式每像素占1.5字节(Y平面1字节+U/V平面各0.25字节)。320×240分辨率10帧的预期文件大小为320×240×1.5×10 = 1,152,000字节。若文件大小匹配,说明管道正确完成了格式转换。
  2. 像素内容验证:使用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),根据生成输入文件的字节序选择。