diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index e818cfe75..f8d32e7ee 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -424,7 +424,6 @@ CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char* filename, int fourcc, CV_IMPL int cvWriteFrame( CvVideoWriter* writer, const IplImage* image ) { - return writer ? writer->writeFrame(image) : 0; } diff --git a/modules/highgui/src/cap_ffmpeg.cpp b/modules/highgui/src/cap_ffmpeg.cpp index 0cc60e3a4..040adbd50 100644 --- a/modules/highgui/src/cap_ffmpeg.cpp +++ b/modules/highgui/src/cap_ffmpeg.cpp @@ -57,11 +57,32 @@ static CvCreateVideoWriter_Plugin icvCreateVideoWriter_FFMPEG_p = 0; static CvReleaseVideoWriter_Plugin icvReleaseVideoWriter_FFMPEG_p = 0; static CvWriteFrame_Plugin icvWriteFrame_FFMPEG_p = 0; -static void -icvInitFFMPEG(void) +static cv::Mutex _icvInitFFMPEG_mutex; + +class icvInitFFMPEG { - static int ffmpegInitialized = 0; - if( !ffmpegInitialized ) +public: + static void Init() + { + cv::AutoLock al(_icvInitFFMPEG_mutex); + static icvInitFFMPEG init; + } + +private: + #if defined WIN32 || defined _WIN32 + HMODULE icvFFOpenCV; + + ~icvInitFFMPEG() + { + if (icvFFOpenCV) + { + FreeLibrary(icvFFOpenCV); + icvFFOpenCV = 0; + } + } + #endif + + icvInitFFMPEG() { #if defined WIN32 || defined _WIN32 const char* module_name = "opencv_ffmpeg" @@ -71,7 +92,7 @@ icvInitFFMPEG(void) #endif ".dll"; - static HMODULE icvFFOpenCV = LoadLibrary( module_name ); + icvFFOpenCV = LoadLibrary( module_name ); if( icvFFOpenCV ) { icvCreateFileCapture_FFMPEG_p = @@ -123,10 +144,8 @@ icvInitFFMPEG(void) icvReleaseVideoWriter_FFMPEG_p = (CvReleaseVideoWriter_Plugin)cvReleaseVideoWriter_FFMPEG; icvWriteFrame_FFMPEG_p = (CvWriteFrame_Plugin)cvWriteFrame_FFMPEG; #endif - - ffmpegInitialized = 1; } -} +}; class CvCapture_FFMPEG_proxy : public CvCapture @@ -161,9 +180,9 @@ public: } virtual bool open( const char* filename ) { + icvInitFFMPEG::Init(); close(); - icvInitFFMPEG(); if( !icvCreateFileCapture_FFMPEG_p ) return false; ffmpegCapture = icvCreateFileCapture_FFMPEG_p( filename ); @@ -196,7 +215,6 @@ CvCapture* cvCreateFileCapture_FFMPEG_proxy(const char * filename) #endif } - class CvVideoWriter_FFMPEG_proxy : public CvVideoWriter { public: @@ -214,8 +232,8 @@ public: } virtual bool open( const char* filename, int fourcc, double fps, CvSize frameSize, bool isColor ) { + icvInitFFMPEG::Init(); close(); - icvInitFFMPEG(); if( !icvCreateVideoWriter_FFMPEG_p ) return false; ffmpegWriter = icvCreateVideoWriter_FFMPEG_p( filename, fourcc, fps, frameSize.width, frameSize.height, isColor ); diff --git a/modules/highgui/src/cap_ffmpeg_impl.hpp b/modules/highgui/src/cap_ffmpeg_impl.hpp index 445a9e620..d6ccdabc5 100644 --- a/modules/highgui/src/cap_ffmpeg_impl.hpp +++ b/modules/highgui/src/cap_ffmpeg_impl.hpp @@ -328,28 +328,179 @@ void CvCapture_FFMPEG::close() #define AVSEEK_FLAG_ANY 1 #endif -static void icvInitFFMPEG_internal() +class ImplMutex { - static volatile bool initialized = false; - if( !initialized ) +public: + ImplMutex(); + ~ImplMutex(); + + void lock(); + bool trylock(); + void unlock(); + + struct Impl; +protected: + Impl* impl; + +private: + ImplMutex(const ImplMutex&); + ImplMutex& operator = (const ImplMutex& m); +}; + +#if defined WIN32 || defined _WIN32 || defined WINCE + +struct ImplMutex::Impl +{ + Impl() { InitializeCriticalSection(&cs); refcount = 1; } + ~Impl() { DeleteCriticalSection(&cs); } + + void lock() { EnterCriticalSection(&cs); } + bool trylock() { return TryEnterCriticalSection(&cs) != 0; } + void unlock() { LeaveCriticalSection(&cs); } + + CRITICAL_SECTION cs; + int refcount; +}; + +#ifndef __GNUC__ +static int _interlockedExchangeAdd(int* addr, int delta) +{ +#if defined _MSC_VER && _MSC_VER >= 1500 + return (int)_InterlockedExchangeAdd((long volatile*)addr, delta); +#else + return (int)InterlockedExchangeAdd((long volatile*)addr, delta); +#endif +} +#endif // __GNUC__ + +#elif defined __APPLE__ + +#include + +struct ImplMutex::Impl +{ + Impl() { sl = OS_SPINLOCK_INIT; refcount = 1; } + ~Impl() {} + + void lock() { OSSpinLockLock(&sl); } + bool trylock() { return OSSpinLockTry(&sl); } + void unlock() { OSSpinLockUnlock(&sl); } + + OSSpinLock sl; + int refcount; +}; + +#elif defined __linux__ && !defined ANDROID + +struct ImplMutex::Impl +{ + Impl() { pthread_spin_init(&sl, 0); refcount = 1; } + ~Impl() { pthread_spin_destroy(&sl); } + + void lock() { pthread_spin_lock(&sl); } + bool trylock() { return pthread_spin_trylock(&sl) == 0; } + void unlock() { pthread_spin_unlock(&sl); } + + pthread_spinlock_t sl; + int refcount; +}; + +#else + +struct ImplMutex::Impl +{ + Impl() { pthread_mutex_init(&sl, 0); refcount = 1; } + ~Impl() { pthread_mutex_destroy(&sl); } + + void lock() { pthread_mutex_lock(&sl); } + bool trylock() { return pthread_mutex_trylock(&sl) == 0; } + void unlock() { pthread_mutex_unlock(&sl); } + + pthread_mutex_t sl; + int refcount; +}; + +#endif + +ImplMutex::ImplMutex() +{ + impl = new ImplMutex::Impl; +} + +ImplMutex::~ImplMutex() +{ + delete impl; + impl = 0; +} + +void ImplMutex::lock() { impl->lock(); } +void ImplMutex::unlock() { impl->unlock(); } +bool ImplMutex::trylock() { return impl->trylock(); } + +static int LockCallBack(void **mutex, AVLockOp op) +{ + switch (op) { - #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 13, 0) + case AV_LOCK_CREATE: + *mutex = reinterpret_cast(new ImplMutex()); + if (!*mutex) + return 1; + break; + + case AV_LOCK_OBTAIN: + reinterpret_cast(*mutex)->lock(); + break; + + case AV_LOCK_RELEASE: + reinterpret_cast(*mutex)->unlock(); + break; + + case AV_LOCK_DESTROY: + ImplMutex* cv_mutex = reinterpret_cast(*mutex); + delete cv_mutex; + cv_mutex = NULL; + break; + } + return 0; +} + +static ImplMutex _InternalFFMpegRegister_mutex; + +class InternalFFMpegRegister +{ +public: + static void Register() + { + _InternalFFMpegRegister_mutex.lock(); + static InternalFFMpegRegister init; + _InternalFFMpegRegister_mutex.unlock(); + } + + ~InternalFFMpegRegister() + { + av_lockmgr_register(NULL); + } + +private: + InternalFFMpegRegister() + { +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 13, 0) avformat_network_init(); - #endif +#endif /* register all codecs, demux and protocols */ av_register_all(); - av_log_set_level(AV_LOG_ERROR); + /* register a callback function for synchronization */ + av_lockmgr_register(&LockCallBack); - initialized = true; + av_log_set_level(AV_LOG_ERROR); } -} +}; bool CvCapture_FFMPEG::open( const char* _filename ) { - icvInitFFMPEG_internal(); - + InternalFFMpegRegister::Register(); unsigned i; bool valid = false; @@ -361,7 +512,8 @@ bool CvCapture_FFMPEG::open( const char* _filename ) int err = av_open_input_file(&ic, _filename, NULL, 0, NULL); #endif - if (err < 0) { + if (err < 0) + { CV_WARN("Error opening file"); goto exit_func; } @@ -371,7 +523,8 @@ bool CvCapture_FFMPEG::open( const char* _filename ) #else av_find_stream_info(ic); #endif - if (err < 0) { + if (err < 0) + { CV_WARN("Could not find codec parameters"); goto exit_func; } @@ -393,7 +546,8 @@ bool CvCapture_FFMPEG::open( const char* _filename ) #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #endif - if( AVMEDIA_TYPE_VIDEO == enc->codec_type && video_stream < 0) { + if( AVMEDIA_TYPE_VIDEO == enc->codec_type && video_stream < 0) + { AVCodec *codec = avcodec_find_decoder(enc->codec_id); if (!codec || #if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0) @@ -401,7 +555,8 @@ bool CvCapture_FFMPEG::open( const char* _filename ) #else avcodec_open(enc, codec) #endif - < 0) goto exit_func; + < 0) + goto exit_func; video_stream = i; video_st = ic->streams[i]; @@ -1275,7 +1430,7 @@ void CvVideoWriter_FFMPEG::close() bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, double fps, int width, int height, bool is_color ) { - icvInitFFMPEG_internal(); + InternalFFMpegRegister::Register(); CodecID codec_id = CODEC_ID_NONE; int err, codec_pix_fmt; @@ -1495,6 +1650,7 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, frame_width = width; frame_height = height; ok = true; + return true; } @@ -1506,6 +1662,7 @@ CvCapture_FFMPEG* cvCreateFileCapture_FFMPEG( const char* filename ) capture->init(); if( capture->open( filename )) return capture; + capture->close(); free(capture); return 0; @@ -1554,7 +1711,6 @@ CvVideoWriter_FFMPEG* cvCreateVideoWriter_FFMPEG( const char* filename, int four return 0; } - void cvReleaseVideoWriter_FFMPEG( CvVideoWriter_FFMPEG** writer ) { if( writer && *writer ) @@ -1741,15 +1897,12 @@ AVStream* OutputMediaStream_FFMPEG::addVideoStream(AVFormatContext *oc, CodecID bool OutputMediaStream_FFMPEG::open(const char* fileName, int width, int height, double fps) { + InternalFFMpegRegister::Register(); + fmt_ = 0; oc_ = 0; video_st_ = 0; - // tell FFMPEG to register codecs - av_register_all(); - - av_log_set_level(AV_LOG_ERROR); - // auto detect the output format from the name and fourcc code #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 2, 0) fmt_ = av_guess_format(NULL, fileName, NULL); @@ -1920,6 +2073,8 @@ private: bool InputMediaStream_FFMPEG::open(const char* fileName, int* codec, int* chroma_format, int* width, int* height) { + InternalFFMpegRegister::Register(); + int err; ctx_ = 0; @@ -1930,11 +2085,6 @@ bool InputMediaStream_FFMPEG::open(const char* fileName, int* codec, int* chroma avformat_network_init(); #endif - // register all codecs, demux and protocols - av_register_all(); - - av_log_set_level(AV_LOG_ERROR); - #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 6, 0) err = avformat_open_input(&ctx_, fileName, 0, 0); #else @@ -2054,7 +2204,7 @@ bool InputMediaStream_FFMPEG::read(unsigned char** data, int* size, int* endOfFi if (ret < 0) { - if (ret == AVERROR_EOF) + if (ret == (int)AVERROR_EOF) *endOfFile = true; return false; } diff --git a/modules/highgui/src/cap_gstreamer.cpp b/modules/highgui/src/cap_gstreamer.cpp index e8cf21326..60ba8852e 100644 --- a/modules/highgui/src/cap_gstreamer.cpp +++ b/modules/highgui/src/cap_gstreamer.cpp @@ -65,7 +65,24 @@ #define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__) #endif -static bool isInited = false; +static cv::Mutex gst_initializer_mutex; + +class gst_initializer +{ +public: + static void init() + { + gst_initializer_mutex.lock(); + static gst_initializer init; + gst_initializer_mutex.unlock(); + } +private: + gst_initializer() + { + gst_init(NULL, NULL); + } +}; + class CvCapture_GStreamer : public CvCapture { public: @@ -298,16 +315,18 @@ bool CvCapture_GStreamer::open( int type, const char* filename ) __BEGIN__; - if(!isInited) { + gst_initializer::init(); + +// if(!isInited) { // printf("gst_init\n"); - gst_init (NULL, NULL); +// gst_init (NULL, NULL); // gst_debug_set_active(TRUE); // gst_debug_set_colored(TRUE); // gst_debug_set_default_threshold(GST_LEVEL_WARNING); - isInited = true; - } +// isInited = true; +// } bool stream = false; bool manualpipeline = false; char *uri = NULL; @@ -477,10 +496,11 @@ bool CvVideoWriter_GStreamer::open( const char * filename, int fourcc, encit=encs.find(fourcc); if (encit==encs.end()) CV_ERROR( CV_StsUnsupportedFormat,"Gstreamer Opencv backend doesn't support this codec acutally."); - if(!isInited) { - gst_init (NULL, NULL); - isInited = true; - } +// if(!isInited) { +// gst_init (NULL, NULL); +// isInited = true; +// } + gst_initializer::init(); close(); source=gst_element_factory_make("appsrc",NULL); file=gst_element_factory_make("filesink", NULL); diff --git a/modules/highgui/test/test_ffmpeg.cpp b/modules/highgui/test/test_ffmpeg.cpp index fb32e015f..ed06a2cbd 100644 --- a/modules/highgui/test/test_ffmpeg.cpp +++ b/modules/highgui/test/test_ffmpeg.cpp @@ -43,11 +43,12 @@ #include "test_precomp.hpp" #include "opencv2/highgui/highgui.hpp" +using namespace cv; + #ifdef HAVE_FFMPEG #include "ffmpeg_codecs.hpp" -using namespace cv; using namespace std; class CV_FFmpegWriteBigVideoTest : public cvtest::BaseTest @@ -118,11 +119,11 @@ 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)); + const int coeff = cvRound(min(frame_s.width, frame_s.height)/(fps0 * time_sec)); for (int i = 0 ; i < static_cast(fps * time_sec); i++ ) { - //circle(img, Point2i(img_c / 2, img_r / 2), cv::min(img_r, img_c) / 2 * (i + 1), Scalar(255, 0, 0, 0), 2); + //circle(img, Point2i(img_c / 2, img_r / 2), min(img_r, img_c) / 2 * (i + 1), Scalar(255, 0, 0, 0), 2); rectangle(img, Point2i(coeff * i, coeff * i), Point2i(coeff * (i + 1), coeff * (i + 1)), Scalar::all(255 * (1.0 - static_cast(i) / (fps * time_sec * 2) )), -1); writer << img; @@ -174,3 +175,221 @@ public: TEST(Highgui_Video, ffmpeg_image) { CV_FFmpegReadImageTest test; test.safe_run(); } #endif + +#if defined(HAVE_FFMPEG) || defined(WIN32) || defined(_WIN32) + +//////////////////////////////// Parallel VideoWriters and VideoCaptures //////////////////////////////////// + +class CreateVideoWriterInvoker : + public ParallelLoopBody +{ +public: + const static Size FrameSize; + static std::string TmpDirectory; + + CreateVideoWriterInvoker(std::vector& _writers, std::vector& _files) : + ParallelLoopBody(), writers(&_writers), files(&_files) + { + } + + virtual void operator() (const Range& range) const + { + for (int i = range.start; i != range.end; ++i) + { + std::ostringstream stream; + stream << i << ".avi"; + std::string fileName = tempfile(stream.str().c_str()); + + files->operator[](i) = fileName; + writers->operator[](i) = new VideoWriter(fileName, CV_FOURCC('X','V','I','D'), 25.0f, FrameSize); + + CV_Assert(writers->operator[](i)->isOpened()); + } + } + + +private: + std::vector* writers; + std::vector* files; +}; + +std::string CreateVideoWriterInvoker::TmpDirectory; +const Size CreateVideoWriterInvoker::FrameSize(1020, 900); + +class WriteVideo_Invoker : + public ParallelLoopBody +{ +public: + enum { FrameCount = 300 }; + + static const Scalar ObjectColor; + static const Point Center; + + WriteVideo_Invoker(const std::vector& _writers) : + ParallelLoopBody(), writers(&_writers) + { + } + + static void GenerateFrame(Mat& frame, unsigned int i) + { + frame = Scalar::all(i % 255); + + std::string text = to_string(i); + putText(frame, text, Point(50, Center.y), FONT_HERSHEY_SIMPLEX, 5.0, ObjectColor, 5, CV_AA); + circle(frame, Center, i + 2, ObjectColor, 2, CV_AA); + } + + virtual void operator() (const Range& range) const + { + CV_Assert((range.start + 1) == range.end); + VideoWriter* writer = writers->operator[](range.start); + CV_Assert(writer != NULL); + CV_Assert(writer->isOpened()); + + Mat frame(CreateVideoWriterInvoker::FrameSize, CV_8UC3); + for (unsigned int i = 0; i < FrameCount; ++i) + { + GenerateFrame(frame, i); + writer->operator<< (frame); + } + } + +protected: + static std::string to_string(unsigned int i) + { + std::stringstream stream(std::ios::out); + stream << "frame #" << i; + return stream.str(); + } + +private: + const std::vector* writers; +}; + +const Scalar WriteVideo_Invoker::ObjectColor(Scalar::all(0)); +const Point WriteVideo_Invoker::Center(CreateVideoWriterInvoker::FrameSize.height / 2, + CreateVideoWriterInvoker::FrameSize.width / 2); + +class CreateVideoCaptureInvoker : + public ParallelLoopBody +{ +public: + CreateVideoCaptureInvoker(std::vector& _readers, const std::vector& _files) : + ParallelLoopBody(), readers(&_readers), files(&_files) + { + } + + virtual void operator() (const Range& range) const + { + for (int i = range.start; i != range.end; ++i) + { + readers->operator[](i) = new VideoCapture(files->operator[](i)); + CV_Assert(readers->operator[](i)->isOpened()); + } + } +private: + std::vector* readers; + const std::vector* files; +}; + +class ReadImageAndTest : + public ParallelLoopBody +{ +public: + ReadImageAndTest(const std::vector& _readers, cvtest::TS* _ts) : + ParallelLoopBody(), readers(&_readers), ts(_ts) + { + } + + virtual void operator() (const Range& range) const + { + CV_Assert(range.start + 1 == range.end); + VideoCapture* capture = readers->operator[](range.start); + CV_Assert(capture != NULL); + CV_Assert(capture->isOpened()); + + const static double eps = 23.0; + unsigned int frameCount = static_cast(capture->get(CV_CAP_PROP_FRAME_COUNT)); + CV_Assert(frameCount == WriteVideo_Invoker::FrameCount); + Mat reference(CreateVideoWriterInvoker::FrameSize, CV_8UC3); + + for (unsigned int i = 0; i < frameCount && next; ++i) + { + Mat actual; + (*capture) >> actual; + + WriteVideo_Invoker::GenerateFrame(reference, i); + + EXPECT_EQ(reference.cols, actual.cols); + EXPECT_EQ(reference.rows, actual.rows); + EXPECT_EQ(reference.depth(), actual.depth()); + EXPECT_EQ(reference.channels(), actual.channels()); + + double psnr = PSNR(actual, reference); + if (psnr < eps) + { +#define SUM cvtest::TS::SUMMARY + ts->printf(SUM, "\nPSNR: %lf\n", psnr); + ts->printf(SUM, "Video #: %d\n", range.start); + ts->printf(SUM, "Frame #: %d\n", i); +#undef SUM + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + ts->set_gtest_status(); + + Mat diff; + absdiff(actual, reference, diff); + + EXPECT_EQ(countNonZero(diff.reshape(1) > 1), 0); + + next = false; + } + } + } + + static bool next; + +private: + const std::vector* readers; + cvtest::TS* ts; +}; + +bool ReadImageAndTest::next; + +TEST(Highgui_Video_parallel_writers_and_readers, accuracy) +{ + const unsigned int threadsCount = 4; + cvtest::TS* ts = cvtest::TS::ptr(); + + // creating VideoWriters + std::vector writers(threadsCount); + Range range(0, threadsCount); + std::vector files(threadsCount); + CreateVideoWriterInvoker invoker1(writers, files); + parallel_for_(range, invoker1); + + // write a video + parallel_for_(range, WriteVideo_Invoker(writers)); + + // deleting the writers + for (std::vector::iterator i = writers.begin(), end = writers.end(); i != end; ++i) + delete *i; + writers.clear(); + + std::vector readers(threadsCount); + CreateVideoCaptureInvoker invoker2(readers, files); + parallel_for_(range, invoker2); + + ReadImageAndTest::next = true; + + parallel_for_(range, ReadImageAndTest(readers, ts)); + + // deleting tmp video files + for (std::vector::const_iterator i = files.begin(), end = files.end(); i != end; ++i) + { + int code = remove(i->c_str()); + if (code == 1) + std::cerr << "Couldn't delete " << *i << std::endl; + } +} + +#endif