mirror of
https://github.com/opencv/opencv.git
synced 2026-09-11 04:43:22 -05:00
videoio: fix FFmpeg VideoCapture ignoring mid-stream pixel format change
(cherry picked from commit f3c9241611cfb46dd17de2b3e3f25f6fe36b71f8)
This commit is contained in:
@@ -582,6 +582,7 @@ struct CvCapture_FFMPEG
|
||||
AVPacket packet;
|
||||
Image_FFMPEG frame;
|
||||
struct SwsContext *img_convert_ctx;
|
||||
AVPixelFormat img_convert_ctx_format;
|
||||
|
||||
int64_t frame_number, first_frame_number;
|
||||
|
||||
@@ -648,6 +649,7 @@ void CvCapture_FFMPEG::init()
|
||||
memset(&packet, 0, sizeof(packet));
|
||||
av_init_packet(&packet);
|
||||
img_convert_ctx = 0;
|
||||
img_convert_ctx_format = AV_PIX_FMT_NONE;
|
||||
|
||||
avcodec = 0;
|
||||
context = 0;
|
||||
@@ -689,6 +691,7 @@ void CvCapture_FFMPEG::close()
|
||||
{
|
||||
sws_freeContext(img_convert_ctx);
|
||||
img_convert_ctx = 0;
|
||||
img_convert_ctx_format = AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
if( picture )
|
||||
@@ -1922,7 +1925,8 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step,
|
||||
if( img_convert_ctx == NULL ||
|
||||
frame.width != video_st->CV_FFMPEG_CODEC_FIELD->width ||
|
||||
frame.height != video_st->CV_FFMPEG_CODEC_FIELD->height ||
|
||||
frame.data == NULL )
|
||||
frame.data == NULL ||
|
||||
(AVPixelFormat)sw_picture->format != img_convert_ctx_format )
|
||||
{
|
||||
#if LIBSWSCALE_BUILD >= CALC_FFMPEG_VERSION(6, 4, 100)
|
||||
int buffer_width = video_st->CV_FFMPEG_CODEC_FIELD->width;
|
||||
@@ -2000,6 +2004,8 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step,
|
||||
return false;
|
||||
}
|
||||
|
||||
img_convert_ctx_format = (AVPixelFormat)sw_picture->format;
|
||||
|
||||
#if USE_AV_FRAME_GET_BUFFER
|
||||
av_frame_unref(&rgb_picture);
|
||||
rgb_picture.format = result_format;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -1188,4 +1189,65 @@ TEST(videoio_ffmpeg, seek_with_negative_dts)
|
||||
}
|
||||
}
|
||||
|
||||
// MJPEG stream whose chroma subsampling changes mid-stream at constant
|
||||
// frame size must not reuse a conversion context built for the previous frame.
|
||||
// related issue: https://github.com/opencv/opencv/issues/29699
|
||||
TEST(videoio_ffmpeg, mjpeg_pixel_format_change)
|
||||
{
|
||||
if (!videoio_registry::hasBackend(CAP_FFMPEG))
|
||||
throw SkipTestException("FFmpeg backend was not found");
|
||||
|
||||
Mat frame0(64, 64, CV_8UC3, Scalar(0, 0, 255));
|
||||
Mat frame1(64, 64, CV_8UC3);
|
||||
for (int y = 0; y < frame1.rows; y++)
|
||||
for (int x = 0; x < frame1.cols; x++)
|
||||
frame1.at<Vec3b>(y, x) = Vec3b((uchar)(x * 4), (uchar)(y * 4), (uchar)((x + y) * 2));
|
||||
|
||||
vector<uchar> buf0, buf1;
|
||||
ASSERT_TRUE(imencode(".jpg", frame0, buf0,
|
||||
{IMWRITE_JPEG_SAMPLING_FACTOR, IMWRITE_JPEG_SAMPLING_FACTOR_422, IMWRITE_JPEG_QUALITY, 100}));
|
||||
ASSERT_TRUE(imencode(".jpg", frame1, buf1,
|
||||
{IMWRITE_JPEG_SAMPLING_FACTOR, IMWRITE_JPEG_SAMPLING_FACTOR_420, IMWRITE_JPEG_QUALITY, 100}));
|
||||
|
||||
const string filename = tempfile("test_pixfmt_change.mjpeg");
|
||||
std::ofstream file(filename.c_str(), ios::out | ios::trunc | std::ios::binary);
|
||||
file.write(reinterpret_cast<char*>(buf0.data()), buf0.size());
|
||||
file.write(reinterpret_cast<char*>(buf1.data()), buf1.size());
|
||||
file.close();
|
||||
|
||||
VideoCapture cap(filename, CAP_FFMPEG);
|
||||
ASSERT_TRUE(cap.isOpened());
|
||||
|
||||
Mat read0, read1;
|
||||
ASSERT_TRUE(cap.read(read0));
|
||||
ASSERT_TRUE(cap.read(read1));
|
||||
|
||||
Mat decodedRef0 = imdecode(buf0, IMREAD_COLOR);
|
||||
Mat decodedRef1 = imdecode(buf1, IMREAD_COLOR);
|
||||
ASSERT_FALSE(decodedRef0.empty());
|
||||
ASSERT_FALSE(decodedRef1.empty());
|
||||
ASSERT_EQ(read0.size(), decodedRef0.size());
|
||||
ASSERT_EQ(read1.size(), decodedRef1.size());
|
||||
|
||||
// JPEG re-encoding is not bit exact, but a correctly decoded frame stays
|
||||
// within a couple of intensity levels per channel; a stale conversion
|
||||
// context (the bug) produces gross corruption of tens of levels.
|
||||
const double maeThreshold = 5.0;
|
||||
|
||||
Mat diff0, diff1;
|
||||
absdiff(read0, decodedRef0, diff0);
|
||||
absdiff(read1, decodedRef1, diff1);
|
||||
Scalar mae0 = mean(diff0);
|
||||
Scalar mae1 = mean(diff1);
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
EXPECT_LE(mae0[c], maeThreshold)
|
||||
<< "First frame (channel " << c << ") decoded incorrectly";
|
||||
EXPECT_LE(mae1[c], maeThreshold)
|
||||
<< "Second frame (channel " << c << ") decoded incorrectly after pixel format change mid-stream";
|
||||
}
|
||||
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
Reference in New Issue
Block a user