系列文章目录
Gstreamer中获取帧数据的方式
gstreamer中如何使用probe(探针)获取帧数据
gstreamer拉流rtsp使用appsink获取帧数据(预览+截图)
gstreamer中如何使用fakesink获取帧数据(预览+截图)
前言
前面已经介绍过多种方式实现在显示预览的同时如何获取帧数据用来截图。本文就继续研究在此前的基础上利用tee如何实现可控的录像的支路。
Tee
Tee有一个 sink pad 而没有初始的 source pads:需要请求tee它们然后 添加它们。通过这种方式,输入流可以被复制任意次。缺点是使用 Request Pads 链接元素不像链接 Always Pads 那样自动连接。同时tee的支路阻塞会响应其他所有支路,导致整个管道阻塞。我们可以利用Request Pad特性就可以实现下游支路有需求时连接tee,不需要时可以断开tee。这样就可以实现控制单个支路的同时不影响其他支路。下面来看演示。
管道结构
-
录像前
-
开始录像(tee和录像支路连接)
-
录像结束后(tee只断开录像支路)
这里演示的管道截图功能使用的是前面介绍的fakesink方式。几种方式截图都是可行的。使用fakesink和appsink会在tee后面多一条支路。而使用probe的方式可以在pipeline中我们想要的element位置任意选择。
为了方便演示还是使用videotestsrc作为测试源。通过管道图能够看到开始录像后tee多了一条用来录像的支路。录像支路的连接和断开都不会影响其他支路。从管道图可以知道录像使用先编码再封装的方式保存为视频文件。gstreamer插件中提供了多种视频的编码方式。有h264、h265、vp8、vp9等等可供选择。下面介绍tee如何实现动态连接和断开。
qt程序界面
除了录制和截图功能外。还有可选择播放源的分辨率以及暂停和播放功能。录像按钮S_Record使用SIGNAL(toggled(bool))方式触发,开始和结束录制由同一个按钮的两种状态控制。为了避免意外,录制时关闭更改分辨率和暂停功能。
Tee的request方式连接和断开(录像)
代码如下(示例):
typedef struct _CustomData {
GstElement *pipeline;
GstElement *vsrc, *capsfilter, *tee, *play_queue, *vconvert, *scale, *vsink ;
GstElement r*record_queue, *h264enc, *recordcapsfilte *h264parse, *qtmux, *filesink;
GstElement *fake_queue, *fconvert, *fakecapsfilter, *fsink;
GstPad *tee_record_pad;
GstPad *queue_record_pad;
GstPad *fsink_pad;
GstCaps *filter_caps,*filter_caps2;
GstCaps *x264enc_caps;
GstMapInfo map_info;
int Width = 640, Height = 480;
// 截图和录像标志
bool capture_flag = false;
bool record_flag = false;
} CustomData;
//下面用的相关变量都在结构体里创建
CustomData *date;
void PlayerWindow::onRecordClicked(bool checked) {
//这里使用qt中的按键slots函数
if(checked){
.........................................................
.........................................................
.........................................................
//这里只列出关键代码 完整代码可见qt工程链接
//先将支路element放入bin中
gst_bin_add_many(GST_BIN (date->pipeline), date->record_queue, date->h264enc, date->recordcapsfilter,
date->h264parse, date->qtmux, date->filesink, NULL);
gst_element_sync_state_with_parent(date->record_queue);
// link element
if(gst_element_link_many(date->record_queue, date->h264enc, date->recordcapsfilter,
date->h264parse, date->qtmux, date->filesink, NULL) != TRUE){
g_printerr ("Elements could not be linked.\n");
gst_object_unref (date->pipeline);
return ;
}
//这里是关键
date->tee_record_pad = gst_element_get_request_pad (date->tee, "src_%u");
g_print ("Obtained request pad %s for capture branch.\n", gst_pad_get_name (date->tee_record_pad));
date->queue_record_pad = gst_element_get_static_pad (date->record_queue, "sink");
if(gst_pad_link (date->tee_record_pad, date->queue_record_pad) != GST_PAD_LINK_OK){
g_printerr ("Tee capture could not be linked.\n");
gst_object_unref (date->pipeline);
return ;
}
gst_object_unref (date->queue_record_pad);
g_print ("start record \n");
date->record_flag = true;
gst_element_set_state (date->pipeline, GST_STATE_PLAYING);
}else{
//stop record
.........................................................
.........................................................
.........................................................
//这里只列出关键代码 完整代码可见qt工程链接
// 发送eos通知支路的qtmux视频封装结束 。异常结束会导致视频文件没有结尾导致文件不能正常播放
if(FALSE == gst_element_send_event(date->qtmux, gst_event_new_eos()))
{
g_print("send eos fail\n");
}
//要结束录像则先断开(tee的srcpad)与录像支路的(queue的sinkpad)连接
date->queue_record_pad = gst_element_get_static_pad (date->record_queue, "sink");
if(gst_pad_unlink(date->tee_record_pad, date->queue_record_pad) != TRUE){
g_printerr ("Tee capture could not be unlinked.\n");
gst_object_unref (date->pipeline);
return ;
}
gst_object_unref (date->queue_record_pad);
gst_element_release_request_pad (date->tee, date->tee_record_pad);
gst_object_unref (date->tee_record_pad); //销毁teepad
.........................................................
.........................................................
.........................................................
//这里只列出关键代码 完整代码可见qt工程链接
}
}
总结
通过tee机制的学习和了解后,不仅仅只是实现可控录像的功能。在其他媒体流中能用到它的地方有很多。比如前面说到的显示的同时tee一条支路appsink来将数据写入文件。或者播放音乐和连接可视化模块。那了解tee如何动态连接后我们就能做更多灵活的处理。