From 1f3043f618ead56b815598d77021d1090a5cea86 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 3 Jun 2015 17:01:45 +0300 Subject: [PATCH] videoio: VideoWriter H264/.mp4 support via ffmpeg/libav --- modules/videoio/src/cap_ffmpeg_impl.hpp | 63 ++++++++++++++++ modules/videoio/test/test_ffmpeg.cpp | 95 +++++++++++++++++-------- 2 files changed, 128 insertions(+), 30 deletions(-) diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 1504d2ae6..96b8b6890 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -1540,6 +1540,30 @@ void CvVideoWriter_FFMPEG::close() init(); } +#define CV_PRINTABLE_CHAR(ch) ((ch) < 32 ? '?' : (ch)) +#define CV_TAG_TO_PRINTABLE_CHAR4(tag) CV_PRINTABLE_CHAR((tag) & 255), CV_PRINTABLE_CHAR(((tag) >> 8) & 255), CV_PRINTABLE_CHAR(((tag) >> 16) & 255), CV_PRINTABLE_CHAR(((tag) >> 24) & 255) + +static inline bool cv_ff_codec_tag_match(const AVCodecTag *tags, enum AVCodecID id, unsigned int tag) +{ + while (tags->id != AV_CODEC_ID_NONE) + { + if (tags->id == id && tags->tag == tag) + return true; + tags++; + } + return false; +} +static inline bool cv_ff_codec_tag_list_match(const AVCodecTag *const *tags, enum AVCodecID id, unsigned int tag) +{ + int i; + for (i = 0; tags && tags[i]; i++) { + bool res = cv_ff_codec_tag_match(tags[i], id, tag); + if (res) + return res; + } + return false; +} + /// Create a video writer object that uses FFMPEG bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, double fps, int width, int height, bool is_color ) @@ -1587,6 +1611,45 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, #if LIBAVCODEC_VERSION_INT<((51<<16)+(49<<8)+0) if( (codec_id = codec_get_bmp_id( fourcc )) == CV_CODEC(CODEC_ID_NONE) ) return false; +#elif LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(54, 1, 0) +// APIchnages: +// 2012-01-31 - dd6d3b0 - lavf 54.01.0 +// Add avformat_get_riff_video_tags() and avformat_get_riff_audio_tags(). + if( (codec_id = av_codec_get_id(fmt->codec_tag, fourcc)) == CV_CODEC(CODEC_ID_NONE) ) + { + const struct AVCodecTag * fallback_tags[] = { + avformat_get_riff_video_tags(), +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 25, 100) +// APIchanges: ffmpeg only +// 2014-01-19 - 1a193c4 - lavf 55.25.100 - avformat.h +// Add avformat_get_mov_video_tags() and avformat_get_mov_audio_tags(). + // TODO ffmpeg only, need to skip libav: avformat_get_mov_video_tags(), +#endif + codec_bmp_tags, NULL }; + if( (codec_id = av_codec_get_id(fallback_tags, fourcc)) == CV_CODEC(CODEC_ID_NONE) ) + { + fflush(stdout); + fprintf(stderr, "OpenCV: FFMPEG: tag 0x%08x/'%c%c%c%c' is not found (format '%s / %s')'\n", + fourcc, CV_TAG_TO_PRINTABLE_CHAR4(fourcc), + fmt->name, fmt->long_name); + return false; + } + } + // validate tag + if (cv_ff_codec_tag_list_match(fmt->codec_tag, codec_id, fourcc) == false) + { + fflush(stdout); + fprintf(stderr, "OpenCV: FFMPEG: tag 0x%08x/'%c%c%c%c' is not supported with codec id %d and format '%s / %s'\n", + fourcc, CV_TAG_TO_PRINTABLE_CHAR4(fourcc), + codec_id, fmt->name, fmt->long_name); + int supported_tag; + if( (supported_tag = av_codec_get_tag(fmt->codec_tag, codec_id)) != 0 ) + { + fprintf(stderr, "OpenCV: FFMPEG: fallback to use tag 0x%08x/'%c%c%c%c'\n", + supported_tag, CV_TAG_TO_PRINTABLE_CHAR4(supported_tag)); + fourcc = supported_tag; + } + } #else const struct AVCodecTag * tags[] = { codec_bmp_tags, NULL}; if( (codec_id = av_codec_get_id(tags, fourcc)) == CV_CODEC(CODEC_ID_NONE) ) diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index 353ca19de..3ccce8194 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -49,8 +49,28 @@ using namespace cv; using namespace std; +static const char* AVI_EXT = ".avi"; +static const char* MP4_EXT = ".mp4"; + class CV_FFmpegWriteBigVideoTest : public cvtest::BaseTest { + struct TestFormatEntry { + int tag; + const char* ext; + bool required; + }; + + static long int getFileSize(string filename) + { + FILE *p_file = NULL; + p_file = fopen(filename.c_str(), "rb"); + if (p_file == NULL) + return -1; + fseek(p_file, 0, SEEK_END); + long int size = ftell(p_file); + fclose(p_file); + return size; + } public: void run(int) { @@ -59,36 +79,35 @@ public: const double fps0 = 15; const double time_sec = 1; - const int tags[] = { - 0, - //VideoWriter::fourcc('D', 'I', 'V', '3'), - //VideoWriter::fourcc('D', 'I', 'V', 'X'), - VideoWriter::fourcc('D', 'X', '5', '0'), - VideoWriter::fourcc('F', 'L', 'V', '1'), - VideoWriter::fourcc('H', '2', '6', '1'), - VideoWriter::fourcc('H', '2', '6', '3'), - VideoWriter::fourcc('I', '4', '2', '0'), - //VideoWriter::fourcc('j', 'p', 'e', 'g'), - VideoWriter::fourcc('M', 'J', 'P', 'G'), - VideoWriter::fourcc('m', 'p', '4', 'v'), - VideoWriter::fourcc('M', 'P', 'E', 'G'), - //VideoWriter::fourcc('W', 'M', 'V', '1'), - //VideoWriter::fourcc('W', 'M', 'V', '2'), - VideoWriter::fourcc('X', 'V', 'I', 'D'), - //VideoWriter::fourcc('Y', 'U', 'Y', '2'), + const TestFormatEntry entries[] = { + {0, AVI_EXT, true}, + //{VideoWriter::fourcc('D', 'I', 'V', '3'), AVI_EXT, true}, + //{VideoWriter::fourcc('D', 'I', 'V', 'X'), AVI_EXT, true}, + {VideoWriter::fourcc('D', 'X', '5', '0'), AVI_EXT, true}, + {VideoWriter::fourcc('F', 'L', 'V', '1'), AVI_EXT, true}, + {VideoWriter::fourcc('H', '2', '6', '1'), AVI_EXT, true}, + {VideoWriter::fourcc('H', '2', '6', '3'), AVI_EXT, true}, + {VideoWriter::fourcc('I', '4', '2', '0'), AVI_EXT, true}, + //{VideoWriter::fourcc('j', 'p', 'e', 'g'), AVI_EXT, true}, + {VideoWriter::fourcc('M', 'J', 'P', 'G'), AVI_EXT, true}, + {VideoWriter::fourcc('m', 'p', '4', 'v'), AVI_EXT, true}, + {VideoWriter::fourcc('M', 'P', 'E', 'G'), AVI_EXT, true}, + //{VideoWriter::fourcc('W', 'M', 'V', '1'), AVI_EXT, true}, + //{VideoWriter::fourcc('W', 'M', 'V', '2'), AVI_EXT, true}, + {VideoWriter::fourcc('X', 'V', 'I', 'D'), AVI_EXT, true}, + //{VideoWriter::fourcc('Y', 'U', 'Y', '2'), AVI_EXT, true}, + {VideoWriter::fourcc('H', '2', '6', '4'), MP4_EXT, false} }; - const size_t n = sizeof(tags)/sizeof(tags[0]); - - bool created = false; + const size_t n = sizeof(entries)/sizeof(entries[0]); for (size_t j = 0; j < n; ++j) { - int tag = tags[j]; - stringstream s; - s << tag; + int tag = entries[j].tag; + const char* ext = entries[j].ext; + string s = cv::format("%08x%s", tag, ext); - const string filename = tempfile((s.str()+".avi").c_str()); + const string filename = tempfile(s.c_str()); try { @@ -113,11 +132,12 @@ public: if (writer.isOpened() == false) { - ts->printf(ts->LOG, "\n\nFile name: %s\n", filename.c_str()); - ts->printf(ts->LOG, "Codec id: %d Codec tag: %c%c%c%c\n", j, + fprintf(stderr, "\n\nFile name: %s\n", filename.c_str()); + fprintf(stderr, "Codec id: %d Codec tag: %c%c%c%c\n", (int)j, tag & 255, (tag >> 8) & 255, (tag >> 16) & 255, (tag >> 24) & 255); - ts->printf(ts->LOG, "Error: cannot create video file."); - ts->set_failed_test_info(ts->FAIL_INVALID_OUTPUT); + fprintf(stderr, "Error: cannot create video file."); + if (entries[j].required) + ts->set_failed_test_info(ts->FAIL_INVALID_OUTPUT); } else { @@ -133,8 +153,23 @@ public: } writer.release(); - if (!created) created = true; - else remove(filename.c_str()); + long int sz = getFileSize(filename); + if (sz < 0) + { + fprintf(stderr, "ERROR: File name: %s was not created\n", filename.c_str()); + if (entries[j].required) + ts->set_failed_test_info(ts->FAIL_INVALID_OUTPUT); + } + else + { + if (sz < 8192) + { + fprintf(stderr, "ERROR: File name: %s is very small (data write problems?)\n", filename.c_str()); + if (entries[j].required) + ts->set_failed_test_info(ts->FAIL_INVALID_OUTPUT); + } + remove(filename.c_str()); + } } } catch(...)