You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GStreamer音视频切换问题:Valve元素未按预期控制媒体流求助

解决GStreamer Valve元素控制音视频流失效问题

问题描述

需求:实现本地WebM文件的音视频切换逻辑——前3秒仅播放视频(无音频),随后播放音频直至EOS,最后从第3秒处恢复视频播放。
尝试用GStreamer的Valve元素分别控制音频流和视频流,但运行代码后音视频始终同时播放,Valve控制逻辑未生效。

原代码如下:

#include <gst/gst.h>

GstElement *pipeline, *source, *demux, *audio_queue, *video_queue, *audio_sink, *video_sink, *audio_valve, *video_valve;
GstPad *demux_audio_pad, *demux_video_pad, *demux_pad_template;

static gboolean audio_valve_open = TRUE;
static gboolean video_valve_open = FALSE;

static void pad_added_callback(GstElement *src, GstPad *new_pad, gpointer data) {
    GstPad *sink_pad;
    GstElement *queue = (GstElement *)data;
    GstCaps *new_pad_caps = NULL;
    GstStructure *new_pad_struct = NULL;
    const gchar *new_pad_type;

    g_print("Received new pad '%s' from '%s':\n", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(src));

    // Check the pad's type
    new_pad_caps = gst_pad_get_current_caps(new_pad);
    new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
    new_pad_type = gst_structure_get_name(new_pad_struct);

    // If it's audio, link it to the audio_queue
    if (g_str_has_prefix(new_pad_type, "audio/x-raw")) {
        g_print("Audio pad added\n");
        sink_pad = gst_element_get_static_pad(audio_queue, "sink");
        gst_pad_link(new_pad, sink_pad);
    } else if (g_str_has_prefix(new_pad_type, "video/x-raw")) {
        // If it's video, link it to the video_queue
        g_print("Video pad added\n");
        sink_pad = gst_element_get_static_pad(video_queue, "sink");
        GstPadLinkReturn ret = gst_pad_link(new_pad, sink_pad);
        if (ret != GST_PAD_LINK_OK) {
            g_error("Failed to link demux video pad to video queue pad. Error: %s",                                  
            gst_pad_link_get_name(ret));
        }
    }

    if (new_pad_caps != NULL)
        gst_caps_unref(new_pad_caps);
}

static gboolean switch_to_video(gpointer data) {
    g_print("Switching to Video...\n");
    // Close audio valve
    gst_element_set_state(audio_valve, GST_STATE_NULL);
    audio_valve_open = FALSE;
    // Open video valve
    gst_element_set_state(video_valve, GST_STATE_PLAYING);
    video_valve_open = TRUE;
    return FALSE; // Don't call this function again
}

int main(int argc, char *argv[]) {
    GstBus *bus;
    GstMessage *msg;

    // Initialize GStreamer
    gst_init(&argc, &argv);

    // Create the pipeline
    pipeline = gst_pipeline_new("pipeline");

    // Create elements
    source = gst_element_factory_make("filesrc", "source");
    demux = gst_element_factory_make("decodebin", "demux");
    audio_queue = gst_element_factory_make("queue", "audio_queue");
    video_queue = gst_element_factory_make("queue", "video_queue");
    audio_sink = gst_element_factory_make("alsasink", "audio_sink");
    video_sink = gst_element_factory_make("autovideosink", "video_sink");
    audio_valve = gst_element_factory_make("valve", "audio_valve");
    video_valve = gst_element_factory_make("valve", "video_valve");

    // Check elements creation
    if (!pipeline || !source || !demux || !audio_queue || !video_queue || !audio_sink || !video_sink || !audio_valve || !video_valve) {
        g_error("Failed to create elements. Exiting.");
        return -1;
    }

    // Set the location of the file to play
    g_object_set(source, "location", "/home/lg/ml/new.webm", NULL);

    // Add elements to the pipeline
    gst_bin_add_many(GST_BIN(pipeline), source, demux, audio_queue, video_queue, audio_sink, video_sink, audio_valve, video_valve, NULL);

    // Link elements
    gst_element_link(source, demux);
    gst_element_link_many(audio_queue, audio_valve, audio_sink, NULL);
    gst_element_link_many(video_queue, video_valve, video_sink, NULL);

    // Connect pad-added signal
    g_signal_connect(demux, "pad-added", G_CALLBACK(pad_added_callback), NULL);

    // Start playing
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // Wait for 5 seconds to switch to video
    g_timeout_add_seconds(5, switch_to_video, NULL);

    // Listen for messages on the pipeline's bus
    bus = gst_element_get_bus(pipeline);
    msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    // Free resources
    gst_message_unref(msg);
    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);

    return 0;
}

问题分析

  1. Valve控制方式错误:Valve元素的开关不是通过修改元素状态,而是通过设置drop属性——drop=true时丢弃所有数据(关闭流),drop=false时允许数据通过(开启流)。原代码用gst_element_set_state()操作Valve完全无效。
  2. 初始状态未配置:代码里定义了audio_valve_open和video_valve_open变量,但没有将其映射到Valve的drop属性,导致初始时音频Valve未关闭、视频Valve未开启,音视频直接同时播放。
  3. 定时器时间不符合需求:需求是前3秒仅播放视频,原代码设置的是5秒后切换,逻辑不符。
  4. 缺少EOS后恢复视频的逻辑:原代码没有处理音频播放到EOS后,从第3秒恢复视频播放的逻辑。

修复后的代码

#include <gst/gst.h>

GstElement *pipeline, *source, *demux, *audio_queue, *video_queue, *audio_sink, *video_sink, *audio_valve, *video_valve;

