背景
业务需求需要将某些解码后的视频帧保存为图片,大部分情况下图片都是正常的,更换了某些视频流后,在保存的图片顶部就会出现一条绿线,现记录下解决过程。
部分代码如下
解码回调如下,完整代码可参考之前的文章Gstreamer 硬解码Rtsp流及代码实现:
最终排查结果是:
有些相机本身推的视频帧的大小与gstreame解码出来的 width 和 height不匹配导致的。如:gstreamer实际解出来的width = 1920, height = 1080, 但实际上一帧的数据是:1920 * 1088,多出来了8个字节。如果用获取到的width 和 height去计算拷贝帧的大小,在相机推送不标准的情况下就会出现该问题。
采用opencv保存图片,先将YUV转成NV12,再调用cv::imwrite() 。以19201080为例:
修复前,使用 解码得到的width 和 height计算帧大小:预期帧大小为:19201080*3 / 2 = 3,110,400
{ ...... cv::Mat img; // 创建并初始化原始YUV Mat对象 yuvNV12.create(height * 3 / 2, width, CV_8UC1); memcpy(yuvNV12.data, map.data, width * height * 3 / 2); // yuv to img Mat cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12); ...... }
实际帧大小 map.size = 3,133,440:
{ ...... cv::Mat img; // 创建并初始化原始YUV Mat对象 yuvNV12.create((map.size / width), width, CV_8UC1); memcpy(yuvNV12.data, map.data, map.size); // yuv to img Mat cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12); }
GstFlowReturn ReadvideoFrame_callback(GstElement *sink, gpointer user_data) { CustomData *data = (CustomData *)user_data; char video_format[32] = {0}; int framerate[2] = {0}; unsigned long long ts = 0; GstSample *sample; GstBuffer *buffer; GstCaps *caps; GstStructure *s; gint width, height; // 图片的尺寸 // 使用pull-sample拉取视频帧,并映射到map变量,通过map拷贝出frame数据 g_signal_emit_by_name(sink, "pull-sample", &sample); // g_print("new_sample succeeded (type '%d').\n", sample); if (sample) { caps = gst_sample_get_caps(sample); if (!caps) { g_print("gst_sample_get_caps fail\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } s = gst_caps_get_structure(caps, 0); gboolean res; res = gst_structure_get_int(s, "width", &width); // 获取图片的宽 // g_print("width: %d, ", width); res |= gst_structure_get_int(s, "height", &height); // 获取图片的高 // g_print("height: %d \n", height); if (!res) { g_print("gst_structure_get_int fail\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } const char *format = gst_structure_get_string(s, "format"); strcpy(video_format, format); gst_structure_get_fraction(s, "framerate", &framerate[0], &framerate[1]); // 获取视频的一帧buffer,注意,这个buffer是无法直接用的,它不是char类型 buffer = gst_sample_get_buffer(sample); if (!buffer) { g_print("gst_sample_get_buffer fail\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } GstMapInfo map; // 把buffer映射到map,这样我们就可以通过map.data取到buffer的数据 auto rett = gst_buffer_map(buffer, &map, GST_MAP_READ); if (rett) { cv::Mat yuvNV12; cv::Mat img; // 创建并初始化原始YUV Mat对象 // 有问题代码如下: // yuvNV12.create(height * 3 / 2, width, CV_8UC1); // memcpy(yuvNV12.data, map.data, width * height * 3 / 2); //修复如下: yuvNV12.create((map.size / width), width, CV_8UC1); memcpy(yuvNV12.data, map.data, map.size); // yuv to img Mat cv::cvtColor(yuvNV12, img, cv::COLOR_YUV2BGR_NV12); gst_buffer_unmap(buffer, &map); // 解除映射 } else { g_print("gst_buffer_map failed!, %d\n", rett); } // release sample reference gst_sample_unref(sample); } else { g_print("sample is null...\n"); } return GST_FLOW_OK; }