阅读本博客需要有对gstreamer基础知识的了解,这里给出学习链接:
在做项目时,需要增加对多路mipi csi摄像头图像获取的支持,以往使用的ffmpeg无法支持该转接板转接的mipi csi摄像头。于是便有了本博客,权当记录啦~
使用gst-lauch-1.0验证管道连接和输出帧是否正常:
gst-lauch-1.0 v4l2src device=/dev/video3 ! video/x-raw,format=NV16,width=1280,height=720,framerate=5/1 ! queue ! videoconvert ! waylandsink
可以看到这个pipeline由4个element元件构成,每个element都实现各自的功能:
其中:
“device=/dev/video3”紧跟着v4l2src意为给v4l2src组件成员device赋值,用于指定摄像头设备路径。
“video/x-raw,format=NV16,width=1280,height=720,framerate=5/1”意为通过Cap来指定数据类型
需要注意的是我手上的硬件转接板获取到的图像数据是NV16格式的,也就是YUV422SP格式,不同硬件可能格式不一样。
最后的waylandsink可以换成appsink,用于在程序中我们自己获取帧,而不是通过waylandsink来进行显示,该例子只用于进行读取摄像头可行性的验证。
根据上面验证了读取该摄像头的可行性并确定了各element元件进行实现:
伪代码如下:
/*
* 初始化gstreamer
* 若无初始化值可以传入NULL,NULL
* 一般情况下初始化不需要参数
*/
gst_init (*argc, **argv[]);
/*
* 使用gstreamer工厂创建各个element元件
* 函数第一个参数为元件型别名,gstreamer根据该元件型别名成查找对应元件
* 第二个参数为用户自定义的该元件的名称
*/
GstElement * v4l2src = gst_element_factory_make ("v4l2src", "source");
GstElement * queue = gst_element_factory_make("queue","Queue");
GstElement * videoconvert = gst_element_factory_make("videoconvert","Videoconvert");
GstElement * appsink = gst_element_factory_make ("appsink","sink");
//创建管道头,输入参数为自定义的管道名称
GstElement * pipeline = gst_pipeline_new ("mipi-pipeline");
/*
* 连接各个element元件前需要先确定创建出来的指针是否为空
* 如果为空说明gstreamer未找到对应的element元件
* 判断是否创建成功略
*/
//调用g_object_set来设置v4l2src的device组件指定摄像头路径
std::string capturePath = "...";
g_object_set (v4l2src, "device", capturePath.data() , NULL);
/*
* 当前当前致远M1808上使用的N4转接板接AHD摄像头获取到的数据规格为
* video/x-raw,format=NV16,width=1280,height=720,framerate=5/1
* 其他转接板接AHD摄像头获取到的数据规格暂不明晰,该数据规格必须准确,否则会导致gstreamer初始化出错
*/
std::string capInfo = "video/x-raw,format=NV16,width=1280,height=720,framerate=5/1";
gchar * video_caps_text = g_strdup_printf (capInfo.data());
//gst_caps_from_string通过Cap来指定数据类型,设置v4l2src输出格式
GstCaps * video_caps = gst_caps_from_string (video_caps_text);
//video_caps需要进行是否为空判断,这里略
//把appsink的emit-signals属性打开,才能收到管道发出的new-sample信号,同时设置输出视频格式
g_object_set(appsink, "emit-signals", TRUE, "caps", video_caps, NULL);
/*
* 设置监听new-sample和回调函数get_sample,&gstData是传给回调函数的参数
* 注意必须使用静态函数来实现回调:static GstFlowReturn get_sample(GstElement *sink, GstreamerMipiData *gstData);
* 回调里使用&gstData进行与外部通讯
*/
g_signal_connect (appsink, "new-sample", G_CALLBACK (get_sample), &gstData);
//把所有元件放入管道里
gst_bin_add_many (GST_BIN (pipeline), v4l2src,queue,videoconvert,appsink, NULL);
/*
* 连接各个元件,注意顺序不能错
* 参数数量可变,以NULL作为结束标志
* 需要判断返回值是不是false,这里略
*/
gst_element_link_many (v4l2src,queue,videoconvert,appsink, NULL);
//至此管道各元件初始化完成并已完成连接
//并已设定回调函数
/*
* 完成初始化和各个element元件连接和设定appsink new-sample信号的回调后
* 开启循环防止管道被释放掉资源而停止
*/
//开启循环略,当前项目中为开启一个循环判断是否暂停和重启线程
//gst_element_set_state(GstElement* , GST_STATE_*)启动管道开始工作,可设置GST_STATE_PAUSED暂停
//gstreamer中管道状态枚举源码定义:
typedef enum {
GST_STATE_VOID_PENDING = 0,
GST_STATE_NULL = 1,
GST_STATE_READY = 2,
GST_STATE_PAUSED = 3,
GST_STATE_PLAYING = 4
} GstState;
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/*
* gstreamer中,管道的工作状态不能跳变
* GST_STATE_VOID_PENDING: 没有等待状态,这只是作为一个枚举的起始
* GST_STATE_NULL: 默认状态,该状态将会回收所有被该元件占用的资源。
* GST_STATE_READY: 准备状态,元件会得到所有所需的全局资源,这些全局资源将被通过该元件的数据流所使用。
* GST_STATE_PAUSED: 在这种状态下,元件已经对流开始了处理,但此刻暂停了处理。
* GST_STATE_PLAYING: PLAYING 状态除了当前运行时钟外,其它与 PAUSED 状态一模一样。
* 状态切换:
* VOID_PENDING <---> NULL <---> READY <---> PAUSED <---> PLAYING
*/
/*
* 前面提到GsreamerCameraReader::get_sample回调函数
* appsink在收到一帧会发出new-sample信号,此时get_sample会被调用
* get_sample定义为:static GstFlowReturn get_sample (GstElement *sink, GstreamerMipiData *gstData);
* sink可取出帧进行处理 -> gstData为外部指针
*/
GstFlowReturn GsreamerCameraReader::get_sample (GstElement *sink,
GstreamerMipiData *gstData){
//从sink中取帧数据到GstSample * sample
g_signal_emit_by_name (sink, "pull-sample", &gstData->outData.sample);
//从gstData获取图像的宽高信息,这里略
//获取一帧buffer,这个buffer是无法直接用的,它不是char类型
//gstData->outData.buff型别为 GstBuffer *
gstData->outData.buff = gst_sample_get_buffer (gstData->outData.sample);
//把buffer映射到map,就可以通过map.data取到buffer的数据
//gstData->outData.map型别为 GstMapInfo map
//需要判断映射返回是否为true,这里略
gst_buffer_map (gstData->outData.buffer, &gstData->outData.map, GST_MAP_READ)
//这样就取出了一帧NV16格式的像素数据,map.data取到的就是该像素数据,opencv中没有提供NV16转BGR
//由于获取到的是NV16也就是YUV422SP格式数据,opencv中没有转码NV16格式的方法
//需要先将NV16转NV12,再使用CV_YUV2BGR_NV12转成BGR
//先创建NV12格式的cv::Mat用于后续操作,使用cv::create创建一个高为原来3/2,宽不变,通道为1的cv::Mat存储
gstData->outData.nv12Img.create(gstData->outData.height * 3 / 2, gstData->outData.width, CV_8UC1);
//将NV16转NV12
nv16_to_nv12(gstData->outData.map.data , gstData->outData.nv12Img.data , gstData->outData.width , gstData->outData.height);
使用CV_YUV2BGR_NV12将NV12转成BGR
cv::cvtColor(gstData->outData.nv12Img, gstData->outData.bgrImg , CV_YUV2BGR_NV12);
//这样,解析完成的BGR格式的cv::Mat就保存在gstData->outData.bgrImg中
//资源释放、抽帧和返回这里略
}
源码链接:
因篇幅问题不能全部显示,请点此查看更多更全内容