Skip to content

Commit 9d61b1e

Browse files
committed
videoio(gstreamer): rework retrieveFrame() to handle strides
1 parent 9d89edf commit 9d61b1e

File tree

2 files changed

+151
-68
lines changed

2 files changed

+151
-68
lines changed

modules/videoio/src/cap_gstreamer.cpp

Lines changed: 138 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,20 @@ class GSafePtr
177177
GSafePtr& operator=(const T*); // = disabled
178178
};
179179

180+
class ScopeGuardGstMapInfo
181+
{
182+
GstBuffer* buf_;
183+
GstMapInfo* info_;
184+
public:
185+
ScopeGuardGstMapInfo(GstBuffer* buf, GstMapInfo* info)
186+
: buf_(buf), info_(info)
187+
{}
188+
~ScopeGuardGstMapInfo()
189+
{
190+
gst_buffer_unmap(buf_, info_);
191+
}
192+
};
193+
180194
} // namespace
181195

182196
/*!
@@ -300,7 +314,6 @@ class GStreamerCapture CV_FINAL : public IVideoCapture
300314
static void newPad(GstElement * /*elem*/, GstPad *pad, gpointer data);
301315

302316
protected:
303-
bool determineFrameDims(CV_OUT Size& sz, CV_OUT gint& channels, CV_OUT bool& isOutputByteBuffer);
304317
bool isPipelinePlaying();
305318
void startPipeline();
306319
void stopPipeline();
@@ -369,72 +382,68 @@ bool GStreamerCapture::grabFrame()
369382
bool GStreamerCapture::retrieveFrame(int, OutputArray dst)
370383
{
371384
if (!sample)
385+
{
372386
return false;
373-
Size sz;
374-
gint channels = 0;
375-
bool isOutputByteBuffer = false;
376-
if (!determineFrameDims(sz, channels, isOutputByteBuffer))
377-
return false;
387+
}
378388

379-
// gstreamer expects us to handle the memory at this point
380-
// so we can just wrap the raw buffer and be done with it
381-
GstBuffer* buf = gst_sample_get_buffer(sample); // no lifetime transfer
382-
if (!buf)
383-
return false;
384-
GstMapInfo info = {};
385-
if (!gst_buffer_map(buf, &info, GST_MAP_READ))
389+
GstCaps* frame_caps = gst_sample_get_caps(sample); // no lifetime transfer
390+
if (!frame_caps)
386391
{
387-
//something weird went wrong here. abort. abort.
388-
CV_WARN("Failed to map GStreamer buffer to system memory");
392+
CV_LOG_ERROR(NULL, "GStreamer: gst_sample_get_caps() returns NULL");
389393
return false;
390394
}
391395

392-
try
396+
if (!GST_CAPS_IS_SIMPLE(frame_caps))
393397
{
394-
Mat src;
395-
if (isOutputByteBuffer)
396-
src = Mat(Size(info.size, 1), CV_8UC1, info.data);
397-
else
398-
src = Mat(sz, CV_MAKETYPE(CV_8U, channels), info.data);
399-
CV_Assert(src.isContinuous());
400-
src.copyTo(dst);
398+
// bail out in no caps
399+
CV_LOG_ERROR(NULL, "GStreamer: GST_CAPS_IS_SIMPLE(frame_caps) check is failed");
400+
return false;
401401
}
402-
catch (...)
402+
403+
GstVideoInfo info = {};
404+
gboolean video_info_res = gst_video_info_from_caps(&info, frame_caps);
405+
if (!video_info_res)
403406
{
404-
gst_buffer_unmap(buf, &info);
405-
throw;
407+
CV_Error(Error::StsError, "GStreamer: gst_video_info_from_caps() is failed. Can't handle unknown layout");
406408
}
407-
gst_buffer_unmap(buf, &info);
408-
409-
return true;
410-
}
411409

