草庐IT

gstreamer中tee如何实现动态增减支路(预览+截图+录像)

唯獨你没懂 2023-07-12 原文

系列文章目录

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。这样就可以实现控制单个支路的同时不影响其他支路。下面来看演示。

管道结构

  1. 录像前

  2. 开始录像(tee和录像支路连接)

  3. 录像结束后(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如何动态连接后我们就能做更多灵活的处理。

附 ubuntu20.04 qt5.14 工程链接

gstreamer+qt实现照相机功能(显示预览+可控录像+拍照截图)

有关gstreamer中tee如何实现动态增减支路(预览+截图+录像)的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