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实现思路:
- 链接
osxaudiosource device=97 ! audioconvert ! audioresample ! capsfilter ! deinterleave - 单独链接
interleave ! encoder ! sink - 在
deinterleave的pad-added回调中,将其src_4和src_5Pad连接到interleave通过gst_element_request_pad_simple(sink, "sink_%u")创建的sink_0、sink_1Pad
遇到的问题:
- 出现警告
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
相关产品推荐
相关产品推荐