412-
bool GStreamerCapture::determineFrameDims(Size &sz, gint& channels, bool& isOutputByteBuffer)
413-
{
414-
GstCaps * frame_caps = gst_sample_get_caps(sample); // no lifetime transfer
415-
416-
// bail out in no caps
417-
if (!GST_CAPS_IS_SIMPLE(frame_caps))
410+
int frame_width = GST_VIDEO_INFO_WIDTH(&info);
411+
int frame_height = GST_VIDEO_INFO_HEIGHT(&info);
412+
if (frame_width <= 0 || frame_height <= 0)
413+
{
414+
CV_LOG_ERROR(NULL, "GStreamer: Can't query frame size from GStreamer sample");
418415
return false;
416+
}
419417

420418
GstStructure* structure = gst_caps_get_structure(frame_caps, 0); // no lifetime transfer
421-
422-
// bail out if width or height are 0
423-
if (!gst_structure_get_int(structure, "width", &width)
424-
|| !gst_structure_get_int(structure, "height", &height))
419+
if (!structure)
425420
{
426-
CV_WARN("Can't query frame size from GStreeamer buffer");
421+
CV_LOG_ERROR(NULL, "GStreamer: Can't query 'structure'-0 from GStreamer sample");
427422
return false;
428423
}
429424

430-
sz = Size(width, height);
431-
432425
const gchar* name_ = gst_structure_get_name(structure);
433426
if (!name_)
427+
{
428+
CV_LOG_ERROR(NULL, "GStreamer: Can't query 'name' from GStreamer sample");
434429
return false;
430+
}
435431
std::string name = toLowerCase(std::string(name_));
436432

437-
// we support 11 types of data:
433+
// gstreamer expects us to handle the memory at this point
434+
// so we can just wrap the raw buffer and be done with it
435+
GstBuffer* buf = gst_sample_get_buffer(sample); // no lifetime transfer
436+
if (!buf)
437+
return false;
438+
GstMapInfo map_info = {};
439+
if (!gst_buffer_map(buf, &map_info, GST_MAP_READ))
440+
{
441+
CV_LOG_ERROR(NULL, "GStreamer: Failed to map GStreamer buffer to system memory");
442+
return false;
443+
}
444+
ScopeGuardGstMapInfo map_guard(buf, &map_info); // call gst_buffer_unmap(buf, &map_info) on scope leave
445+
446+
// we support these types of data:
438447
// video/x-raw, format=BGR -> 8bit, 3 channels
439448
// video/x-raw, format=GRAY8 -> 8bit, 1 channel
440449
// video/x-raw, format=UYVY -> 8bit, 2 channel
@@ -448,50 +457,117 @@ bool GStreamerCapture::determineFrameDims(Size &sz, gint& channels, bool& isOutp
448457
// image/jpeg -> 8bit, mjpeg: buffer_size x 1 x 1
449458
// bayer data is never decoded, the user is responsible for that
450459
// everything is 8 bit, so we just test the caps for bit depth
460+
Size sz = Size(frame_width, frame_height);
461+
guint n_planes = GST_VIDEO_INFO_N_PLANES(&info);
451462
if (name == "video/x-raw")
452463
{
453464
const gchar* format_ = gst_structure_get_string(structure, "format");
454465
if (!format_)
466+
{
467+
CV_LOG_ERROR(NULL, "GStreamer: Can't query 'format' of 'video/x-raw'");
455468
return false;
469+
}
456470
std::string format = toUpperCase(std::string(format_));
457471

458472
if (format == "BGR")
459473
{
460-
channels = 3;
474+
CV_CheckEQ((int)n_planes, 1, "");
475+
size_t step = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
476+
CV_CheckGE(step, (size_t)frame_width * 3, "");
477+
Mat src(sz, CV_8UC3, map_info.data + GST_VIDEO_INFO_PLANE_OFFSET(&info, 0), step);
478+
src.copyTo(dst);
479+
return true;
461480
}
462-
else if (format == "UYVY" || format == "YUY2" || format == "YVYU")
481+
else if (format == "GRAY8")
463482
{
464-
channels = 2;
483+
CV_CheckEQ((int)n_planes, 1, "");
484+
size_t step = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
485+
CV_CheckGE(step, (size_t)frame_width, "");
486+
Mat src(sz, CV_8UC1, map_info.data + GST_VIDEO_INFO_PLANE_OFFSET(&info, 0), step);
487+
src.copyTo(dst);
488+
return true;
465489
}
466-
else if (format == "NV12" || format == "NV21" || format == "YV12" || format == "I420")
490+
else if (format == "UYVY" || format == "YUY2" || format == "YVYU")
467491
{
468-
channels = 1;
469-
sz.height = sz.height * 3 / 2;
492+
CV_CheckEQ((int)n_planes, 1, "");
493+
size_t step = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
494+
CV_CheckGE(step, (size_t)frame_width * 2, "");
495+
Mat src(sz, CV_8UC2, map_info.data + GST_VIDEO_INFO_PLANE_OFFSET(&info, 0), step);
496+
src.copyTo(dst);
497+
return true;
498+
}
499+
else if (format == "NV12" || format == "NV21")
500+
{
501+
CV_CheckEQ((int)n_planes, 2, "");
502+
size_t stepY = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
503+
CV_CheckGE(stepY, (size_t)frame_width, "");
504+
size_t stepUV = GST_VIDEO_INFO_PLANE_STRIDE(&info, 1);
505+
CV_CheckGE(stepUV, (size_t)frame_width, "");
506+
size_t offsetY = GST_VIDEO_INFO_PLANE_OFFSET(&info, 0);
507+
size_t offsetUV = GST_VIDEO_INFO_PLANE_OFFSET(&info, 1);
508+
if (stepY != stepUV || (offsetUV - offsetY) != (stepY * frame_height))
509+
{
510+
dst.create(Size(frame_width, frame_height * 3 / 2), CV_8UC1);
511+
Mat dst_ = dst.getMat();
512+
Mat srcY(sz, CV_8UC1, map_info.data + offsetY, stepY);
513+
Mat srcUV(Size(frame_width, frame_height / 2), CV_8UC1, map_info.data + offsetUV, stepUV);
514+
srcY.copyTo(dst_(Rect(0, 0, frame_width, frame_height)));
515+
srcUV.copyTo(dst_(Rect(0, frame_height, frame_width, frame_height / 2)));
516+
}
517+
else
518+
{
519+
Mat src(Size(frame_width, frame_height * 3 / 2), CV_8UC1, map_info.data + offsetY, stepY);
520+
src.copyTo(dst);
521+
}
522+
return true;
470523
}
471-
else if (format == "GRAY8")
524+
else if (format == "YV12" || format == "I420")
472525
{
473-
channels = 1;
526+
CV_CheckEQ((int)n_planes, 3, "");
527+
size_t step0 = GST_VIDEO_INFO_PLANE_STRIDE(&info, 0);
528+
CV_CheckGE(step0, (size_t)frame_width, "");
529+
size_t step1 = GST_VIDEO_INFO_PLANE_STRIDE(&info, 1);
530+
CV_CheckGE(step1, (size_t)frame_width / 2, "");
531+
size_t step2 = GST_VIDEO_INFO_PLANE_STRIDE(&info, 2);
532+
CV_CheckGE(step2, (size_t)frame_width / 2, "");
533+
534+
size_t offset0 = GST_VIDEO_INFO_PLANE_OFFSET(&info, 0);
535+
size_t offset1 = GST_VIDEO_INFO_PLANE_OFFSET(&info, 1);
536+
size_t offset2 = GST_VIDEO_INFO_PLANE_OFFSET(&info, 2);
537+
{
538+
dst.create(Size(frame_width, frame_height * 3 / 2), CV_8UC1);
539+
Mat dst_ = dst.getMat();
540+
Mat srcY(sz, CV_8UC1, map_info.data + offset0, step0);
541+
Size sz2(frame_width / 2, frame_height / 2);
542+
Mat src1(sz2, CV_8UC1, map_info.data + offset1, step1);
543+
Mat src2(sz2, CV_8UC1, map_info.data + offset2, step2);
544+
srcY.copyTo(dst_(Rect(0, 0, frame_width, frame_height)));
545+
src1.copyTo(Mat(sz2, CV_8UC1, dst_.ptr<uchar>(frame_height)));
546+
src2.copyTo(Mat(sz2, CV_8UC1, dst_.ptr<uchar>(frame_height) + src1.total()));
547+
}
548+
return true;
474549
}
475550
else
476551
{
477-
CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer format: %s", format.c_str()));
552+
CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer 'video/x-raw' format: %s", format.c_str()));
478553
}
479554
}
480555
else if (name == "video/x-bayer")
481556
{
482-
channels = 1;
557+
CV_CheckEQ((int)n_planes, 0, "");
558+
Mat src = Mat(sz, CV_8UC1, map_info.data);
559+
src.copyTo(dst);
560+
return true;
483561
}
484562
else if (name == "image/jpeg")
485563
{
486-
// the correct size will be set once the first frame arrives
487-
channels = 1;
488-
isOutputByteBuffer = true;
489-
}
490-
else
491-
{
492-
CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer layer type: %s", name.c_str()));
564+
CV_CheckEQ((int)n_planes, 0, "");
565+
Mat src = Mat(Size(map_info.size, 1), CV_8UC1, map_info.data);
566+
src.copyTo(dst);
567+
return true;
493568
}
494-
return true;
569+
570+
CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer layer type: %s", name.c_str()));
495571
}
496572

497573
bool GStreamerCapture::isPipelinePlaying()

modules/videoio/test/test_gstreamer.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
#include "test_precomp.hpp"
66

7-
namespace opencv_test
8-
{
7+
namespace opencv_test { namespace {
98

109
typedef tuple< string, Size, Size, int > Param;
1110
typedef testing::TestWithParam< Param > videoio_gstreamer;
1211

13-
TEST_P(videoio_gstreamer, read_write)
12+
TEST_P(videoio_gstreamer, read_check)
1413
{
1514
if (!videoio_registry::hasBackend(CAP_GSTREAMER))
1615
throw SkipTestException("GStreamer backend was not found");
@@ -57,7 +56,7 @@ TEST_P(videoio_gstreamer, read_write)
5756
ASSERT_FALSE(cap.isOpened());
5857
}
5958

60-
Param test_data[] = {
59+
static const Param test_data[] = {
6160
make_tuple("video/x-raw, format=BGR" , Size(640, 480), Size(640, 480), COLOR_BGR2RGB),
6261
make_tuple("video/x-raw, format=GRAY8", Size(640, 480), Size(640, 480), COLOR_GRAY2RGB),
6362
make_tuple("video/x-raw, format=UYVY" , Size(640, 480), Size(640, 480), COLOR_YUV2RGB_UYVY),
@@ -68,7 +67,15 @@ Param test_data[] = {
6867
make_tuple("video/x-raw, format=YV12" , Size(640, 480), Size(640, 720), COLOR_YUV2RGB_YV12),
6968
make_tuple("video/x-raw, format=I420" , Size(640, 480), Size(640, 720), COLOR_YUV2RGB_I420),
7069
make_tuple("video/x-bayer" , Size(640, 480), Size(640, 480), COLOR_BayerBG2RGB),
71-
make_tuple("jpegenc ! image/jpeg" , Size(640, 480), Size(640, 480), COLOR_BGR2RGB)
70+
make_tuple("jpegenc ! image/jpeg" , Size(640, 480), Size(640, 480), COLOR_BGR2RGB),
71+
72+
// unaligned cases, strides information must be used
73+
make_tuple("video/x-raw, format=BGR" , Size(322, 242), Size(322, 242), COLOR_BGR2RGB),
74+
make_tuple("video/x-raw, format=GRAY8", Size(322, 242), Size(322, 242), COLOR_GRAY2RGB),
75+
make_tuple("video/x-raw, format=NV12" , Size(322, 242), Size(322, 363), COLOR_YUV2RGB_NV12),
76+
make_tuple("video/x-raw, format=NV21" , Size(322, 242), Size(322, 363), COLOR_YUV2RGB_NV21),
77+
make_tuple("video/x-raw, format=YV12" , Size(322, 242), Size(322, 363), COLOR_YUV2RGB_YV12),
78+
make_tuple("video/x-raw, format=I420" , Size(322, 242), Size(322, 363), COLOR_YUV2RGB_I420),
7279
};
7380

7481
INSTANTIATE_TEST_CASE_P(videoio, videoio_gstreamer, testing::ValuesIn(test_data));
@@ -132,4 +139,4 @@ TEST(videoio_gstreamer, gray16_writing)
132139
EXPECT_EQ(0, remove(temp_file.c_str()));
133140
}
134141

135-
} // namespace
142+
}} // namespace

0 commit comments

Comments
 (0)