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

MacOS下用GStreamer C API录制双声道音频至WAV的问题

问题描述

在MacOS上使用C++结合GStreamer C API,将Clarret 8pre声卡的两个声道录制为立体声WAV文件(不使用gst_launch工具)。

已通过终端命令实现功能:

gst-launch-1.0 osxaudiosrc device=97 ! audioconvert ! deinterleave name=d interleave name=i ! audioconvert ! wavenc ! filesink location=stereo.wav d.src_4 ! queue ! i.sink_0 d.src_5 ! queue ! i.sink_1

当前API实现思路:

  1. 链接osxaudiosource device=97 ! audioconvert ! audioresample ! capsfilter ! deinterleave
  2. 单独链接interleave ! encoder ! sink
  3. 在deinterleave的pad-added回调中,将其src_4和src_5 Pad连接到interleave通过gst_element_request_pad_simple(sink, "sink_%u")创建的sink_0、sink_1 Pad

遇到的问题:

  • 出现警告WARN interleave interleave.c:319:gst_interleave_set_channel_positions:<interleave> Invalid channel positions, using NONE,尝试用capsfilter解决但未成功
  • 不确定终端命令中的queue元素在API实现中是否需要,以及如何添加
  • 已实现单声道录制,但立体声录制失败

完整代码:

#include <gst/gst.h>

static void on_pad_added(GstElement *element, GstPad *pad, gpointer data);

int main(int argc, char *argv[])
{
    setenv("GST_DEBUG", "3", true);

    // return my_sound_dev::gst_main(reinterpret_cast<GstMainFunc>(my_main), argc, argv);
   GMainLoop *loop;
    GstElement *pipeline, *src, *convert, *deinterleave, *interleave, *resample, *encoder, *sink;
    GstPad *pad, *sinkpad;
    GstCaps *caps;

    gst_init(&argc, &argv);

    src = gst_element_factory_make("osxaudiosrc", "source");
    convert = gst_element_factory_make("audioconvert", "convert");
    resample = gst_element_factory_make("audioresample", "resample");
    deinterleave = gst_element_factory_make("deinterleave", "deinterleave"); 
    GstElement *capsfilter = gst_element_factory_make("capsfilter", "filter");
    caps = gst_caps_from_string("audio/x-raw,channels=20,layout=(string)interleaved");
    //caps = gst_caps_from_string("audio/x-raw,channels=2,channel-mask=(bitmask)0x3,layout=(string)interleaved");
    g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);

    interleave = gst_element_factory_make("interleave", "interleave");
    encoder = gst_element_factory_make("wavenc", "encoder");
    sink = gst_element_factory_make("filesink", "sink");

    g_object_set(src, "device", 97, NULL);
    pipeline = gst_pipeline_new("audio-pipeline");

    gst_bin_add_many(GST_BIN(pipeline), src, deinterleave, interleave, convert, resample, encoder, sink, capsfilter, NULL);

    g_object_set(sink, "location", "/Users/ufk/stereo.wav", NULL);

    if (!gst_element_link_many(src, convert, resample, capsfilter, deinterleave, NULL))
    {
        g_error("Failed to link elements");
        return -1;
    }

    if(!gst_element_link(interleave, encoder)){
        g_error("Failed to link interleave and encoder");
        return -1;
    }


    if(!gst_element_link(encoder, sink)){
        g_error("Failed to link encoder and sink");
        return -1;
    }

    loop = g_main_loop_new(NULL, FALSE);

    g_signal_connect(deinterleave, "pad-added", G_CALLBACK(on_pad_added), interleave);

    if(gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE){
        g_error("Could not set pipeline to playing state");
        return -1;
    }

    g_main_loop_run(loop);

    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));
    g_main_loop_unref(loop);


    return 0;
}

void print_pads(GstElement* element) {
    GValue item = G_VALUE_INIT;
    gboolean done = FALSE;

    GstIterator* it = gst_element_iterate_pads(element);

    while (!done) {
        switch (gst_iterator_next(it, &item)) {
        case GST_ITERATOR_OK: {
                GstPad *pad = GST_PAD(g_value_get_object(&item));
                g_print ("pad name: %s\n", GST_PAD_NAME(pad));
                g_value_reset(&item);
                break;
        }
        case GST_ITERATOR_RESYNC:
            gst_iterator_resync(it);
            break;
        case GST_ITERATOR_ERROR:
        case GST_ITERATOR_DONE:
          done = TRUE;
            break;
        }
    }
    g_value_unset(&item);
    gst_iterator_free(it);
}

