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

Mac端循环创建webrtcbin触发heap-use-after-free段错误

问题现象

同一份基于GStreamer webrtcbin 实现多源媒体流接收的代码,Windows平台可正常运行,Mac平台下for循环创建webrtcbin数量超过1个时就会触发段错误,报错类型为 heap-use-after-free(堆内存释放后重用)。

问题根因

代码存在多处内存管理不规范问题,Windows平台C运行库内存回收策略宽松,悬空指针访问时对应内存内容未被篡改,所以暂时表现为运行正常;Mac平台libc内存回收机制更激进,释放的内存会被快速标记为不可访问或重新分配给其他逻辑,因此会稳定触发内存访问错误,具体问题点如下:

  • 无效堆内存分配:循环中先后给peerid、webrtc指针调用g_malloc0分配指针大小的堆内存,紧接着就用API返回值覆盖了这两个指针,之前分配的内存直接泄漏,没有任何实际作用。
  • 悬空指针风险:json_array_get_string_element返回的字符串由JsonArray对象内部持有,所有权没有转移给调用方,JsonArray生命周期变动或循环迭代过程中该字符串内存会被自动释放,后续异步回调中再访问这个地址就会触发堆内存重用错误。
  • 回调参数内存生命周期无管理:给信号回调传入的g_strdup(peerid)分配的字符串没有绑定释放逻辑,既会造成内存泄漏,也可能因为内存被提前回收出现非法访问。
  • GStreamer引用计数不匹配:gst_bin_get_by_name返回的webrtc元素会自动新增一个引用计数,代码中使用完该指针后没有调用gst_object_unref匹配释放,长期运行会造成引用泄漏,打乱进程内存布局,放大内存问题触发概率。
  • 临时资源未释放:gst_caps_from_string创建的caps、Json对象、动态生成的SDP字符串等临时资源用完后没有对应释放逻辑,会造成额外内存泄漏。
修复方案

针对上述问题逐一修正,严格匹配GLib/GStreamer的内存管理和引用计数规则:

  1. 删除循环中对peerid、webrtc无意义的g_malloc0调用,两个变量仅作为指针存储API返回值即可,不需要额外在堆上分配指针大小的内存。
  2. 从JsonArray拿到peerid字符串后,立刻调用g_strdup拷贝一份独立持有所有权,作为信号回调的传入参数;使用g_signal_connect_data替代g_signal_connect,传入g_free作为闭包销毁回调,信号断开时自动释放拷贝的字符串内存,既避免泄漏也保证回调执行期间内存有效。
  3. 所有通过GStreamer/GLib API获取的带引用计数的对象、动态分配的临时资源,用完后严格匹配对应的释放接口,保证引用计数平衡。

修正后的核心代码如下:

static void on_offer_created(GstPromise *promise, gpointer data)
{
  GstWebRTCSessionDescription *offer = NULL;
  const GstStructure *reply;
  gchar *sdp_string;
  gchar *json_string;
  reply = gst_promise_get_reply(promise);
  GstElement *webrtc;
  webrtc = gst_bin_get_by_name(GST_BIN(gst_pipe), (gchar *)data);
  g_assert_nonnull(webrtc);
  gst_structure_get(reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
  gst_promise_unref(promise);

  g_signal_emit_by_name(webrtc, "set-local-description", offer, NULL);

  sdp_string = gst_sdp_message_as_text(offer->sdp);
  g_print(" offer created:\n%s\n", sdp_string);
  JsonObject *sdp_json = json_object_new();
  json_object_set_string_member(sdp_json, "type", "offer");
  json_object_set_string_member(sdp_json, "sdp", sdp_string);
  json_object_set_string_member(sdp_json, "from", ourid);
  json_object_set_string_member(sdp_json, "to", (gchar *)data);

  json_string = get_string_from_json_object(sdp_json);

  soup_websocket_connection_send_text(connection, json_string);
  g_print("sending offer to %s",(gchar *)data);

  // 匹配释放所有临时资源
  gst_webrtc_session_description_free(offer);
  g_free(sdp_string);
  g_free(json_string);
  json_object_unref(sdp_json);
  // 释放gst_bin_get_by_name新增的引用,保证引用计数平衡
  gst_object_unref(webrtc);
}

static void on_negotiation_needed(GstElement *webrtc, gpointer user_data)
{
  GstPromise *promise;
  g_print("negotiation needed\n");
  promise = gst_promise_new_with_change_func(on_offer_created, user_data, NULL);
  g_signal_emit_by_name(webrtc, "create-offer", NULL, promise);
}

// 修正后的webrtcbin创建循环
for (int i = json_array_get_length(chain)-1; i >= 0; i--)
{
  const gchar *tmp_peerid = json_array_get_string_element(chain, i);
  // 独立拷贝peerid,绑定到信号回调生命周期
  gchar *peerid = g_strdup(tmp_peerid);
  // 直接接收元素工厂返回的指针,删除无意义的malloc
  GstElement *webrtc = gst_element_factory_make("webrtcbin", peerid);
  g_assert(webrtc != NULL);

  gst_bin_add_many(GST_BIN(gst_pipe), webrtc, NULL);
  GstCaps *caps = gst_caps_from_string(TRANS_AUDIO_CAPS);
  g_signal_emit_by_name(webrtc, "add-transceiver", GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, caps, NULL);
  // 用完caps立刻释放
  gst_caps_unref(caps);

  g_signal_connect(webrtc, "pad-added", G_CALLBACK(on_incoming_stream), &peer_delay);
  // 带销毁回调注册信号,信号销毁时自动g_free释放peerid内存
  g_signal_connect_data(webrtc, "on-negotiation-needed", G_CALLBACK(on_negotiation_needed), 
                        peerid, (GClosureNotify)g_free, 0);
  g_signal_connect_data(webrtc, "on-ice-candidate", G_CALLBACK(send_ice_candidate_message), 
                        g_strdup(peerid), (GClosureNotify)g_free, 0);
  g_assert_true(gst_element_sync_state_with_parent(webrtc));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 17:06:26