// 切换音频开启、视频关闭
static gboolean switch_to_audio(gpointer data) {
    g_print("Switching to Audio...\n");
    // 开启音频Valve
    g_object_set(audio_valve, "drop", FALSE, NULL);
    // 关闭视频Valve
    g_object_set(video_valve, "drop", TRUE, NULL);
    return FALSE;
}

// EOS后恢复视频播放(从第3秒开始)
static gboolean restore_video_on_eos(gpointer data) {
    g_print("Restoring Video from 3s...\n");
    // 重置视频流到第3秒位置
    gint64 seek_pos = 3 * GST_SECOND;
    gst_element_seek_simple(pipeline, GST_FORMAT_TIME,
                            GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
                            seek_pos);
    // 开启视频Valve
    g_object_set(video_valve, "drop", FALSE, NULL);
    return FALSE;
}

static void pad_added_callback(GstElement *src, GstPad *new_pad, gpointer data) {
    GstPad *sink_pad = NULL;
    GstCaps *new_pad_caps = gst_pad_get_current_caps(new_pad);
    GstStructure *new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
    const gchar *new_pad_type = gst_structure_get_name(new_pad_struct);

    g_print("Received new pad '%s' from '%s':\n", GST_PAD_NAME(new_pad), GST_ELEMENT_NAME(src));

    if (g_str_has_prefix(new_pad_type, "audio/x-raw")) {
        g_print("Audio pad added\n");
        sink_pad = gst_element_get_static_pad(audio_queue, "sink");
    } else if (g_str_has_prefix(new_pad_type, "video/x-raw")) {
        g_print("Video pad added\n");
        sink_pad = gst_element_get_static_pad(video_queue, "sink");
    }

    if (sink_pad) {
        GstPadLinkReturn ret = gst_pad_link(new_pad, sink_pad);
        if (ret != GST_PAD_LINK_OK) {
            g_error("Failed to link pad: %s", gst_pad_link_get_name(ret));
        }
        gst_object_unref(sink_pad);
    }

    gst_caps_unref(new_pad_caps);
}

int main(int argc, char *argv[]) {
    GstBus *bus;
    GstMessage *msg;

    gst_init(&argc, &argv);

    // 创建元素
    pipeline = gst_pipeline_new("pipeline");
    source = gst_element_factory_make("filesrc", "source");
    demux = gst_element_factory_make("decodebin", "demux");
    audio_queue = gst_element_factory_make("queue", "audio_queue");
    video_queue = gst_element_factory_make("queue", "video_queue");
    audio_sink = gst_element_factory_make("alsasink", "audio_sink");
    video_sink = gst_element_factory_make("autovideosink", "video_sink");
    audio_valve = gst_element_factory_make("valve", "audio_valve");
    video_valve = gst_element_factory_make("valve", "video_valve");

    if (!pipeline || !source || !demux || !audio_queue || !video_queue || !audio_sink || !video_sink || !audio_valve || !video_valve) {
        g_error("Failed to create elements.");
        return -1;
    }

    // 设置文件路径
    g_object_set(source, "location", "/home/lg/ml/new.webm", NULL);

    // 添加元素到管道
    gst_bin_add_many(GST_BIN(pipeline), source, demux, audio_queue, video_queue, audio_sink, video_sink, audio_valve, video_valve, NULL);

    // 链接元素
    gst_element_link(source, demux);
    gst_element_link_many(audio_queue, audio_valve, audio_sink, NULL);
    gst_element_link_many(video_queue, video_valve, video_sink, NULL);

    // 配置初始Valve状态:关闭音频,开启视频
    g_object_set(audio_valve, "drop", TRUE, NULL);
    g_object_set(video_valve, "drop", FALSE, NULL);

    // 连接pad-added信号
    g_signal_connect(demux, "pad-added", G_CALLBACK(pad_added_callback), NULL);

    // 启动管道
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // 3秒后切换到音频(关闭视频)
    g_timeout_add_seconds(3, switch_to_audio, NULL);

    // 监听总线消息
    bus = gst_element_get_bus(pipeline);
    while (TRUE) {
        msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_STATE_CHANGED);
        if (!msg) break;

        switch (GST_MESSAGE_TYPE(msg)) {
            case GST_MESSAGE_EOS:
                g_print("End of stream reached.\n");
                // EOS后恢复视频播放
                g_idle_add(restore_video_on_eos, NULL);
                break;
            case GST_MESSAGE_ERROR:
                gchar *debug;
                GError *err;
                gst_message_parse_error(msg, &err, &debug);
                g_error("Error: %s\nDebug: %s", err->message, debug);
                g_error_free(err);
                g_free(debug);
                goto cleanup;
            default:
                break;
        }
        gst_message_unref(msg);
    }

cleanup:
    if (msg) gst_message_unref(msg);
    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);

    return 0;
}

修复说明

  • 正确控制Valve:使用g_object_set()修改Valve的drop属性来开关流,这是Valve元素的标准控制方式。
  • 初始状态配置:启动前直接设置音频Valve为drop=true(关闭),视频Valve为drop=false(开启),满足前3秒仅视频的需求。
  • 修正定时器时间:将切换定时器改为3秒,符合需求。
  • 添加EOS处理逻辑:监听总线的EOS消息,触发后调用restore_video_on_eos函数,通过gst_element_seek_simple将视频流定位到第3秒,然后开启视频Valve恢复播放。
  • 优化Pad链接逻辑:移除无效的参数引用,直接根据pad类型获取对应的queue sink pad,逻辑更清晰。

内容的提问来源于stack exchange,提问作者Muthulakshimi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:44:53