void on_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
    char *name;
    GstPad *sinkpad;
    GstElement *queue = gst_element_factory_make("queue", "queue");
    GstElement *sink = (GstElement *)data;

    name = gst_pad_get_name(pad);

    if(g_strcmp0(name, "src_4") == 0){
        sinkpad = gst_element_request_pad_simple(sink, "sink_%u");
    if (!sinkpad)
    {
        g_error("could not create sink for src_4");
    }

        if (gst_pad_is_linked(sinkpad))
        {
            MoLog::fatal() << "sinkpad already linked";
        }
        if (gst_pad_is_linked(pad))
        {
            MoLog::fatal() << "new_pad already linked";
        }
        if (!gst_pad_can_link(pad, sinkpad))
        {
            MoLog::error() << "can't link new_pad to sinkpad";

            GstCaps *new_pad_caps = gst_pad_get_current_caps(pad);
            GstCaps *sinkpad_caps = gst_pad_get_pad_template_caps(sinkpad);
            if (!gst_caps_can_intersect(new_pad_caps, sinkpad_caps)) {
                MoLog::fatal() << "Caps are not compatible";
            } else
            {
                MoLog::info() << "caps can intersect";
            }
            gst_caps_unref(new_pad_caps);
            gst_caps_unref(sinkpad_caps);
        }
        GstPadLinkReturn ret = gst_pad_link(pad, sinkpad);
        if (ret != GST_PAD_LINK_OK){
            print_pads(sink);
            g_error("Error linking pad src_4: %d",ret);
        } else
        {
            g_print("src_4 linked successfully!");
        }

        gst_object_unref(sinkpad);
    }

    else if(g_strcmp0(name, "src_5") == 0){
        sinkpad = gst_element_request_pad_simple(sink, "sink_%u");
        if (!sinkpad)
        {
            g_error("could not create sink for src_5");
        }

        if ((gst_pad_link(pad, sinkpad)) != GST_PAD_LINK_OK){
            g_error("Error linking pad src_5");
        } else
        {
            g_print("successfully linked src_5\n");
        }

        gst_object_unref(sinkpad);
    }

g_free(name);
}
解决建议

1. 修复CapsFilter配置,解决声道位置警告

当前capsfilter配置的channels=20不符合需求,需明确指定立体声的声道位置和格式,替换为:

caps = gst_caps_from_string("audio/x-raw,channels=2,channel-positions=(int)<FRONT_LEFT,FRONT_RIGHT>,layout=(string)interleaved");

该配置会明确告知interleave元素声道的物理位置,直接解决无效声道位置的警告。同时可去掉audioresample,若声卡输出格式已符合需求,无需额外重采样。

2. 正确添加Queue元素

终端命令中的queue是必需的,用于提供数据流缓冲,避免线程阻塞。API中需按以下逻辑添加:

  • 在on_pad_added回调中,创建queue后必须先将其添加到管道,再进行Pad链接
  • 修改后回调逻辑:
void on_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
    char *name = gst_pad_get_name(pad);
    GstElement *interleave = (GstElement *)data;
    GstPad *sinkpad;
    GstElement *queue = gst_element_factory_make("queue", NULL); // 自动生成唯一名称

    // 将queue添加到管道并同步状态
    gst_bin_add(GST_BIN(gst_element_get_parent(element)), queue);
    gst_element_sync_state_with_parent(queue);

    if(g_strcmp0(name, "src_4") == 0){
        sinkpad = gst_element_request_pad_simple(interleave, "sink_0"); // 明确指定sink_0,保证声道顺序
        if (!sinkpad) {
            g_error("could not create sink_0 for interleave");
        }
        // 链路:pad -> queue.sink -> queue.src -> interleave.sink_0
        gst_pad_link(pad, gst_element_get_static_pad(queue, "sink"));
        gst_pad_link(gst_element_get_static_pad(queue, "src"), sinkpad);
        gst_object_unref(sinkpad);
        g_print("src_4 linked with queue to interleave sink_0\n");
    }
    else if(g_strcmp0(name, "src_5") == 0){
        sinkpad = gst_element_request_pad_simple(interleave, "sink_1"); // 明确指定sink_1
        if (!sinkpad) {
            g_error("could not create sink_1 for interleave");
        }
        gst_pad_link(pad, gst_element_get_static_pad(queue, "sink"));
        gst_pad_link(gst_element_get_static_pad(queue, "src"), sinkpad);
        gst_object_unref(sinkpad);
        g_print("src_5 linked with queue to interleave sink_1\n");
    }

    g_free(name);
    gst_object_unref(queue);
}

3. 对齐终端命令的管道链路

和终端命令保持一致,在interleave之后添加audioconvert,确保输出格式能被wavenc正确处理:

// 新增convert2元素
GstElement *convert2 = gst_element_factory_make("audioconvert", "convert2");
// 将convert2添加到管道
gst_bin_add(GST_BIN(pipeline), convert2);
// 修改链路
if(!gst_element_link_many(interleave, convert2, encoder, sink, NULL)){
    g_error("Failed to link interleave -> convert -> encoder -> sink");
    return -1;
}

4. 调试技巧

  • 提高GST_DEBUG级别到4或5,获取更详细的数据流日志:setenv("GST_DEBUG", "4", true);
  • 在回调中打印Pad的Caps信息,确认数据流格式是否正确:
GstCaps *pad_caps = gst_pad_get_current_caps(pad);
g_print("Pad %s caps: %s\n", name, gst_caps_to_string(pad_caps));
gst_caps_unref(pad_caps);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:35:55