From b253665f8c794d522cbf8393f7829782aaa994d2 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Mon, 9 Apr 2012 23:12:52 +0000 Subject: [PATCH] added explicit VideoWriter::release(). Fixed video stream finalization (write remaining frames); Fixed occasional crash in the case of odd-width or odd-height videos; Fixed a few problems in ffmpeg tests. Positioning still does not work well. --- .../highgui/include/opencv2/highgui/highgui.hpp | 3 +- modules/highgui/src/cap.cpp | 7 ++- modules/highgui/src/cap_ffmpeg_impl_v2.hpp | 67 ++++++++++++++------ modules/highgui/test/test_ffmpeg.cpp | 18 ++++-- modules/highgui/test/test_framecount.cpp | 2 +- modules/highgui/test/test_positioning.cpp | 6 +- modules/highgui/test/test_video_io.cpp | 71 +++++++++++++--------- modules/highgui/test/test_video_pos.cpp | 8 +-- 8 files changed, 119 insertions(+), 63 deletions(-) diff --git a/modules/highgui/include/opencv2/highgui/highgui.hpp b/modules/highgui/include/opencv2/highgui/highgui.hpp index 726707f..5c5d24b 100644 --- a/modules/highgui/include/opencv2/highgui/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui/highgui.hpp @@ -232,8 +232,9 @@ public: virtual ~VideoWriter(); CV_WRAP virtual bool open(const string& filename, int fourcc, double fps, - Size frameSize, bool isColor=true); + Size frameSize, bool isColor=true); CV_WRAP virtual bool isOpened() const; + CV_WRAP virtual void release(); virtual VideoWriter& operator << (const Mat& image); CV_WRAP virtual void write(const Mat& image); diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index 0a8968b..96767d9 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -493,9 +493,14 @@ VideoWriter::VideoWriter(const string& filename, int fourcc, double fps, Size fr open(filename, fourcc, fps, frameSize, isColor); } -VideoWriter::~VideoWriter() +void VideoWriter::release() { writer.release(); +} + +VideoWriter::~VideoWriter() +{ + release(); } bool VideoWriter::open(const string& filename, int fourcc, double fps, Size frameSize, bool isColor) diff --git a/modules/highgui/src/cap_ffmpeg_impl_v2.hpp b/modules/highgui/src/cap_ffmpeg_impl_v2.hpp index 1d1a6a4..e8b3815 100755 --- a/modules/highgui/src/cap_ffmpeg_impl_v2.hpp +++ b/modules/highgui/src/cap_ffmpeg_impl_v2.hpp @@ -383,7 +383,7 @@ bool CvCapture_FFMPEG::open( const char* _filename ) /* register all codecs, demux and protocols */ av_register_all(); - //av_log_set_level(AV_LOG_ERROR); + av_log_set_level(AV_LOG_ERROR); int err = avformat_open_input(&ic, _filename, NULL, NULL); if (err < 0) { @@ -711,7 +711,7 @@ int64_t CvCapture_FFMPEG::get_total_frames() if (nbf == 0) { - nbf = static_cast(get_duration_sec() * get_fps()); + nbf = (int64_t)floor(get_duration_sec() * get_fps() + 0.5); } return nbf; } @@ -829,6 +829,7 @@ struct CvVideoWriter_FFMPEG AVStream * video_st; int input_pix_fmt; Image_FFMPEG temp_image; + int frame_width, frame_height; bool ok; #if defined(HAVE_FFMPEG_SWSCALE) struct SwsContext *img_convert_ctx; @@ -909,6 +910,7 @@ void CvVideoWriter_FFMPEG::init() #if defined(HAVE_FFMPEG_SWSCALE) img_convert_ctx = 0; #endif + frame_width = frame_height = 0; ok = false; } @@ -986,6 +988,9 @@ static AVStream *icv_add_video_stream_FFMPEG(AVFormatContext *oc, /* put sample parameters */ c->bit_rate = bitrate; + // took advice from + // http://ffmpeg-users.933282.n4.nabble.com/warning-clipping-1-dct-coefficients-to-127-127-td934297.html + c->qmin = 3; /* resolution must be a multiple of two */ c->width = w; @@ -1050,6 +1055,8 @@ static AVStream *icv_add_video_stream_FFMPEG(AVFormatContext *oc, return st; } +static const int OPENCV_NO_FRAMES_WRITTEN_CODE = 1000; + int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, uint8_t * outbuf, uint32_t outbuf_size, AVFrame * picture ) { #if LIBAVFORMAT_BUILD > 4628 @@ -1058,7 +1065,7 @@ int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, uint8_ AVCodecContext * c = &(video_st->codec); #endif int out_size; - int ret; + int ret = 0; if (oc->oformat->flags & AVFMT_RAWPICTURE) { /* raw video case. The API will change slightly in the near @@ -1099,18 +1106,21 @@ int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, uint8_ /* write the compressed frame in the media file */ ret = av_write_frame(oc, &pkt); } else { - ret = 0; + ret = OPENCV_NO_FRAMES_WRITTEN_CODE; } } - if (ret != 0) return -1; - - return 0; + return ret; } /// write a frame with FFMPEG bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int width, int height, int cn, int origin ) { bool ret = false; + + if( (width & -2) != frame_width || (height & -2) != frame_height || !data ) + return false; + width = frame_width; + height = frame_height; // typecast from opaque data type to implemented struct #if LIBAVFORMAT_BUILD > 4628 @@ -1232,7 +1242,18 @@ void CvVideoWriter_FFMPEG::close() /* write the trailer, if any */ if(ok && oc) + { + if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) + { + for(;;) + { + int ret = icv_av_write_frame_FFMPEG( oc, video_st, outbuf, outbuf_size, NULL); + if( ret == OPENCV_NO_FRAMES_WRITTEN_CODE || ret < 0 ) + break; + } + } av_write_trailer(oc); + } // free pictures #if LIBAVFORMAT_BUILD > 4628 @@ -1297,17 +1318,28 @@ void CvVideoWriter_FFMPEG::close() double fps, int width, int height, bool is_color ) { CodecID codec_id = CODEC_ID_NONE; - int err, codec_pix_fmt, bitrate_scale=64; + int err, codec_pix_fmt; + double bitrate_scale = 1; close(); // check arguments - assert (filename); - assert (fps > 0); - assert (width > 0 && height > 0); + if( !filename ) + return false; + if(fps <= 0) + return false; + + // we allow frames of odd width or height, but in this case we truncate + // the rightmost column/the bottom row. Probably, this should be handled more elegantly, + // but some internal functions inside FFMPEG swscale require even width/height. + width &= -2; + height &= -2; + if( width <= 0 || height <= 0 ) + return false; // tell FFMPEG to register codecs - av_register_all (); + av_register_all(); + av_log_set_level(AV_LOG_ERROR); /* auto detect the output format from the name and fourcc code. */ @@ -1367,7 +1399,7 @@ void CvVideoWriter_FFMPEG::close() case CODEC_ID_MJPEG: case CODEC_ID_LJPEG: codec_pix_fmt = PIX_FMT_YUVJ420P; - bitrate_scale = 128; + bitrate_scale = 3; break; case CODEC_ID_RAWVIDEO: codec_pix_fmt = input_pix_fmt == PIX_FMT_GRAY8 || @@ -1379,13 +1411,14 @@ void CvVideoWriter_FFMPEG::close() codec_pix_fmt = PIX_FMT_YUV420P; break; } + + double bitrate = MIN(bitrate_scale*fps*width*height, (double)INT_MAX/2); // TODO -- safe to ignore output audio stream? video_st = icv_add_video_stream_FFMPEG(oc, codec_id, - width, height, width*height*bitrate_scale, + width, height, cvRound(bitrate), fps, codec_pix_fmt); - /* set the output parameters (must be done even if no parameters). */ #if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0) @@ -1488,6 +1521,8 @@ void CvVideoWriter_FFMPEG::close() remove(filename); return false; } + frame_width = width; + frame_height = height; ok = true; return true; } @@ -1536,8 +1571,6 @@ void CvVideoWriter_FFMPEG::close() return capture->retrieveFrame(0, data, step, width, height, cn); } - - CvVideoWriter_FFMPEG* cvCreateVideoWriter_FFMPEG( const char* filename, int fourcc, double fps, int width, int height, int isColor ) { diff --git a/modules/highgui/test/test_ffmpeg.cpp b/modules/highgui/test/test_ffmpeg.cpp index 562242d..e2eed5e 100644 --- a/modules/highgui/test/test_ffmpeg.cpp +++ b/modules/highgui/test/test_ffmpeg.cpp @@ -57,11 +57,9 @@ public: { const int img_r = 4096; const int img_c = 4096; - Size frame_s = Size(img_c, img_r); - const double fps = 15; + const double fps0 = 15; const double time_sec = 1; - const int coeff = static_cast(static_cast(cv::min(img_c, img_r)) / (fps * time_sec)); - + const size_t n = sizeof(codec_bmp_tags)/sizeof(codec_bmp_tags[0]); bool created = false; @@ -70,7 +68,7 @@ public: { stringstream s; s << codec_bmp_tags[j].tag; int tag = codec_bmp_tags[j].tag; - + if( tag != MKTAG('H', '2', '6', '3') && tag != MKTAG('H', '2', '6', '1') && tag != MKTAG('D', 'I', 'V', 'X') && @@ -93,12 +91,19 @@ public: try { + double fps = fps0; + Size frame_s = Size(img_c, img_r); - frame_s = Size(img_c, img_r); if( tag == CV_FOURCC('H', '2', '6', '1') ) frame_s = Size(352, 288); else if( tag == CV_FOURCC('H', '2', '6', '3') ) frame_s = Size(704, 576); + /*else if( tag == CV_FOURCC('M', 'J', 'P', 'G') || + tag == CV_FOURCC('j', 'p', 'e', 'g') ) + frame_s = Size(1920, 1080);*/ + + if( tag == CV_FOURCC('M', 'P', 'E', 'G') ) + fps = 25; VideoWriter writer(filename, tag, fps, frame_s); @@ -113,6 +118,7 @@ public: else { Mat img(frame_s, CV_8UC3, Scalar::all(0)); + const int coeff = cvRound(cv::min(frame_s.width, frame_s.height)/(fps0 * time_sec)); for (int i = 0 ; i < static_cast(fps * time_sec); i++ ) { diff --git a/modules/highgui/test/test_framecount.cpp b/modules/highgui/test/test_framecount.cpp index cdf0590..1764c27 100644 --- a/modules/highgui/test/test_framecount.cpp +++ b/modules/highgui/test/test_framecount.cpp @@ -55,7 +55,7 @@ public: void CV_FramecountTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP const int time_sec = 5, fps = 25; diff --git a/modules/highgui/test/test_positioning.cpp b/modules/highgui/test/test_positioning.cpp index 358145a..87e42c9 100644 --- a/modules/highgui/test/test_positioning.cpp +++ b/modules/highgui/test/test_positioning.cpp @@ -122,7 +122,7 @@ void CV_VideoPositioningTest::run_test(int method) ts->printf(cvtest::TS::LOG, "\n\nSource files directory: %s\n", (src_dir+"video/").c_str()); - const string ext[] = {"mov", "avi", "mp4", "mpg", "wmv"}; + const string ext[] = {"avi", "mp4", "wmv"}; size_t n = sizeof(ext)/sizeof(ext[0]); @@ -209,7 +209,7 @@ void CV_VideoPositioningTest::run_test(int method) void CV_VideoProgressivePositioningTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP run_test(PROGRESSIVE); @@ -220,7 +220,7 @@ void CV_VideoProgressivePositioningTest::run(int) void CV_VideoRandomPositioningTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP run_test(RANDOM); diff --git a/modules/highgui/test/test_video_io.cpp b/modules/highgui/test/test_video_io.cpp index 17e10dd..3910547 100644 --- a/modules/highgui/test/test_video_io.cpp +++ b/modules/highgui/test/test_video_io.cpp @@ -377,29 +377,40 @@ void CV_HighGuiTest::SpecificImageTest(const string& dir) void CV_HighGuiTest::SpecificVideoFileTest(const string& dir, const char codecchars[4]) { - const string ext[] = {"avi", "mov", "mp4", "mpg", "wmv"}; - - const size_t n = sizeof(ext)/sizeof(ext[0]); - + const string exts[] = {"avi", "mov", "mpg", "wmv"}; + const size_t n = sizeof(exts)/sizeof(exts[0]); + int fourcc0 = CV_FOURCC(codecchars[0], codecchars[1], codecchars[2], codecchars[3]); + for (size_t j = 0; j < n; ++j) - if ((ext[j]!="mp4")||(string(&codecchars[0], 4)!="IYUV")) - #if defined WIN32 || defined _WIN32 - if (((ext[j]!="mov")||(string(&codecchars[0], 4)=="XVID"))&&(ext[j]!="mp4")) - #endif { - const string video_file = "video_" + string(&codecchars[0], 4) + "." + ext[j]; + string ext = exts[j]; + int fourcc = fourcc0; + + if( (ext == "mov" && fourcc != CV_FOURCC('M', 'J', 'P', 'G')) || + (ext == "mpg" && fourcc != CV_FOURCC('m', 'p', 'e', 'g')) || + (ext == "wmv" && fourcc != CV_FOURCC('M', 'J', 'P', 'G'))) + continue; + if( ext == "mov" ) + fourcc = CV_FOURCC('m', 'p', '4', 'v'); + + string fourcc_str = format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255); + const string video_file = "video_" + fourcc_str + "." + ext; - VideoWriter writer = cv::VideoWriter(video_file, CV_FOURCC(codecchars[0], codecchars[1], codecchars[2], codecchars[3]), 25, cv::Size(968, 757), true); + Size frame_size(968 & -2, 757 & -2); + //Size frame_size(968 & -16, 757 & -16); + //Size frame_size(640, 480); + VideoWriter writer(video_file, fourcc, 25, frame_size, true); if (!writer.isOpened()) { + VideoWriter writer(video_file, fourcc, 25, frame_size, true); ts->printf(ts->LOG, "Creating a video in %s...\n", video_file.c_str()); - ts->printf(ts->LOG, "Cannot create VideoWriter object with codec %s.\n", string(&codecchars[0], 4).c_str()); + ts->printf(ts->LOG, "Cannot create VideoWriter object with codec %s.\n", fourcc_str.c_str()); ts->set_failed_test_info(ts->FAIL_MISMATCH); continue; } - const size_t IMAGE_COUNT = 30; + const size_t IMAGE_COUNT = 30; for(size_t i = 0; i < IMAGE_COUNT; ++i) { @@ -417,10 +428,10 @@ void CV_HighGuiTest::SpecificVideoFileTest(const string& dir, const char codecch ts->printf(ts->LOG, "Error: cannot read frame from %s.\n", (ts->get_data_path()+"../python/images/QCIF_"+s_digit.str()+".bmp").c_str()); ts->printf(ts->LOG, "Continue creating the video file...\n"); ts->set_failed_test_info(ts->FAIL_INVALID_TEST_DATA); - continue; + break;//continue; } - cv::resize(img, img, Size(968, 757), 0.0, 0.0, cv::INTER_CUBIC); + cv::resize(img, img, frame_size, 0.0, 0.0, cv::INTER_CUBIC); for (int k = 0; k < img.rows; ++k) for (int l = 0; l < img.cols; ++l) @@ -433,30 +444,31 @@ void CV_HighGuiTest::SpecificVideoFileTest(const string& dir, const char codecch writer << img; } + writer.release(); cv::VideoCapture cap(video_file); size_t FRAME_COUNT = (size_t)cap.get(CV_CAP_PROP_FRAME_COUNT); - if (FRAME_COUNT != IMAGE_COUNT) + if (FRAME_COUNT != IMAGE_COUNT && ext != "mpg" ) { - ts->printf(ts->LOG, "\nFrame count checking for video_%s.%s...\n", string(&codecchars[0], 4).c_str(), ext[j].c_str()); - ts->printf(ts->LOG, "Video codec: %s\n", string(&codecchars[0], 4).c_str()); + ts->printf(ts->LOG, "\nFrame count checking for video_%s.%s...\n", fourcc_str.c_str(), ext.c_str()); + ts->printf(ts->LOG, "Video codec: %s\n", fourcc_str.c_str()); ts->printf(ts->LOG, "Required frame count: %d; Returned frame count: %d\n", IMAGE_COUNT, FRAME_COUNT); ts->printf(ts->LOG, "Error: Incorrect frame count in the video.\n"); ts->printf(ts->LOG, "Continue checking...\n"); ts->set_failed_test_info(ts->FAIL_BAD_ACCURACY); } - cap.set(CV_CAP_PROP_POS_FRAMES, -1); + //cap.set(CV_CAP_PROP_POS_FRAMES, -1); - for (int i = -1; i < (int)std::min(FRAME_COUNT, IMAGE_COUNT)-1; i++) + for (int i = 0; i < (int)std::min(FRAME_COUNT, IMAGE_COUNT)-1; i++) { cv::Mat frame; cap >> frame; if (frame.empty()) { ts->printf(ts->LOG, "\nVideo file directory: %s\n", "."); - ts->printf(ts->LOG, "File name: video_%s.%s\n", string(&codecchars[0], 4).c_str(), ext[j].c_str()); - ts->printf(ts->LOG, "Video codec: %s\n", string(&codecchars[0], 4).c_str()); + ts->printf(ts->LOG, "File name: video_%s.%s\n", fourcc_str.c_str(), ext.c_str()); + ts->printf(ts->LOG, "Video codec: %s\n", fourcc_str.c_str()); ts->printf(ts->LOG, "Error: cannot read the next frame with index %d.\n", i+1); ts->set_failed_test_info(ts->FAIL_MISSING_TEST_DATA); break; @@ -475,20 +487,19 @@ void CV_HighGuiTest::SpecificVideoFileTest(const string& dir, const char codecch continue; } - const double thresDbell = 20; + const double thresDbell = 40; double psnr = PSNR(img, frame); if (psnr > thresDbell) { - ts->printf(ts->LOG, "\nReading frame from the file video_%s.%s...\n", string(&codecchars[0], 4).c_str(), ext[j].c_str()); + ts->printf(ts->LOG, "\nReading frame from the file video_%s.%s...\n", fourcc_str.c_str(), ext.c_str()); ts->printf(ts->LOG, "Frame index: %d\n", i+1); ts->printf(ts->LOG, "Difference between saved and original images: %g\n", psnr); ts->printf(ts->LOG, "Maximum allowed difference: %g\n", thresDbell); ts->printf(ts->LOG, "Error: too big difference between saved and original images.\n"); - continue; + break; } - } } } @@ -621,12 +632,12 @@ void CV_SpecificImageTest::run(int) void CV_VideoTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP const char codecs[][4] = { {'I', 'Y', 'U', 'V'}, {'X', 'V', 'I', 'D'}, - {'M', 'P', 'G', '2'}, + {'m', 'p', 'e', 'g'}, {'M', 'J', 'P', 'G'} }; printf("%s", ts->get_data_path().c_str()); @@ -644,10 +655,10 @@ void CV_VideoTest::run(int) void CV_SpecificVideoFileTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP - const char codecs[][4] = { {'M', 'P', 'G', '2'}, + const char codecs[][4] = { {'m', 'p', 'e', 'g'}, {'X', 'V', 'I', 'D'}, {'M', 'J', 'P', 'G'}, {'I', 'Y', 'U', 'V'} }; @@ -668,7 +679,7 @@ void CV_SpecificVideoCameraTest::run(int) #if defined WIN32 || (defined __linux__ && !defined ANDROID) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP - const char codecs[][4] = { {'M', 'P', 'G', '2'}, + const char codecs[][4] = { {'m', 'p', 'e', 'g'}, {'X', 'V', 'I', 'D'}, {'M', 'J', 'P', 'G'}, {'I', 'Y', 'U', 'V'} }; diff --git a/modules/highgui/test/test_video_pos.cpp b/modules/highgui/test/test_video_pos.cpp index 387912f..2091f7c 100755 --- a/modules/highgui/test/test_video_pos.cpp +++ b/modules/highgui/test/test_video_pos.cpp @@ -61,8 +61,8 @@ void CV_PositioningTest::CreateTestVideo(const string& format, int codec, int fr { stringstream s; s << codec; - if( format == "mov" && codec == CV_FOURCC('M', 'P', 'G', '2')) - putchar('$'); + //if( format == "mov" && codec == CV_FOURCC('m', 'p', 'e', 'g') + // putchar('$'); cv::VideoWriter writer("test_video_"+s.str()+"."+format, codec, 25, cv::Size(640, 480), false); @@ -102,13 +102,13 @@ void CV_PositioningTest::CreateTestVideo(const string& format, int codec, int fr void CV_PositioningTest::run(int) { -#if defined WIN32 || (defined __linux__ && !defined ANDROID) +#if defined WIN32 || (defined __linux__ && !defined ANDROID) || (defined __APPLE__ && defined HAVE_FFMPEG) #if !defined HAVE_GSTREAMER || defined HAVE_GSTREAMER_APP const string format[] = {"avi", "mov", "mp4", "mpg", "wmv", "3gp"}; const char codec[][4] = { {'X', 'V', 'I', 'D'}, - {'M', 'P', 'G', '2'}, + {'m', 'p', 'e', 'g'}, {'M', 'J', 'P', 'G'} }; size_t n_format = sizeof(format)/sizeof(format[0]), -- 2.7.4