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

GStreamer播放Raw PCM时无法调音量、跳转及查询时长问题

GStreamer播放PCM缓冲数据的三个问题解决

问题1:gst_element_seek_simple无法跳转、查询时长返回-1

原因

rawaudioparse本身不存储PCM数据的总时长信息,且默认配置下appsrc是不可查找的(non-seekable)。由于所有PCM数据由你手动推送,GStreamer框架无法自动感知数据总量和时间位置的对应关系,因此无法处理seek请求或返回有效时长。

解决步骤

  1. 配置appsrc为可查找并设置总时长
    在获取appsrc后,设置时间格式属性并计算总时长:

    // 设置appsrc使用时间格式,禁用自动打时间戳
    g_object_set(appsrc, "format", GST_FORMAT_TIME, "do-timestamp", FALSE, NULL);
    
    // 计算总时长:假设已获取全部PCM数据的总字节数total_bytes
    gint bytes_per_frame = 1 * 4; // 单通道+S32LE(4字节/采样)
    gint64 total_frames = total_bytes / bytes_per_frame;
    gint64 total_duration = (total_frames * GST_SECOND) / 44100; // 采样率44100
    gst_app_src_set_duration(GST_APP_SRC(appsrc), total_duration);
    
  2. 实现appsrc的seek回调处理跳转
    需要绑定seek信号的回调函数,手动处理跳转逻辑,计算对应PCM数据的起始位置并重置推送状态:

    // 自定义上下文结构体,存储PCM数据和当前推送位置
    typedef struct {
        guint8 *pcm_data;
        gsize total_bytes;
        gsize current_pos;
    } AudioDataCtx;
    
    // seek回调函数
    static gboolean appsrc_seek_cb(GstAppSrc *src, guint64 offset, GstSeekType type, guint64 stop, GstSeekType stop_type, GstAppSeekFlags flags, gpointer user_data) {
        AudioDataCtx *ctx = (AudioDataCtx*)user_data;
    
        // 将时间偏移转换为字节位置
        gint64 target_frames = (offset * 44100) / GST_SECOND;
        ctx->current_pos = target_frames * 4;
    
        // 停止当前推送,重新从目标位置推送数据
        gst_app_src_end_of_stream(src);
        gsize remaining = ctx->total_bytes - ctx->current_pos;
        if (remaining > 0) {
            GstBuffer* buffer = gst_buffer_new_allocate(NULL, remaining, NULL);
            gst_buffer_fill(buffer, 0, ctx->pcm_data + ctx->current_pos, remaining);
            gst_app_src_push_buffer(src, buffer);
            gst_app_src_end_of_stream(src);
        }
        return TRUE;
    }
    
    // 在获取appsrc后绑定回调
    AudioDataCtx ctx = {pcm_data, total_bytes, 0};
    g_signal_connect(appsrc, "seek", G_CALLBACK(appsrc_seek_cb), &ctx);
    
  3. 正确时机查询时长
    需等待pipeline进入PAUSED或PLAYING状态后再查询时长:

    gst_element_set_state(pipeline, GST_STATE_PLAYING);
    // 等待状态切换完成
    gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
    
    gint64 duration;
    if (gst_element_query_duration(pipeline, GST_FORMAT_TIME, &duration)) {
        std::cout << "Duration: " << duration << std::endl;
    }
    

问题2:gst_stream_volume_set_volume无法调节音量

原因

你错误地将整个pipeline转换为GstStreamVolume调用接口,而GstStreamVolume必须作用于管道中实际的volume元素,而非整个管道对象。

解决方法

直接使用你之前获取的volume元素调用接口:

// 复用已获取的volume元素
gst_stream_volume_set_volume(volume, GST_STREAM_VOLUME_FORMAT_CUBIC, volumelevel);

注:GST_STREAM_VOLUME_FORMAT_CUBIC格式下,取值范围为0(静音)~1(原音量),大于1会放大音量但可能导致失真。

完整修正后的核心代码片段

#include <gst/gst.h>
#include <iostream>

typedef struct {
    guint8 *pcm_data;
    gsize total_bytes;
    gsize current_pos;
} AudioDataCtx;

static gboolean appsrc_seek_cb(GstAppSrc *src, guint64 offset, GstSeekType type, guint64 stop, GstSeekType stop_type, GstAppSeekFlags flags, gpointer user_data) {
    AudioDataCtx *ctx = (AudioDataCtx*)user_data;
    gint64 target_frames = (offset * 44100) / GST_SECOND;
    ctx->current_pos = target_frames * 4;

    gst_app_src_end_of_stream(src);
    gsize remaining = ctx->total_bytes - ctx->current_pos;
    if (remaining > 0) {
        GstBuffer* buffer = gst_buffer_new_allocate(NULL, remaining, NULL);
        gst_buffer_fill(buffer, 0, ctx->pcm_data + ctx->current_pos, remaining);
        gst_app_src_push_buffer(src, buffer);
        gst_app_src_end_of_stream(src);
    }
    return TRUE;
}

int main(int argc, char *argv[]) {
    gst_init(&argc, &argv);

    GstElement* pipeline = gst_parse_launch("appsrc name=src ! rawaudioparse format=pcm num-channels=1 sample-rate=44100 pcm-format=GST_AUDIO_FORMAT_S32LE ! volume name=vol ! audioconvert ! audioresample ! autoaudiosink", NULL);
    GstElement* appsrc = gst_bin_get_by_name(GST_BIN(pipeline), "src");
    GstStreamVolume* volume = (GstStreamVolume*)gst_bin_get_by_name(GST_BIN(pipeline), "vol");

    // 假设已加载PCM数据到pcm_data,总字节数total_bytes
    guint8 *pcm_data = ...;
    gsize total_bytes = ...;
    AudioDataCtx ctx = {pcm_data, total_bytes, 0};

    // 配置appsrc
    g_object_set(appsrc, "format", GST_FORMAT_TIME, "do-timestamp", FALSE, NULL);
    gint bytes_per_frame = 1 * 4;
    gint64 total_frames = total_bytes / bytes_per_frame;
    gint64 total_duration = (total_frames * GST_SECOND) / 44100;
    gst_app_src_set_duration(GST_APP_SRC(appsrc), total_duration);
    g_signal_connect(appsrc, "seek", G_CALLBACK(appsrc_seek_cb), &ctx);

    // 推送初始数据
    GstBuffer* buffer = gst_buffer_new_allocate(NULL, total_bytes, NULL);
    gst_buffer_fill(buffer, 0, pcm_data, total_bytes);
    gst_app_src_push_buffer(GST_APP_SRC(appsrc), buffer);
    gst_app_src_end_of_stream(GST_APP_SRC(appsrc));

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

    // 查询时长
    gint64 duration;
    if (gst_element_query_duration(pipeline, GST_FORMAT_TIME, &duration)) {
        std::cout << "Duration: " << duration << std::endl;
    }

    // 调节音量为0.5倍
    gst_stream_volume_set_volume(volume, GST_STREAM_VOLUME_FORMAT_CUBIC, 0.5);

    // 等待播放结束
    GstBus* bus = gst_element_get_bus(pipeline);
    gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_EOS | GST_MESSAGE_ERROR);

    // 释放资源
    gst_object_unref(bus);
    gst_object_unref(volume);
    gst_object_unref(appsrc);
    gst_object_unref(pipeline);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:47:27