需求:现在根据推理事件触发保存mp4事件,但这样保存的视频会延后事件,所以需要先缓存一定时间的数据,当事件触发时就能够保存事件前后的数据了。
该如何先缓存数据呢?
1.将数据通过数据回调将数据拷贝出来然后丢入缓存队列(当达到限制时丢旧缓存数据)自己来控制。这种后续实现操作比较多。
2.通过原有的queue元件实现缓存,如何实现呢?通过研究发现queue具备以下max-size-bytes(最大内存大小限制)、max-size-time(最大时间限制)、max-size-buffers(最大帧数据个数限制)三个最大值设置项三者都有默认值,当达到一个条件时就不会继续缓存数据,会导致缓存数据量没到预期值,所以当选择一个设置时,其它必须设置为0禁用,不然无效。leaky(在元件输入丢(丢新数据)还是输出丢(丢旧数据))属性,这些设置项就能够使queue成为一个固定大小的丢旧数据的循环队列,当通过tee的复制特性将保存mp4的分支元件加到管道并设置为playing状态时数据就会流通,将存储设置大小的数据量到queue并设置leaky属性使用新数据刷新队列,不需要等到事件到来时从当前数据开始存储,所以需要阻塞当前管道的运行,直到存事件触发。
那么如何设置阻塞呢?
之前没接触过gstreamer后面发现gstpad存在一个阻塞回调函数,这样数据就不会继续往下传输了。所以当元件加入管道连接成功后设置为playing状态时,直接将queue的src pad获取到并设置阻塞回调,就可以了,当事件触发时删除阻塞回调即可。
示例:
//在回调返回的时候令探针阻塞数据流。这叫做阻塞探针,可以指定GST_PAD_PROBE_TYPE_BLOCK标志来激活。
//你可以同时使用其他标志来选定某种活动发生时阻塞数据流。移除探针或回调返回GST_PAD_PROBE_REMOVE时,
//阻塞的pad会重新疏通。你可以通过令回调返回GST_PAD_PROBE_PASS仅通过现在阻塞的项目,这会在下一个项目上再次阻塞
//。阻塞探针用于临时阻塞你将要解除链接或没有链接的探针。
//如果数据流没有阻塞,管道会由于数据推入一个没有链接的帕的而进入错误状态。我们会探究如何使用阻塞探针来部分地预滚管道
//阻塞pad回调函数
GstPadProbeReturn AIStreamSaveToMp4::pad_probe_cb(GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
/*GstPad *srcpad, *sinkpad;*/
AIStreamSaveToMp4 *pAIStreamSaveToMp4 = (AIStreamSaveToMp4*)user_data;
/*GST_DEBUG_OBJECT(pad, "pad is blocked now");*/
//dzlog_info("pad is blocked now cb id:%d", info->id);
return GST_PAD_PROBE_OK;
}
int AIStreamSaveToMp4::Init(int nStreamID)
{
dzlog_info("StreamMp4 begin streamID:%d", nStreamID);
char file_name[128];
memset(file_name, 0, sizeof(file_name));
time_t nowtime = time(NULL);
sprintf(file_name, "/home/..../test%d_%ld.mp4", nStreamID, nowtime);
gchar elem_name[50] = { 0 };
gchar tee_src_pad_name[16] = { };
g_snprintf(elem_name, sizeof(elem_name), "file-sink%d", nStreamID);
GstElement *file_sink = gst_element_factory_make("filesink", elem_name);
g_snprintf(elem_name, sizeof(elem_name), "mp4_mux%d", nStreamID);
GstElement *mp4mux = gst_element_factory_make("mp4mux", elem_name);
g_snprintf(elem_name, sizeof(elem_name), "record_queue%d", nStreamID);
GstElement *record_queue = gst_element_factory_make(NVDS_ELEM_QUEUE, elem_name);
g_object_set(G_OBJECT(file_sink), "location", file_name, NULL);
g_snprintf(elem_name, sizeof(elem_name), "filter%d", nStreamID);
GstElement * caps_mp4mux_filter = gst_element_factory_make("capsfilter", elem_name);
GstCaps * caps_to_mp4mux = gst_caps_new_simple("video/x-h264",
"stream-format", G_TYPE_STRING, "avc",
NULL);
g_object_set(caps_mp4mux_filter, "caps", caps_to_mp4mux, NULL);
gst_caps_unref(caps_to_mp4mux);
gst_bin_add_many(GST_BIN(m_pipeline), record_queue, caps_mp4mux_filter, mp4mux, file_sink, NULL);
gst_element_sync_state_with_parent(record_queue);
if (gst_element_link_many(record_queue, caps_mp4mux_filter, mp4mux, file_sink, NULL) != TRUE)
{
g_printerr("GstData.mp4mux could not link GstData.file_sink\n");
return FALSE;
}
g_snprintf(elem_name, sizeof(elem_name), "tee%d", nStreamID);
GstElement *tee = gst_bin_get_by_name(GST_BIN(m_pipeline), elem_name);
设置为0时代表禁用,此处必须要禁用这些参数才能达到效果(因为最大值,是以先达到的条件为准)
g_object_set(G_OBJECT(record_queue), "max-size-bytes", 0, NULL);
g_object_set(G_OBJECT(record_queue), "max-size-time", 0, NULL);
g_object_set(G_OBJECT(record_queue), "max-size-buffers", 200, NULL);
g_object_set(G_OBJECT(record_queue), "leaky", 2, NULL); //设置丢弃旧缓冲区数据
g_snprintf(tee_src_pad_name, 15, "src_%d", nRecodePadID);
GstPad *tee_src_recodepad = gst_element_get_static_pad(tee, tee_src_pad_name);
if (NULL == tee_src_recodepad)
{
dzlog_error("get tee_src_recodepad failed");
return FALSE;
}
GstPad *record_queue_sink_pad = gst_element_get_static_pad(record_queue, "sink");
if (NULL == record_queue_sink_pad)
{
dzlog_error("get record_queue record_queue_sink_pad failed");
return FALSE;
}
// link the pads
if (gst_pad_link(tee_src_recodepad, record_queue_sink_pad) != GST_PAD_LINK_OK) {
gchar *n1 = gst_pad_get_name(tee_src_recodepad);
gchar *n2 = gst_pad_get_name(record_queue_sink_pad);
dzlog_error("Failed to link '%s' and '%s'", n1, n2);
g_free(n1);
g_free(n2);
return FALSE;
// handle errors in any way you see fit (maybe exit)
}
// don't forget to unref your pad pointers
gst_object_unref(tee_src_recodepad);
gst_object_unref(record_queue_sink_pad);
GstPad *record_queue_src_pad = gst_element_get_static_pad(record_queue, "src");
if (!record_queue_src_pad)
dzlog_info("Unable to get src pad\n");
else
{
m_vecBlockid[nStreamID] = gst_pad_add_probe(record_queue_src_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
pad_probe_cb, this, NULL);//设置阻塞探针回调
}
gst_object_unref(record_queue_src_pad);
dzlog_info("StreamMp4 end ");
return TRUE;
}
int AIStreamSaveToMp4::StartMp4(int nStreamID)
{
gchar elem_name[50] = { 0 };
g_snprintf(elem_name, sizeof(elem_name), "record_queue%d", nStreamID);
GstElement *record_queue = gst_bin_get_by_name(GST_BIN(m_pipeline), elem_name);
g_snprintf(elem_name, sizeof(elem_name), "file-sink%d", nStreamID);
GstElement *file_sink = gst_bin_get_by_name(GST_BIN(m_pipeline), elem_name);
if (NULL == file_sink)
{
dzlog_error("file_sink is null");
return FALSE;
}
GstPad *record_queue_src_pad = gst_element_get_static_pad(record_queue, "src");
if (!record_queue_src_pad)
dzlog_info("Unable to get src pad\n");
else
{
gst_pad_remove_probe(record_queue_src_pad, m_vecBlockid[nStreamID]); //删除阻塞探针回调
}
gst_object_unref(record_queue_src_pad);
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
return TRUE;
}