From df8f61877e740443e36e3b1afb791f12425ce05c Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Wed, 14 Feb 2018 18:04:54 +0300 Subject: [PATCH] Added avi-container with tests --- modules/videoio/CMakeLists.txt | 2 +- .../opencv2/videoio/container_avi.private.hpp | 191 ++++ modules/videoio/src/cap_mjpeg_decoder.cpp | 720 +----------- modules/videoio/src/cap_mjpeg_encoder.cpp | 620 +++------- modules/videoio/src/container_avi.cpp | 1017 +++++++++++++++++ modules/videoio/test/test_container_avi.cpp | 87 ++ modules/videoio/test/test_precomp.hpp | 14 + modules/videoio/test/test_video_io.cpp | 24 +- 8 files changed, 1462 insertions(+), 1213 deletions(-) create mode 100644 modules/videoio/include/opencv2/videoio/container_avi.private.hpp create mode 100644 modules/videoio/src/container_avi.cpp create mode 100644 modules/videoio/test/test_container_avi.cpp diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index 0ded292f0c..3562bfe05f 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -19,12 +19,12 @@ endif() set(videoio_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp ) - set(videoio_srcs ${CMAKE_CURRENT_LIST_DIR}/src/cap.cpp ${CMAKE_CURRENT_LIST_DIR}/src/cap_images.cpp ${CMAKE_CURRENT_LIST_DIR}/src/cap_mjpeg_encoder.cpp ${CMAKE_CURRENT_LIST_DIR}/src/cap_mjpeg_decoder.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/container_avi.cpp ) file(GLOB videoio_ext_hdrs diff --git a/modules/videoio/include/opencv2/videoio/container_avi.private.hpp b/modules/videoio/include/opencv2/videoio/container_avi.private.hpp new file mode 100644 index 0000000000..212a892699 --- /dev/null +++ b/modules/videoio/include/opencv2/videoio/container_avi.private.hpp @@ -0,0 +1,191 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef CONTAINER_AVI_HPP +#define CONTAINER_AVI_HPP + +#ifndef __OPENCV_BUILD +# error this is a private header which should not be used from outside of the OpenCV library +#endif + +#include "opencv2/core/cvdef.h" +#include "opencv2/videoio/videoio_c.h" +#include + +namespace cv +{ + +/* +AVI struct: + +RIFF ('AVI ' + LIST ('hdrl' + 'avih'(
) + LIST ('strl' + 'strh'() + 'strf'() + [ 'strd'() ] + [ 'strn'() ] + [ 'indx'() ] + ... + ) + [LIST ('strl' ...)] + [LIST ('strl' ...)] + ... + [LIST ('odml' + 'dmlh'() + ... + ) + ] + ... + ) + [LIST ('INFO' ...)] + [JUNK] + LIST ('movi' + {{xxdb|xxdc|xxpc|xxwb}() | LIST ('rec ' + {xxdb|xxdc|xxpc|xxwb}() + {xxdb|xxdc|xxpc|xxwb}() + ... + ) + ... + } + ... + ) + ['idx1' () ] + ) + + {xxdb|xxdc|xxpc|xxwb} + xx - stream number: 00, 01, 02, ... + db - uncompressed video frame + dc - commpressed video frame + pc - palette change + wb - audio frame + + JUNK section may pad any data section and must be ignored +*/ + +typedef std::deque< std::pair > frame_list; +typedef frame_list::iterator frame_iterator; +struct RiffChunk; +struct RiffList; +class VideoInputStream; +enum Codecs { MJPEG }; + +//Represents single MJPEG video stream within single AVI/AVIX entry +//Multiple video streams within single AVI/AVIX entry are not supported +//ODML index is not supported +class CV_EXPORTS AVIReadContainer +{ +public: + AVIReadContainer(); + + void initStream(const String& filename); + void initStream(Ptr m_file_stream_); + + void close(); + //stores founded frames in m_frame_list which can be accessed via getFrames + bool parseAvi(Codecs codec_) { return parseAviWithFrameList(m_frame_list, codec_); } + //stores founded frames in in_frame_list. getFrames() would return empty list + bool parseAvi(frame_list& in_frame_list, Codecs codec_) { return parseAviWithFrameList(in_frame_list, codec_); } + size_t getFramesCount() { return m_frame_list.size(); } + frame_list& getFrames() { return m_frame_list; } + unsigned int getWidth() { return m_width; } + unsigned int getHeight() { return m_height; } + double getFps() { return m_fps; } + std::vector readFrame(frame_iterator it); + bool parseRiff(frame_list &m_mjpeg_frames); + +protected: + + bool parseAviWithFrameList(frame_list& in_frame_list, Codecs codec_); + void skipJunk(RiffChunk& chunk); + void skipJunk(RiffList& list); + bool parseHdrlList(Codecs codec_); + bool parseIndex(unsigned int index_size, frame_list& in_frame_list); + bool parseMovi(frame_list& in_frame_list) + { + //not implemented + in_frame_list.empty(); + return true; + } + bool parseStrl(char stream_id, Codecs codec_); + bool parseInfo() + { + //not implemented + return true; + } + + void printError(RiffList& list, unsigned int expected_fourcc); + + void printError(RiffChunk& chunk, unsigned int expected_fourcc); + + Ptr m_file_stream; + unsigned int m_stream_id; + unsigned long long int m_movi_start; + unsigned long long int m_movi_end; + frame_list m_frame_list; + unsigned int m_width; + unsigned int m_height; + double m_fps; + bool m_is_indx_present; +}; + +enum { COLORSPACE_GRAY=0, COLORSPACE_RGBA=1, COLORSPACE_BGR=2, COLORSPACE_YUV444P=3 }; +enum StreamType { db, dc, pc, wb }; +class BitStream; + +// {xxdb|xxdc|xxpc|xxwb} +// xx - stream number: 00, 01, 02, ... +// db - uncompressed video frame +// dc - commpressed video frame +// pc - palette change +// wb - audio frame + + +class CV_EXPORTS AVIWriteContainer +{ +public: + AVIWriteContainer(); + ~AVIWriteContainer(); + + bool initContainer(const String& filename, double fps, Size size, bool iscolor); + void startWriteAVI(int stream_count); + void writeStreamHeader(Codecs codec_); + void startWriteChunk(int fourcc); + void endWriteChunk(); + + int getAVIIndex(int stream_number, StreamType strm_type); + void writeIndex(int stream_number, StreamType strm_type); + void finishWriteAVI(); + + bool isOpenedStream() const; + bool isEmptyFrameOffset() const { return frameOffset.empty(); } + int getWidth() const { return width; } + int getHeight() const { return height; } + int getChannels() const { return channels; } + size_t getMoviPointer() const { return moviPointer; } + size_t getStreamPos() const; + + void pushFrameOffset(size_t elem) { frameOffset.push_back(elem); } + void pushFrameSize(size_t elem) { frameSize.push_back(elem); } + bool isEmptyFrameSize() const { return frameSize.empty(); } + size_t atFrameSize(size_t i) const { return frameSize[i]; } + size_t countFrameSize() const { return frameSize.size(); } + void jputStreamShort(int val); + void putStreamBytes(const uchar* buf, int count); + void putStreamByte(int val); + void jputStream(unsigned currval); + void jflushStream(unsigned currval, int bitIdx); + +private: + Ptr strm; + int outfps; + int width, height, channels; + size_t moviPointer; + std::vector frameOffset, frameSize, AVIChunkSizeIndex, frameNumIndexes; +}; + +} + +#endif //CONTAINER_AVI_HPP diff --git a/modules/videoio/src/cap_mjpeg_decoder.cpp b/modules/videoio/src/cap_mjpeg_decoder.cpp index 74a7bf4683..cd2d841d04 100644 --- a/modules/videoio/src/cap_mjpeg_decoder.cpp +++ b/modules/videoio/src/cap_mjpeg_decoder.cpp @@ -40,652 +40,11 @@ //M*/ #include "precomp.hpp" -#include -#include +#include "opencv2/videoio/container_avi.private.hpp" namespace cv { -const uint32_t RIFF_CC = CV_FOURCC('R','I','F','F'); -const uint32_t LIST_CC = CV_FOURCC('L','I','S','T'); -const uint32_t HDRL_CC = CV_FOURCC('h','d','r','l'); -const uint32_t AVIH_CC = CV_FOURCC('a','v','i','h'); -const uint32_t STRL_CC = CV_FOURCC('s','t','r','l'); -const uint32_t STRH_CC = CV_FOURCC('s','t','r','h'); -const uint32_t VIDS_CC = CV_FOURCC('v','i','d','s'); -const uint32_t MJPG_CC = CV_FOURCC('M','J','P','G'); -const uint32_t MOVI_CC = CV_FOURCC('m','o','v','i'); -const uint32_t IDX1_CC = CV_FOURCC('i','d','x','1'); -const uint32_t AVI_CC = CV_FOURCC('A','V','I',' '); -const uint32_t AVIX_CC = CV_FOURCC('A','V','I','X'); -const uint32_t JUNK_CC = CV_FOURCC('J','U','N','K'); -const uint32_t INFO_CC = CV_FOURCC('I','N','F','O'); - -String fourccToString(uint32_t fourcc); - -String fourccToString(uint32_t fourcc) -{ - return format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255); -} - -#ifndef DWORD -typedef uint32_t DWORD; -#endif -#ifndef WORD -typedef uint16_t WORD; -#endif -#ifndef LONG -typedef int32_t LONG; -#endif - -#pragma pack(push, 1) -struct AviMainHeader -{ - DWORD dwMicroSecPerFrame; // The period between video frames - DWORD dwMaxBytesPerSec; // Maximum data rate of the file - DWORD dwReserved1; // 0 - DWORD dwFlags; // 0x10 AVIF_HASINDEX: The AVI file has an idx1 chunk containing an index at the end of the file. - DWORD dwTotalFrames; // Field of the main header specifies the total number of frames of data in file. - DWORD dwInitialFrames; // Is used for interleaved files - DWORD dwStreams; // Specifies the number of streams in the file. - DWORD dwSuggestedBufferSize; // Field specifies the suggested buffer size forreading the file - DWORD dwWidth; // Fields specify the width of the AVIfile in pixels. - DWORD dwHeight; // Fields specify the height of the AVIfile in pixels. - DWORD dwReserved[4]; // 0, 0, 0, 0 -}; - -struct AviStreamHeader -{ - uint32_t fccType; // 'vids', 'auds', 'txts'... - uint32_t fccHandler; // "cvid", "DIB " - DWORD dwFlags; // 0 - DWORD dwPriority; // 0 - DWORD dwInitialFrames; // 0 - DWORD dwScale; // 1 - DWORD dwRate; // Fps (dwRate - frame rate for video streams) - DWORD dwStart; // 0 - DWORD dwLength; // Frames number (playing time of AVI file as defined by scale and rate) - DWORD dwSuggestedBufferSize; // For reading the stream - DWORD dwQuality; // -1 (encoding quality. If set to -1, drivers use the default quality value) - DWORD dwSampleSize; // 0 means that each frame is in its own chunk - struct { - short int left; - short int top; - short int right; - short int bottom; - } rcFrame; // If stream has a different size than dwWidth*dwHeight(unused) -}; - -struct AviIndex -{ - DWORD ckid; - DWORD dwFlags; - DWORD dwChunkOffset; - DWORD dwChunkLength; -}; - -struct BitmapInfoHeader -{ - DWORD biSize; // Write header size of BITMAPINFO header structure - LONG biWidth; // width in pixels - LONG biHeight; // height in pixels - WORD biPlanes; // Number of color planes in which the data is stored - WORD biBitCount; // Number of bits per pixel - DWORD biCompression; // Type of compression used (uncompressed: NO_COMPRESSION=0) - DWORD biSizeImage; // Image Buffer. Quicktime needs 3 bytes also for 8-bit png - // (biCompression==NO_COMPRESSION)?0:xDim*yDim*bytesPerPixel; - LONG biXPelsPerMeter; // Horizontal resolution in pixels per meter - LONG biYPelsPerMeter; // Vertical resolution in pixels per meter - DWORD biClrUsed; // 256 (color table size; for 8-bit only) - DWORD biClrImportant; // Specifies that the first x colors of the color table. Are important to the DIB. -}; - -struct RiffChunk -{ - uint32_t m_four_cc; - uint32_t m_size; -}; - -struct RiffList -{ - uint32_t m_riff_or_list_cc; - uint32_t m_size; - uint32_t m_list_type_cc; -}; - -#pragma pack(pop) - -class MjpegInputStream -{ -public: - MjpegInputStream(); - MjpegInputStream(const String& filename); - ~MjpegInputStream(); - MjpegInputStream& read(char*, uint64_t); - MjpegInputStream& seekg(uint64_t); - uint64_t tellg(); - bool isOpened() const; - bool open(const String& filename); - void close(); - operator bool(); - -private: - bool m_is_valid; - FILE* m_f; -}; - -MjpegInputStream::MjpegInputStream(): m_is_valid(false), m_f(0) -{ -} - -MjpegInputStream::MjpegInputStream(const String& filename): m_is_valid(false), m_f(0) -{ - open(filename); -} - -bool MjpegInputStream::isOpened() const -{ - return m_f != 0; -} - -bool MjpegInputStream::open(const String& filename) -{ - close(); - - m_f = fopen(filename.c_str(), "rb"); - - m_is_valid = isOpened(); - - return m_is_valid; -} - -void MjpegInputStream::close() -{ - if(isOpened()) - { - m_is_valid = false; - - fclose(m_f); - m_f = 0; - } -} - -MjpegInputStream& MjpegInputStream::read(char* buf, uint64_t count) -{ - if(isOpened()) - { - m_is_valid = (count == fread((void*)buf, 1, (size_t)count, m_f)); - } - - return *this; -} - -MjpegInputStream& MjpegInputStream::seekg(uint64_t pos) -{ - m_is_valid = (fseek(m_f, (long)pos, SEEK_SET) == 0); - - return *this; -} - -uint64_t MjpegInputStream::tellg() -{ - return ftell(m_f); -} - -MjpegInputStream::operator bool() -{ - return m_is_valid; -} - -MjpegInputStream::~MjpegInputStream() -{ - close(); -} - -MjpegInputStream& operator >> (MjpegInputStream& is, AviMainHeader& avih); -MjpegInputStream& operator >> (MjpegInputStream& is, AviStreamHeader& strh); -MjpegInputStream& operator >> (MjpegInputStream& is, BitmapInfoHeader& bmph); -MjpegInputStream& operator >> (MjpegInputStream& is, RiffList& riff_list); -MjpegInputStream& operator >> (MjpegInputStream& is, RiffChunk& riff_chunk); -MjpegInputStream& operator >> (MjpegInputStream& is, AviIndex& idx1); - -MjpegInputStream& operator >> (MjpegInputStream& is, AviMainHeader& avih) -{ - is.read((char*)(&avih), sizeof(AviMainHeader)); - return is; -} - -MjpegInputStream& operator >> (MjpegInputStream& is, AviStreamHeader& strh) -{ - is.read((char*)(&strh), sizeof(AviStreamHeader)); - return is; -} - -MjpegInputStream& operator >> (MjpegInputStream& is, BitmapInfoHeader& bmph) -{ - is.read((char*)(&bmph), sizeof(BitmapInfoHeader)); - return is; -} - -MjpegInputStream& operator >> (MjpegInputStream& is, RiffList& riff_list) -{ - is.read((char*)(&riff_list), sizeof(riff_list)); - return is; -} - -MjpegInputStream& operator >> (MjpegInputStream& is, RiffChunk& riff_chunk) -{ - is.read((char*)(&riff_chunk), sizeof(riff_chunk)); - return is; -} - -MjpegInputStream& operator >> (MjpegInputStream& is, AviIndex& idx1) -{ - is.read((char*)(&idx1), sizeof(idx1)); - return is; -} - -/* -AVI struct: - -RIFF ('AVI ' - LIST ('hdrl' - 'avih'(
) - LIST ('strl' - 'strh'() - 'strf'() - [ 'strd'() ] - [ 'strn'() ] - [ 'indx'() ] - ... - ) - [LIST ('strl' ...)] - [LIST ('strl' ...)] - ... - [LIST ('odml' - 'dmlh'() - ... - ) - ] - ... - ) - [LIST ('INFO' ...)] - [JUNK] - LIST ('movi' - {{xxdb|xxdc|xxpc|xxwb}() | LIST ('rec ' - {xxdb|xxdc|xxpc|xxwb}() - {xxdb|xxdc|xxpc|xxwb}() - ... - ) - ... - } - ... - ) - ['idx1' () ] - ) - - {xxdb|xxdc|xxpc|xxwb} - xx - stream number: 00, 01, 02, ... - db - uncompressed video frame - dc - commpressed video frame - pc - palette change - wb - audio frame - - JUNK section may pad any data section and must be ignored -*/ - -typedef std::deque< std::pair > frame_list; -typedef frame_list::iterator frame_iterator; - -//Represents single MJPEG video stream within single AVI/AVIX entry -//Multiple video streams within single AVI/AVIX entry are not supported -//ODML index is not supported -class AviMjpegStream -{ -public: - AviMjpegStream(); - //stores founded frames in m_frame_list which can be accessed via getFrames - bool parseAvi(MjpegInputStream& in_str); - //stores founded frames in in_frame_list. getFrames() would return empty list - bool parseAvi(MjpegInputStream& in_str, frame_list& in_frame_list); - size_t getFramesCount(); - frame_list& getFrames(); - uint32_t getWidth(); - uint32_t getHeight(); - double getFps(); - -protected: - - bool parseAviWithFrameList(MjpegInputStream& in_str, frame_list& in_frame_list); - void skipJunk(RiffChunk& chunk, MjpegInputStream& in_str); - void skipJunk(RiffList& list, MjpegInputStream& in_str); - bool parseHdrlList(MjpegInputStream& in_str); - bool parseIndex(MjpegInputStream& in_str, uint32_t index_size, frame_list& in_frame_list); - bool parseMovi(MjpegInputStream& in_str, frame_list& in_frame_list); - bool parseStrl(MjpegInputStream& in_str, uint8_t stream_id); - bool parseInfo(MjpegInputStream& in_str); - void printError(MjpegInputStream& in_str, RiffList& list, uint32_t expected_fourcc); - void printError(MjpegInputStream& in_str, RiffChunk& chunk, uint32_t expected_fourcc); - - uint32_t m_stream_id; - uint64_t m_movi_start; - uint64_t m_movi_end; - frame_list m_frame_list; - uint32_t m_width; - uint32_t m_height; - double m_fps; - bool m_is_indx_present; -}; - -AviMjpegStream::AviMjpegStream(): m_stream_id(0), m_movi_start(0), m_movi_end(0), m_width(0), m_height(0), m_fps(0), m_is_indx_present(false) -{ -} - -size_t AviMjpegStream::getFramesCount() -{ - return m_frame_list.size(); -} - -frame_list& AviMjpegStream::getFrames() -{ - return m_frame_list; -} - -uint32_t AviMjpegStream::getWidth() -{ - return m_width; -} - -uint32_t AviMjpegStream::getHeight() -{ - return m_height; -} - -double AviMjpegStream::getFps() -{ - return m_fps; -} - -void AviMjpegStream::printError(MjpegInputStream& in_str, RiffList& list, uint32_t expected_fourcc) -{ - if(!in_str) - { - fprintf(stderr, "Unexpected end of file while searching for %s list\n", fourccToString(expected_fourcc).c_str()); - } - else if(list.m_riff_or_list_cc != LIST_CC) - { - fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(LIST_CC).c_str(), fourccToString(list.m_riff_or_list_cc).c_str()); - } - else - { - fprintf(stderr, "Unexpected list type. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(list.m_list_type_cc).c_str()); - } -} - -void AviMjpegStream::printError(MjpegInputStream& in_str, RiffChunk& chunk, uint32_t expected_fourcc) -{ - if(!in_str) - { - fprintf(stderr, "Unexpected end of file while searching for %s chunk\n", fourccToString(expected_fourcc).c_str()); - } - else - { - fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(chunk.m_four_cc).c_str()); - } -} - - -bool AviMjpegStream::parseMovi(MjpegInputStream&, frame_list&) -{ - //not implemented - return true; -} - -bool AviMjpegStream::parseInfo(MjpegInputStream&) -{ - //not implemented - return true; -} - -bool AviMjpegStream::parseIndex(MjpegInputStream& in_str, uint32_t index_size, frame_list& in_frame_list) -{ - uint64_t index_end = in_str.tellg(); - index_end += index_size; - bool result = false; - - while(in_str && (in_str.tellg() < index_end)) - { - AviIndex idx1; - in_str >> idx1; - - if(idx1.ckid == m_stream_id) - { - uint64_t absolute_pos = m_movi_start + idx1.dwChunkOffset; - - if(absolute_pos < m_movi_end) - { - in_frame_list.push_back(std::make_pair(absolute_pos, idx1.dwChunkLength)); - } - else - { - //unsupported case - fprintf(stderr, "Frame offset points outside movi section.\n"); - } - } - - result = true; - } - - return result; -} - -bool AviMjpegStream::parseStrl(MjpegInputStream& in_str, uint8_t stream_id) -{ - RiffChunk strh; - in_str >> strh; - - if(in_str && strh.m_four_cc == STRH_CC) - { - uint64_t next_strl_list = in_str.tellg(); - next_strl_list += strh.m_size; - - AviStreamHeader strm_hdr; - in_str >> strm_hdr; - - if(strm_hdr.fccType == VIDS_CC && strm_hdr.fccHandler == MJPG_CC) - { - uint8_t first_digit = (stream_id/10) + '0'; - uint8_t second_digit = (stream_id%10) + '0'; - - if(m_stream_id == 0) - { - m_stream_id = CV_FOURCC(first_digit, second_digit, 'd', 'c'); - m_fps = double(strm_hdr.dwRate)/strm_hdr.dwScale; - } - else - { - //second mjpeg video stream found which is not supported - fprintf(stderr, "More than one video stream found within AVI/AVIX list. Stream %c%cdc would be ignored\n", first_digit, second_digit); - } - - return true; - } - } - - return false; -} - -void AviMjpegStream::skipJunk(RiffChunk& chunk, MjpegInputStream& in_str) -{ - if(chunk.m_four_cc == JUNK_CC) - { - in_str.seekg(in_str.tellg() + chunk.m_size); - in_str >> chunk; - } -} - -void AviMjpegStream::skipJunk(RiffList& list, MjpegInputStream& in_str) -{ - if(list.m_riff_or_list_cc == JUNK_CC) - { - //JUNK chunk is 4 bytes less than LIST - in_str.seekg(in_str.tellg() + list.m_size - 4); - in_str >> list; - } -} - -bool AviMjpegStream::parseHdrlList(MjpegInputStream& in_str) -{ - bool result = false; - - RiffChunk avih; - in_str >> avih; - - if(in_str && avih.m_four_cc == AVIH_CC) - { - uint64_t next_strl_list = in_str.tellg(); - next_strl_list += avih.m_size; - - AviMainHeader avi_hdr; - in_str >> avi_hdr; - - if(in_str) - { - m_is_indx_present = ((avi_hdr.dwFlags & 0x10) != 0); - DWORD number_of_streams = avi_hdr.dwStreams; - CV_Assert(number_of_streams < 0xFF); - m_width = avi_hdr.dwWidth; - m_height = avi_hdr.dwHeight; - - //the number of strl lists must be equal to number of streams specified in main avi header - for(DWORD i = 0; i < number_of_streams; ++i) - { - in_str.seekg(next_strl_list); - RiffList strl_list; - in_str >> strl_list; - - if( in_str && strl_list.m_riff_or_list_cc == LIST_CC && strl_list.m_list_type_cc == STRL_CC ) - { - next_strl_list = in_str.tellg(); - //RiffList::m_size includes fourCC field which we have already read - next_strl_list += (strl_list.m_size - 4); - - result = parseStrl(in_str, (uint8_t)i); - } - else - { - printError(in_str, strl_list, STRL_CC); - } - } - } - } - else - { - printError(in_str, avih, AVIH_CC); - } - - return result; -} - -bool AviMjpegStream::parseAviWithFrameList(MjpegInputStream& in_str, frame_list& in_frame_list) -{ - RiffList hdrl_list; - in_str >> hdrl_list; - - if( in_str && hdrl_list.m_riff_or_list_cc == LIST_CC && hdrl_list.m_list_type_cc == HDRL_CC ) - { - uint64_t next_list = in_str.tellg(); - //RiffList::m_size includes fourCC field which we have already read - next_list += (hdrl_list.m_size - 4); - //parseHdrlList sets m_is_indx_present flag which would be used later - if(parseHdrlList(in_str)) - { - in_str.seekg(next_list); - - RiffList some_list; - in_str >> some_list; - - //an optional section INFO - if(in_str && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == INFO_CC) - { - next_list = in_str.tellg(); - //RiffList::m_size includes fourCC field which we have already read - next_list += (some_list.m_size - 4); - parseInfo(in_str); - - in_str.seekg(next_list); - in_str >> some_list; - } - - //an optional section JUNK - skipJunk(some_list, in_str); - - //we are expecting to find here movi list. Must present in avi - if(in_str && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == MOVI_CC) - { - bool is_index_found = false; - - m_movi_start = in_str.tellg(); - m_movi_start -= 4; - - m_movi_end = m_movi_start + some_list.m_size; - //if m_is_indx_present is set to true we should find index - if(m_is_indx_present) - { - //we are expecting to find index section after movi list - uint32_t indx_pos = (uint32_t)m_movi_start + 4; - indx_pos += (some_list.m_size - 4); - in_str.seekg(indx_pos); - - RiffChunk index_chunk; - in_str >> index_chunk; - - if(in_str && index_chunk.m_four_cc == IDX1_CC) - { - is_index_found = parseIndex(in_str, index_chunk.m_size, in_frame_list); - //we are not going anywhere else - } - else - { - printError(in_str, index_chunk, IDX1_CC); - } - } - //index not present or we were not able to find it - //parsing movi list - if(!is_index_found) - { - //not implemented - parseMovi(in_str, in_frame_list); - - fprintf(stderr, "Failed to parse avi: index was not found\n"); - //we are not going anywhere else - } - } - else - { - printError(in_str, some_list, MOVI_CC); - } - } - } - else - { - printError(in_str, hdrl_list, HDRL_CC); - } - - return in_frame_list.size() > 0; -} - -bool AviMjpegStream::parseAvi(MjpegInputStream& in_str, frame_list& in_frame_list) -{ - return parseAviWithFrameList(in_str, in_frame_list); -} - -bool AviMjpegStream::parseAvi(MjpegInputStream& in_str) -{ - return parseAviWithFrameList(in_str, m_frame_list); -} - - class MotionJpegCapture: public IVideoCapture { public: @@ -702,12 +61,9 @@ public: void close(); protected: - bool parseRiff(MjpegInputStream& in_str); - inline uint64_t getFramePos() const; - std::vector readFrame(frame_iterator it); - MjpegInputStream m_file_stream; + Ptr m_avi_container; bool m_is_first_frame; frame_list m_mjpeg_frames; @@ -779,23 +135,6 @@ double MotionJpegCapture::getProperty(int property) const } } -std::vector MotionJpegCapture::readFrame(frame_iterator it) -{ - m_file_stream.seekg(it->first); - - RiffChunk chunk; - m_file_stream >> chunk; - - std::vector result; - - result.reserve(chunk.m_size); - result.resize(chunk.m_size); - - m_file_stream.read(&(result[0]), chunk.m_size); // result.data() failed with MSVS2008 - - return result; -} - bool MotionJpegCapture::grabFrame() { if(isOpened()) @@ -818,7 +157,7 @@ bool MotionJpegCapture::retrieveFrame(int, OutputArray output_frame) { if(m_frame_iterator != m_mjpeg_frames.end()) { - std::vector data = readFrame(m_frame_iterator); + std::vector data = m_avi_container->readFrame(m_frame_iterator); if(data.size()) { @@ -840,6 +179,8 @@ MotionJpegCapture::~MotionJpegCapture() MotionJpegCapture::MotionJpegCapture(const String& filename) { + m_avi_container = makePtr(); + m_avi_container->initStream(filename); open(filename); } @@ -850,7 +191,7 @@ bool MotionJpegCapture::isOpened() const void MotionJpegCapture::close() { - m_file_stream.close(); + m_avi_container->close(); m_frame_iterator = m_mjpeg_frames.end(); } @@ -858,58 +199,25 @@ bool MotionJpegCapture::open(const String& filename) { close(); - m_file_stream.open(filename); + m_avi_container = makePtr(); + m_avi_container->initStream(filename); m_frame_iterator = m_mjpeg_frames.end(); m_is_first_frame = true; - if(!parseRiff(m_file_stream)) + if(!m_avi_container->parseRiff(m_mjpeg_frames)) { close(); + } else + { + m_frame_width = m_avi_container->getWidth(); + m_frame_height = m_avi_container->getHeight(); + m_fps = m_avi_container->getFps(); } return isOpened(); } - -bool MotionJpegCapture::parseRiff(MjpegInputStream& in_str) -{ - bool result = false; - while(in_str) - { - RiffList riff_list; - - in_str >> riff_list; - - if( in_str && riff_list.m_riff_or_list_cc == RIFF_CC && - ((riff_list.m_list_type_cc == AVI_CC) | (riff_list.m_list_type_cc == AVIX_CC)) ) - { - uint64_t next_riff = in_str.tellg(); - //RiffList::m_size includes fourCC field which we have already read - next_riff += (riff_list.m_size - 4); - - AviMjpegStream mjpeg_video_stream; - bool is_parsed = mjpeg_video_stream.parseAvi(in_str, m_mjpeg_frames); - result = result || is_parsed; - - if(is_parsed) - { - m_frame_width = mjpeg_video_stream.getWidth(); - m_frame_height = mjpeg_video_stream.getHeight(); - m_fps = mjpeg_video_stream.getFps(); - } - - in_str.seekg(next_riff); - } - else - { - break; - } - } - - return result; -} - Ptr createMotionJpegCapture(const String& filename) { Ptr mjdecoder(new MotionJpegCapture(filename)); diff --git a/modules/videoio/src/cap_mjpeg_encoder.cpp b/modules/videoio/src/cap_mjpeg_encoder.cpp index c67bcaec29..60d727039e 100644 --- a/modules/videoio/src/cap_mjpeg_encoder.cpp +++ b/modules/videoio/src/cap_mjpeg_encoder.cpp @@ -40,8 +40,12 @@ //M*/ #include "precomp.hpp" +#include "opencv2/videoio/container_avi.private.hpp" + #include #include +#include +#include #if CV_NEON #define WITH_NEON @@ -49,22 +53,6 @@ namespace cv { -namespace mjpeg -{ - -enum { COLORSPACE_GRAY=0, COLORSPACE_RGBA=1, COLORSPACE_BGR=2, COLORSPACE_YUV444P=3 }; - -#define fourCC(a,b,c,d) ((int)((uchar(d)<<24) | (uchar(c)<<16) | (uchar(b)<<8) | uchar(a))) - -static const int AVIH_STRH_SIZE = 56; -static const int STRF_SIZE = 40; -static const int AVI_DWFLAG = 0x00000910; -static const int AVI_DWSCALE = 1; -static const int AVI_DWQUALITY = -1; -static const int JUNK_SEEK = 4096; -static const int AVIIF_KEYFRAME = 0x10; -static const int MAX_BYTES_PER_SEC = 99999999; -static const int SUG_BUFFER_SIZE = 1048576; static const unsigned bit_mask[] = { @@ -79,279 +67,84 @@ static const unsigned bit_mask[] = 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF }; -class BitStream +static const uchar huff_val_shift = 20; +static const int huff_code_mask = (1 << huff_val_shift) - 1; + +static bool createEncodeHuffmanTable( const int* src, unsigned* table, int max_size ) { -public: - enum - { - DEFAULT_BLOCK_SIZE = (1 << 15), - huff_val_shift = 20, - huff_code_mask = (1 << huff_val_shift) - 1 - }; + int i, k; + int min_val = INT_MAX, max_val = INT_MIN; + int size; - BitStream() + /* calc min and max values in the table */ + for( i = 1, k = 1; src[k] >= 0; i++ ) { - m_buf.resize(DEFAULT_BLOCK_SIZE + 1024); - m_start = &m_buf[0]; - m_end = m_start + DEFAULT_BLOCK_SIZE; - m_is_opened = false; - m_f = 0; - m_current = 0; - m_pos = 0; - } + int code_count = src[k++]; - ~BitStream() - { - close(); - } - - bool open(const String& filename) - { - close(); - m_f = fopen(filename.c_str(), "wb"); - if( !m_f ) - return false; - m_current = m_start; - m_pos = 0; - return true; - } - - bool isOpened() const { return m_f != 0; } - - void close() - { - writeBlock(); - if( m_f ) - fclose(m_f); - m_f = 0; - } - - void writeBlock() - { - size_t wsz0 = m_current - m_start; - if( wsz0 > 0 && m_f ) + for( code_count += k; k < code_count; k++ ) { - size_t wsz = fwrite(m_start, 1, wsz0, m_f); - CV_Assert( wsz == wsz0 ); - } - m_pos += wsz0; - m_current = m_start; - } - - size_t getPos() const - { - return (size_t)(m_current - m_start) + m_pos; - } - - void putByte(int val) - { - *m_current++ = (uchar)val; - if( m_current >= m_end ) - writeBlock(); - } - - void putBytes(const uchar* buf, int count) - { - uchar* data = (uchar*)buf; - CV_Assert(m_f && data && m_current && count >= 0); - if( m_current >= m_end ) - writeBlock(); - - while( count ) - { - int l = (int)(m_end - m_current); - - if (l > count) - l = count; - - if( l > 0 ) - { - memcpy(m_current, data, l); - m_current += l; - data += l; - count -= l; - } - if( m_current >= m_end ) - writeBlock(); + int val = src[k] >> huff_val_shift; + if( val < min_val ) + min_val = val; + if( val > max_val ) + max_val = val; } } - void putShort(int val) + size = max_val - min_val + 3; + + if( size > max_size ) { - m_current[0] = (uchar)val; - m_current[1] = (uchar)(val >> 8); - m_current += 2; - if( m_current >= m_end ) - writeBlock(); + CV_Error(CV_StsOutOfRange, "too big maximum Huffman code size"); + return false; } - void putInt(int val) - { - m_current[0] = (uchar)val; - m_current[1] = (uchar)(val >> 8); - m_current[2] = (uchar)(val >> 16); - m_current[3] = (uchar)(val >> 24); - m_current += 4; - if( m_current >= m_end ) - writeBlock(); - } + memset( table, 0, size*sizeof(table[0])); - void jputShort(int val) - { - m_current[0] = (uchar)(val >> 8); - m_current[1] = (uchar)val; - m_current += 2; - if( m_current >= m_end ) - writeBlock(); - } + table[0] = min_val; + table[1] = size - 2; - void patchInt(int val, size_t pos) + for( i = 1, k = 1; src[k] >= 0; i++ ) { - if( pos >= m_pos ) + int code_count = src[k++]; + + for( code_count += k; k < code_count; k++ ) { - ptrdiff_t delta = pos - m_pos; - CV_Assert( delta < m_current - m_start ); - m_start[delta] = (uchar)val; - m_start[delta+1] = (uchar)(val >> 8); - m_start[delta+2] = (uchar)(val >> 16); - m_start[delta+3] = (uchar)(val >> 24); - } - else - { - long fpos = ftell(m_f); - fseek(m_f, (long)pos, SEEK_SET); - uchar buf[] = { (uchar)val, (uchar)(val >> 8), (uchar)(val >> 16), (uchar)(val >> 24) }; - fwrite(buf, 1, 4, m_f); - fseek(m_f, fpos, SEEK_SET); + int val = src[k] >> huff_val_shift; + int code = src[k] & huff_code_mask; + + table[val - min_val + 2] = (code << 8) | i; } } + return true; +} - void jput(unsigned currval) - { - uchar v; - uchar* ptr = m_current; - v = (uchar)(currval >> 24); - *ptr++ = v; - if( v == 255 ) - *ptr++ = 0; - v = (uchar)(currval >> 16); - *ptr++ = v; - if( v == 255 ) - *ptr++ = 0; - v = (uchar)(currval >> 8); - *ptr++ = v; - if( v == 255 ) - *ptr++ = 0; - v = (uchar)currval; - *ptr++ = v; - if( v == 255 ) - *ptr++ = 0; - m_current = ptr; - if( m_current >= m_end ) - writeBlock(); - } - - void jflush(unsigned currval, int bitIdx) - { - uchar v; - uchar* ptr = m_current; - currval |= (1 << bitIdx)-1; - while( bitIdx < 32 ) - { - v = (uchar)(currval >> 24); - *ptr++ = v; - if( v == 255 ) - *ptr++ = 0; - currval <<= 8; - bitIdx += 8; - } - m_current = ptr; - if( m_current >= m_end ) - writeBlock(); - } - - static bool createEncodeHuffmanTable( const int* src, unsigned* table, int max_size ) - { - int i, k; - int min_val = INT_MAX, max_val = INT_MIN; - int size; - - /* calc min and max values in the table */ - for( i = 1, k = 1; src[k] >= 0; i++ ) - { - int code_count = src[k++]; - - for( code_count += k; k < code_count; k++ ) - { - int val = src[k] >> huff_val_shift; - if( val < min_val ) - min_val = val; - if( val > max_val ) - max_val = val; - } - } - - size = max_val - min_val + 3; - - if( size > max_size ) - { - CV_Error(CV_StsOutOfRange, "too big maximum Huffman code size"); - return false; - } - - memset( table, 0, size*sizeof(table[0])); - - table[0] = min_val; - table[1] = size - 2; - - for( i = 1, k = 1; src[k] >= 0; i++ ) - { - int code_count = src[k++]; - - for( code_count += k; k < code_count; k++ ) - { - int val = src[k] >> huff_val_shift; - int code = src[k] & huff_code_mask; - - table[val - min_val + 2] = (code << 8) | i; - } - } - return true; - } - - static int* createSourceHuffmanTable(const uchar* src, int* dst, +static int* createSourceHuffmanTable(const uchar* src, int* dst, int max_bits, int first_bits) +{ + int i, val_idx, code = 0; + int* table = dst; + *dst++ = first_bits; + for (i = 1, val_idx = max_bits; i <= max_bits; i++) { - int i, val_idx, code = 0; - int* table = dst; - *dst++ = first_bits; - for (i = 1, val_idx = max_bits; i <= max_bits; i++) + int code_count = src[i - 1]; + dst[0] = code_count; + code <<= 1; + for (int k = 0; k < code_count; k++) { - int code_count = src[i - 1]; - dst[0] = code_count; - code <<= 1; - for (int k = 0; k < code_count; k++) - { - dst[k + 1] = (src[val_idx + k] << huff_val_shift) | (code + k); - } - code += code_count; - dst += code_count + 1; - val_idx += code_count; + dst[k + 1] = (src[val_idx + k] << huff_val_shift) | (code + k); } - dst[0] = -1; - return table; + code += code_count; + dst += code_count + 1; + val_idx += code_count; } + dst[0] = -1; + return table; +} -protected: - std::vector m_buf; - uchar* m_start; - uchar* m_end; - uchar* m_current; - size_t m_pos; - bool m_is_opened; - FILE* m_f; -}; +namespace mjpeg +{ class mjpeg_buffer { @@ -593,11 +386,6 @@ public: { rawstream = false; nstripes = -1; - height = 0; - width = 0; - moviPointer = 0; - channels = 0; - outfps = 0; quality = 0; } @@ -611,20 +399,15 @@ public: void close() { - if( !strm.isOpened() ) + if( !container.isOpenedStream() ) return; - if( !frameOffset.empty() && !rawstream ) + if( !container.isEmptyFrameOffset() && !rawstream ) { - endWriteChunk(); // end LIST 'movi' - writeIndex(); - finishWriteAVI(); + container.endWriteChunk(); // end LIST 'movi' + container.writeIndex(0, dc); + container.finishWriteAVI(); } - strm.close(); - frameOffset.clear(); - frameSize.clear(); - AVIChunkSizeIndex.clear(); - frameNumIndexes.clear(); } bool open(const String& filename, double fps, Size size, bool iscolor) @@ -639,222 +422,74 @@ public: if( strcmp(ext, ".avi") != 0 && strcmp(ext, ".AVI") != 0 && strcmp(ext, ".Avi") != 0 ) return false; - bool ok = strm.open(filename); - if( !ok ) + if( !container.initContainer(filename, fps, size, iscolor) ) return false; CV_Assert(fps >= 1); - outfps = cvRound(fps); - width = size.width; - height = size.height; quality = 75; rawstream = false; - channels = iscolor ? 3 : 1; if( !rawstream ) { - startWriteAVI(); - writeStreamHeader(); + container.startWriteAVI(1); // count stream + container.writeStreamHeader(MJPEG); } //printf("motion jpeg stream %s has been successfully opened\n", filename.c_str()); return true; } - bool isOpened() const { return strm.isOpened(); } - - void startWriteAVI() - { - startWriteChunk(fourCC('R', 'I', 'F', 'F')); - - strm.putInt(fourCC('A', 'V', 'I', ' ')); - - startWriteChunk(fourCC('L', 'I', 'S', 'T')); - - strm.putInt(fourCC('h', 'd', 'r', 'l')); - strm.putInt(fourCC('a', 'v', 'i', 'h')); - strm.putInt(AVIH_STRH_SIZE); - strm.putInt(cvRound(1e6 / outfps)); - strm.putInt(MAX_BYTES_PER_SEC); - strm.putInt(0); - strm.putInt(AVI_DWFLAG); - - frameNumIndexes.push_back(strm.getPos()); - - strm.putInt(0); - strm.putInt(0); - strm.putInt(1); // number of streams - strm.putInt(SUG_BUFFER_SIZE); - strm.putInt(width); - strm.putInt(height); - strm.putInt(0); - strm.putInt(0); - strm.putInt(0); - strm.putInt(0); - } - - void writeStreamHeader() - { - // strh - startWriteChunk(fourCC('L', 'I', 'S', 'T')); - - strm.putInt(fourCC('s', 't', 'r', 'l')); - strm.putInt(fourCC('s', 't', 'r', 'h')); - strm.putInt(AVIH_STRH_SIZE); - strm.putInt(fourCC('v', 'i', 'd', 's')); - strm.putInt(fourCC('M', 'J', 'P', 'G')); - strm.putInt(0); - strm.putInt(0); - strm.putInt(0); - strm.putInt(AVI_DWSCALE); - strm.putInt(outfps); - strm.putInt(0); - - frameNumIndexes.push_back(strm.getPos()); - - strm.putInt(0); - strm.putInt(SUG_BUFFER_SIZE); - strm.putInt(AVI_DWQUALITY); - strm.putInt(0); - strm.putShort(0); - strm.putShort(0); - strm.putShort(width); - strm.putShort(height); - - // strf (use the BITMAPINFOHEADER for video) - startWriteChunk(fourCC('s', 't', 'r', 'f')); - - strm.putInt(STRF_SIZE); - strm.putInt(width); - strm.putInt(height); - strm.putShort(1); // planes (1 means interleaved data (after decompression)) - - strm.putShort(8 * channels); // bits per pixel - strm.putInt(fourCC('M', 'J', 'P', 'G')); - strm.putInt(width * height * channels); - strm.putInt(0); - strm.putInt(0); - strm.putInt(0); - strm.putInt(0); - // Must be indx chunk - endWriteChunk(); // end strf - endWriteChunk(); // end strl - - // odml - startWriteChunk(fourCC('L', 'I', 'S', 'T')); - strm.putInt(fourCC('o', 'd', 'm', 'l')); - startWriteChunk(fourCC('d', 'm', 'l', 'h')); - - frameNumIndexes.push_back(strm.getPos()); - - strm.putInt(0); - strm.putInt(0); - - endWriteChunk(); // end dmlh - endWriteChunk(); // end odml - - endWriteChunk(); // end hdrl - - // JUNK - startWriteChunk(fourCC('J', 'U', 'N', 'K')); - size_t pos = strm.getPos(); - for( ; pos < (size_t)JUNK_SEEK; pos += 4 ) - strm.putInt(0); - endWriteChunk(); // end JUNK - // movi - startWriteChunk(fourCC('L', 'I', 'S', 'T')); - moviPointer = strm.getPos(); - strm.putInt(fourCC('m', 'o', 'v', 'i')); - } - - void startWriteChunk(int fourcc) - { - CV_Assert(fourcc != 0); - strm.putInt(fourcc); - - AVIChunkSizeIndex.push_back(strm.getPos()); - strm.putInt(0); - } - - void endWriteChunk() - { - if( !AVIChunkSizeIndex.empty() ) - { - size_t currpos = strm.getPos(); - size_t pospos = AVIChunkSizeIndex.back(); - AVIChunkSizeIndex.pop_back(); - int chunksz = (int)(currpos - (pospos + 4)); - strm.patchInt(chunksz, pospos); - } - } - - void writeIndex() - { - // old style AVI index. Must be Open-DML index - startWriteChunk(fourCC('i', 'd', 'x', '1')); - int nframes = (int)frameOffset.size(); - for( int i = 0; i < nframes; i++ ) - { - strm.putInt(fourCC('0', '0', 'd', 'c')); - strm.putInt(AVIIF_KEYFRAME); - strm.putInt((int)frameOffset[i]); - strm.putInt((int)frameSize[i]); - } - endWriteChunk(); // End idx1 - } - - void finishWriteAVI() - { - int nframes = (int)frameOffset.size(); - // Record frames numbers to AVI Header - while (!frameNumIndexes.empty()) - { - size_t ppos = frameNumIndexes.back(); - frameNumIndexes.pop_back(); - strm.patchInt(nframes, ppos); - } - endWriteChunk(); // end RIFF - } + bool isOpened() const { return container.isOpenedStream(); } void write(InputArray _img) { Mat img = _img.getMat(); - size_t chunkPointer = strm.getPos(); + size_t chunkPointer = container.getStreamPos(); int input_channels = img.channels(); int colorspace = -1; + int imgWidth = img.cols; + int frameWidth = container.getWidth(); + int imgHeight = img.rows; + int frameHeight = container.getHeight(); + int channels = container.getChannels(); + if( input_channels == 1 && channels == 1 ) { - CV_Assert( img.cols == width && img.rows == height ); + CV_Assert( imgWidth == frameWidth && imgHeight == frameHeight ); colorspace = COLORSPACE_GRAY; } else if( input_channels == 4 ) { - CV_Assert( img.cols == width && img.rows == height && channels == 3 ); + CV_Assert( imgWidth == frameWidth && imgHeight == frameHeight && channels == 3 ); colorspace = COLORSPACE_RGBA; } else if( input_channels == 3 ) { - CV_Assert( img.cols == width && img.rows == height && channels == 3 ); + CV_Assert( imgWidth == frameWidth && imgHeight == frameHeight && channels == 3 ); colorspace = COLORSPACE_BGR; } else if( input_channels == 1 && channels == 3 ) { - CV_Assert( img.cols == width && img.rows == height*3 ); + CV_Assert( imgWidth == frameWidth && imgHeight == frameHeight*3 ); colorspace = COLORSPACE_YUV444P; } else CV_Error(CV_StsBadArg, "Invalid combination of specified video colorspace and the input image colorspace"); - if( !rawstream ) - startWriteChunk(fourCC('0', '0', 'd', 'c')); + if( !rawstream ) { + int avi_index = container.getAVIIndex(0, dc); + container.startWriteChunk(avi_index); + } writeFrameData(img.data, (int)img.step, colorspace, input_channels); if( !rawstream ) { - frameOffset.push_back(chunkPointer - moviPointer); - frameSize.push_back(strm.getPos() - chunkPointer - 8); // Size excludes '00dc' and size field - endWriteChunk(); // end '00dc' + size_t tempChunkPointer = container.getStreamPos(); + size_t moviPointer = container.getMoviPointer(); + container.pushFrameOffset(chunkPointer - moviPointer); + container.pushFrameSize(tempChunkPointer - chunkPointer - 8); // Size excludes '00dc' and size field + container.endWriteChunk(); // end '00dc' } } @@ -863,7 +498,10 @@ public: if( propId == VIDEOWRITER_PROP_QUALITY ) return quality; if( propId == VIDEOWRITER_PROP_FRAMEBYTES ) - return frameSize.empty() ? 0. : (double)frameSize.back(); + { + bool isEmpty = container.isEmptyFrameSize(); + return isEmpty ? 0. : container.atFrameSize(container.countFrameSize() - 1); + } if( propId == VIDEOWRITER_PROP_NSTRIPES ) return nstripes; return 0.; @@ -889,16 +527,12 @@ public: void writeFrameData( const uchar* data, int step, int colorspace, int input_channels ); protected: - int outfps; - int width, height, channels; double quality; - size_t moviPointer; - std::vector frameOffset, frameSize, AVIChunkSizeIndex, frameNumIndexes; bool rawstream; mjpeg_buffer_keeper buffers_list; double nstripes; - BitStream strm; + AVIWriteContainer container; }; #define DCT_DESCALE(x, n) (((x) + (((int)1) << ((n) - 1))) >> (n)) @@ -1758,6 +1392,10 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa } //double total_dct = 0, total_cvt = 0; + int width = container.getWidth(); + int height = container.getHeight(); + int channels = container.getChannels(); + CV_Assert( data && width > 0 && height > 0 ); // encode the header and tables @@ -1784,7 +1422,7 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa double inv_quality = 1./_quality; // Encode header - strm.putBytes( (const uchar*)jpegHeader, sizeof(jpegHeader) - 1 ); + container.putStreamBytes( (const uchar*)jpegHeader, sizeof(jpegHeader) - 1 ); // Encode quantization tables for( i = 0; i < (channels > 1 ? 2 : 1); i++ ) @@ -1792,9 +1430,9 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa const uchar* qtable = i == 0 ? jpegTableK1_T : jpegTableK2_T; int chroma_scale = i > 0 ? luma_count : 1; - strm.jputShort( 0xffdb ); // DQT marker - strm.jputShort( 2 + 65*1 ); // put single qtable - strm.putByte( 0*16 + i ); // 8-bit table + container.jputStreamShort( 0xffdb ); // DQT marker + container.jputStreamShort( 2 + 65*1 ); // put single qtable + container.putStreamByte( 0*16 + i ); // 8-bit table // put coefficients for( j = 0; j < 64; j++ ) @@ -1807,7 +1445,7 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa qval = 255; fdct_qtab[i][idx] = (short)(cvRound((1 << (postshift + 11)))/ (qval*chroma_scale*idct_prescale[idx])); - strm.putByte( qval ); + container.putStreamByte( qval ); } } @@ -1820,49 +1458,49 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa int idx = i >= 2; int tableSize = 16 + (is_ac_tab ? 162 : 12); - strm.jputShort( 0xFFC4 ); // DHT marker - strm.jputShort( 3 + tableSize ); // define one huffman table - strm.putByte( is_ac_tab*16 + idx ); // put DC/AC flag and table index - strm.putBytes( htable, tableSize ); // put table + container.jputStreamShort( 0xFFC4 ); // DHT marker + container.jputStreamShort( 3 + tableSize ); // define one huffman table + container.putStreamByte( is_ac_tab*16 + idx ); // put DC/AC flag and table index + container.putStreamBytes( htable, tableSize ); // put table - BitStream::createEncodeHuffmanTable( BitStream::createSourceHuffmanTable( - htable, hbuffer, 16, 9 ), is_ac_tab ? huff_ac_tab[idx] : - huff_dc_tab[idx], is_ac_tab ? 256 : 16 ); + createEncodeHuffmanTable(createSourceHuffmanTable( htable, hbuffer, 16, 9 ), + is_ac_tab ? huff_ac_tab[idx] : huff_dc_tab[idx], + is_ac_tab ? 256 : 16 ); } // put frame header - strm.jputShort( 0xFFC0 ); // SOF0 marker - strm.jputShort( 8 + 3*channels ); // length of frame header - strm.putByte( 8 ); // sample precision - strm.jputShort( height ); - strm.jputShort( width ); - strm.putByte( channels ); // number of components + container.jputStreamShort( 0xFFC0 ); // SOF0 marker + container.jputStreamShort( 8 + 3*channels ); // length of frame header + container.putStreamByte( 8 ); // sample precision + container.jputStreamShort( height ); + container.jputStreamShort( width ); + container.putStreamByte( channels ); // number of components for( i = 0; i < channels; i++ ) { - strm.putByte( i + 1 ); // (i+1)-th component id (Y,U or V) + container.putStreamByte( i + 1 ); // (i+1)-th component id (Y,U or V) if( i == 0 ) - strm.putByte(x_scale*16 + y_scale); // chroma scale factors + container.putStreamByte(x_scale*16 + y_scale); // chroma scale factors else - strm.putByte(1*16 + 1); - strm.putByte( i > 0 ); // quantization table idx + container.putStreamByte(1*16 + 1); + container.putStreamByte( i > 0 ); // quantization table idx } // put scan header - strm.jputShort( 0xFFDA ); // SOS marker - strm.jputShort( 6 + 2*channels ); // length of scan header - strm.putByte( channels ); // number of components in the scan + container.jputStreamShort( 0xFFDA ); // SOS marker + container.jputStreamShort( 6 + 2*channels ); // length of scan header + container.putStreamByte( channels ); // number of components in the scan for( i = 0; i < channels; i++ ) { - strm.putByte( i+1 ); // component id - strm.putByte( (i>0)*16 + (i>0) );// selection of DC & AC tables + container.putStreamByte( i+1 ); // component id + container.putStreamByte( (i>0)*16 + (i>0) );// selection of DC & AC tables } - strm.jputShort(0*256 + 63); // start and end of spectral selection - for + container.jputStreamShort(0*256 + 63); // start and end of spectral selection - for // sequential DCT start is 0 and end is 63 - strm.putByte( 0 ); // successive approximation bit position + container.putStreamByte( 0 ); // successive approximation bit position // high & low - (0,0) for sequential DCT buffers_list.reset(); @@ -1877,18 +1515,18 @@ void MotionJpegWriter::writeFrameData( const uchar* data, int step, int colorspa for(unsigned k = 0; k < last_data_elem; ++k) { - strm.jput(v[k]); + container.jputStream(v[k]); } - strm.jflush(v[last_data_elem], 32 - buffers_list.get_last_bit_len()); - strm.jputShort( 0xFFD9 ); // EOI marker + container.jflushStream(v[last_data_elem], 32 - buffers_list.get_last_bit_len()); + container.jputStreamShort( 0xFFD9 ); // EOI marker /*printf("total dct = %.1fms, total cvt = %.1fms\n", total_dct*1000./cv::getTickFrequency(), total_cvt*1000./cv::getTickFrequency());*/ - size_t pos = strm.getPos(); + size_t pos = container.getStreamPos(); size_t pos1 = (pos + 3) & ~3; for( ; pos < pos1; pos++ ) - strm.putByte(0); + container.putStreamByte(0); } } diff --git a/modules/videoio/src/container_avi.cpp b/modules/videoio/src/container_avi.cpp new file mode 100644 index 0000000000..272f022ec0 --- /dev/null +++ b/modules/videoio/src/container_avi.cpp @@ -0,0 +1,1017 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "opencv2/videoio/container_avi.private.hpp" + +namespace cv +{ + +const unsigned int RIFF_CC = CV_FOURCC('R','I','F','F'); +const unsigned int LIST_CC = CV_FOURCC('L','I','S','T'); +const unsigned int HDRL_CC = CV_FOURCC('h','d','r','l'); +const unsigned int AVIH_CC = CV_FOURCC('a','v','i','h'); +const unsigned int STRL_CC = CV_FOURCC('s','t','r','l'); +const unsigned int STRH_CC = CV_FOURCC('s','t','r','h'); +const unsigned int STRF_CC = CV_FOURCC('s','t','r','f'); +const unsigned int VIDS_CC = CV_FOURCC('v','i','d','s'); +const unsigned int MJPG_CC = CV_FOURCC('M','J','P','G'); +const unsigned int MOVI_CC = CV_FOURCC('m','o','v','i'); +const unsigned int IDX1_CC = CV_FOURCC('i','d','x','1'); +const unsigned int AVI_CC = CV_FOURCC('A','V','I',' '); +const unsigned int AVIX_CC = CV_FOURCC('A','V','I','X'); +const unsigned int JUNK_CC = CV_FOURCC('J','U','N','K'); +const unsigned int INFO_CC = CV_FOURCC('I','N','F','O'); +const unsigned int ODML_CC = CV_FOURCC('o','d','m','l'); +const unsigned int DMLH_CC = CV_FOURCC('d','m','l','h'); + +String fourccToString(unsigned int fourcc); + +#ifndef DWORD +typedef unsigned int DWORD; +#endif +#ifndef WORD +typedef unsigned short int WORD; +#endif +#ifndef LONG +typedef int LONG; +#endif + +#pragma pack(push, 1) +struct AviMainHeader +{ + DWORD dwMicroSecPerFrame; // The period between video frames + DWORD dwMaxBytesPerSec; // Maximum data rate of the file + DWORD dwReserved1; // 0 + DWORD dwFlags; // 0x10 AVIF_HASINDEX: The AVI file has an idx1 chunk containing an index at the end of the file. + DWORD dwTotalFrames; // Field of the main header specifies the total number of frames of data in file. + DWORD dwInitialFrames; // Is used for interleaved files + DWORD dwStreams; // Specifies the number of streams in the file. + DWORD dwSuggestedBufferSize; // Field specifies the suggested buffer size forreading the file + DWORD dwWidth; // Fields specify the width of the AVIfile in pixels. + DWORD dwHeight; // Fields specify the height of the AVIfile in pixels. + DWORD dwReserved[4]; // 0, 0, 0, 0 +}; + +struct AviStreamHeader +{ + unsigned int fccType; // 'vids', 'auds', 'txts'... + unsigned int fccHandler; // "cvid", "DIB " + DWORD dwFlags; // 0 + DWORD dwPriority; // 0 + DWORD dwInitialFrames; // 0 + DWORD dwScale; // 1 + DWORD dwRate; // Fps (dwRate - frame rate for video streams) + DWORD dwStart; // 0 + DWORD dwLength; // Frames number (playing time of AVI file as defined by scale and rate) + DWORD dwSuggestedBufferSize; // For reading the stream + DWORD dwQuality; // -1 (encoding quality. If set to -1, drivers use the default quality value) + DWORD dwSampleSize; // 0 means that each frame is in its own chunk + struct { + short int left; + short int top; + short int right; + short int bottom; + } rcFrame; // If stream has a different size than dwWidth*dwHeight(unused) +}; + +struct AviIndex +{ + DWORD ckid; + DWORD dwFlags; + DWORD dwChunkOffset; + DWORD dwChunkLength; +}; + +struct BitmapInfoHeader +{ + DWORD biSize; // Write header size of BITMAPINFO header structure + LONG biWidth; // width in pixels + LONG biHeight; // height in pixels + WORD biPlanes; // Number of color planes in which the data is stored + WORD biBitCount; // Number of bits per pixel + DWORD biCompression; // Type of compression used (uncompressed: NO_COMPRESSION=0) + DWORD biSizeImage; // Image Buffer. Quicktime needs 3 bytes also for 8-bit png + // (biCompression==NO_COMPRESSION)?0:xDim*yDim*bytesPerPixel; + LONG biXPelsPerMeter; // Horizontal resolution in pixels per meter + LONG biYPelsPerMeter; // Vertical resolution in pixels per meter + DWORD biClrUsed; // 256 (color table size; for 8-bit only) + DWORD biClrImportant; // Specifies that the first x colors of the color table. Are important to the DIB. +}; + +struct RiffChunk +{ + unsigned int m_four_cc; + unsigned int m_size; +}; + +struct RiffList +{ + unsigned int m_riff_or_list_cc; + unsigned int m_size; + unsigned int m_list_type_cc; +}; + +class VideoInputStream +{ +public: + VideoInputStream(); + VideoInputStream(const String& filename); + ~VideoInputStream(); + VideoInputStream& read(char*, unsigned long long int); + VideoInputStream& seekg(unsigned long long int); + unsigned long long int tellg(); + bool isOpened() const; + bool open(const String& filename); + void close(); + operator bool(); + VideoInputStream& operator=(const VideoInputStream& stream); + +private: + bool m_is_valid; + String m_fname; + FILE* m_f; +}; + +#pragma pack(pop) + +inline VideoInputStream& operator >> (VideoInputStream& is, AviMainHeader& avih) +{ + is.read((char*)(&avih), sizeof(AviMainHeader)); + return is; +} +inline VideoInputStream& operator >> (VideoInputStream& is, AviStreamHeader& strh) +{ + is.read((char*)(&strh), sizeof(AviStreamHeader)); + return is; +} +inline VideoInputStream& operator >> (VideoInputStream& is, BitmapInfoHeader& bmph) +{ + is.read((char*)(&bmph), sizeof(BitmapInfoHeader)); + return is; +} +inline VideoInputStream& operator >> (VideoInputStream& is, AviIndex& idx1) +{ + is.read((char*)(&idx1), sizeof(idx1)); + return is; +} + +inline VideoInputStream& operator >> (VideoInputStream& is, RiffChunk& riff_chunk) +{ + is.read((char*)(&riff_chunk), sizeof(riff_chunk)); + return is; +} + +inline VideoInputStream& operator >> (VideoInputStream& is, RiffList& riff_list) +{ + is.read((char*)(&riff_list), sizeof(riff_list)); + return is; +} + +static const int AVIH_STRH_SIZE = 56; +static const int STRF_SIZE = 40; +static const int AVI_DWFLAG = 0x00000910; +static const int AVI_DWSCALE = 1; +static const int AVI_DWQUALITY = -1; +static const int JUNK_SEEK = 4096; +static const int AVIIF_KEYFRAME = 0x10; +static const int MAX_BYTES_PER_SEC = 99999999; +static const int SUG_BUFFER_SIZE = 1048576; + +String fourccToString(unsigned int fourcc) +{ + return format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255); +} + +VideoInputStream::VideoInputStream(): m_is_valid(false), m_f(0) +{ + m_fname = String(); +} + +VideoInputStream::VideoInputStream(const String& filename): m_is_valid(false), m_f(0) +{ + m_fname = filename; + open(filename); +} + +bool VideoInputStream::isOpened() const +{ + return m_f != 0; +} + +bool VideoInputStream::open(const String& filename) +{ + close(); + + m_f = fopen(filename.c_str(), "rb"); + + m_is_valid = isOpened(); + + return m_is_valid; +} + +void VideoInputStream::close() +{ + if(isOpened()) + { + m_is_valid = false; + + fclose(m_f); + m_f = 0; + } +} + +VideoInputStream& VideoInputStream::read(char* buf, unsigned long long int count) +{ + if(isOpened()) + { + m_is_valid = (count == fread((void*)buf, 1, (size_t)count, m_f)); + } + + return *this; +} + +VideoInputStream& VideoInputStream::seekg(unsigned long long int pos) +{ + m_is_valid = (fseek(m_f, (long)pos, SEEK_SET) == 0); + + return *this; +} + +unsigned long long int VideoInputStream::tellg() +{ + return ftell(m_f); +} + +VideoInputStream::operator bool() +{ + return m_is_valid; +} + +VideoInputStream& VideoInputStream::operator=(const VideoInputStream& stream) +{ + if (this != &stream) { + m_fname = stream.m_fname; + // m_f = stream.m_f; + open(m_fname); + } + return *this; +} + +VideoInputStream::~VideoInputStream() +{ + close(); +} + +AVIReadContainer::AVIReadContainer(): m_stream_id(0), m_movi_start(0), m_movi_end(0), m_width(0), m_height(0), m_fps(0), m_is_indx_present(false) +{ + m_file_stream = makePtr(); +} + +void AVIReadContainer::initStream(const String &filename) +{ + m_file_stream = makePtr(filename); +} + +void AVIReadContainer::initStream(Ptr m_file_stream_) +{ + m_file_stream = m_file_stream_; +} + +void AVIReadContainer::close() +{ + m_file_stream->close(); +} + +bool AVIReadContainer::parseIndex(unsigned int index_size, frame_list& in_frame_list) +{ + unsigned long long int index_end = m_file_stream->tellg(); + index_end += index_size; + bool result = false; + + while(m_file_stream && (m_file_stream->tellg() < index_end)) + { + AviIndex idx1; + *m_file_stream >> idx1; + + if(idx1.ckid == m_stream_id) + { + unsigned long long int absolute_pos = m_movi_start + idx1.dwChunkOffset; + + if(absolute_pos < m_movi_end) + { + in_frame_list.push_back(std::make_pair(absolute_pos, idx1.dwChunkLength)); + } + else + { + //unsupported case + fprintf(stderr, "Frame offset points outside movi section.\n"); + } + } + + result = true; + } + + return result; +} + +bool AVIReadContainer::parseStrl(char stream_id, Codecs codec_) +{ + RiffChunk strh; + *m_file_stream >> strh; + + if(m_file_stream && strh.m_four_cc == STRH_CC) + { + unsigned long long int next_strl_list = m_file_stream->tellg(); + next_strl_list += strh.m_size; + + AviStreamHeader strm_hdr; + *m_file_stream >> strm_hdr; + + if (codec_ == MJPEG) + { + if(strm_hdr.fccType == VIDS_CC && strm_hdr.fccHandler == MJPG_CC) + { + char first_digit = (stream_id/10) + '0'; + char second_digit = (stream_id%10) + '0'; + + if(m_stream_id == 0) + { + m_stream_id = CV_FOURCC(first_digit, second_digit, 'd', 'c'); + m_fps = double(strm_hdr.dwRate)/strm_hdr.dwScale; + } + else + { + //second mjpeg video stream found which is not supported + fprintf(stderr, "More than one video stream found within AVI/AVIX list. Stream %c%cdc would be ignored\n", first_digit, second_digit); + } + + return true; + } + } + } + + return false; +} + +void AVIReadContainer::skipJunk(RiffChunk& chunk) +{ + if(chunk.m_four_cc == JUNK_CC) + { + m_file_stream->seekg(m_file_stream->tellg() + chunk.m_size); + *m_file_stream >> chunk; + } +} + +void AVIReadContainer::skipJunk(RiffList& list) +{ + if(list.m_riff_or_list_cc == JUNK_CC) + { + //JUNK chunk is 4 bytes less than LIST + m_file_stream->seekg(m_file_stream->tellg() + list.m_size - 4); + *m_file_stream >> list; + } +} + +bool AVIReadContainer::parseHdrlList(Codecs codec_) +{ + bool result = false; + + RiffChunk avih; + *m_file_stream >> avih; + + if(m_file_stream && avih.m_four_cc == AVIH_CC) + { + unsigned long long int next_strl_list = m_file_stream->tellg(); + next_strl_list += avih.m_size; + + AviMainHeader avi_hdr; + *m_file_stream >> avi_hdr; + + if(m_file_stream) + { + m_is_indx_present = ((avi_hdr.dwFlags & 0x10) != 0); + DWORD number_of_streams = avi_hdr.dwStreams; + CV_Assert(number_of_streams < 0xFF); + m_width = avi_hdr.dwWidth; + m_height = avi_hdr.dwHeight; + + //the number of strl lists must be equal to number of streams specified in main avi header + for(DWORD i = 0; i < number_of_streams; ++i) + { + m_file_stream->seekg(next_strl_list); + RiffList strl_list; + *m_file_stream >> strl_list; + + if( m_file_stream && strl_list.m_riff_or_list_cc == LIST_CC && strl_list.m_list_type_cc == STRL_CC ) + { + next_strl_list = m_file_stream->tellg(); + //RiffList::m_size includes fourCC field which we have already read + next_strl_list += (strl_list.m_size - 4); + + result = parseStrl((char)i, codec_); + } + else + { + printError(strl_list, STRL_CC); + } + } + } + } + else + { + printError(avih, AVIH_CC); + } + + return result; +} + +bool AVIReadContainer::parseAviWithFrameList(frame_list& in_frame_list, Codecs codec_) +{ + RiffList hdrl_list; + *m_file_stream >> hdrl_list; + + if( m_file_stream && hdrl_list.m_riff_or_list_cc == LIST_CC && hdrl_list.m_list_type_cc == HDRL_CC ) + { + unsigned long long int next_list = m_file_stream->tellg(); + //RiffList::m_size includes fourCC field which we have already read + next_list += (hdrl_list.m_size - 4); + //parseHdrlList sets m_is_indx_present flag which would be used later + if(parseHdrlList(codec_)) + { + m_file_stream->seekg(next_list); + + RiffList some_list; + *m_file_stream >> some_list; + + //an optional section INFO + if(m_file_stream && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == INFO_CC) + { + next_list = m_file_stream->tellg(); + //RiffList::m_size includes fourCC field which we have already read + next_list += (some_list.m_size - 4); + parseInfo(); + + m_file_stream->seekg(next_list); + *m_file_stream >> some_list; + } + + //an optional section JUNK + skipJunk(some_list); + + //we are expecting to find here movi list. Must present in avi + if(m_file_stream && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == MOVI_CC) + { + bool is_index_found = false; + + m_movi_start = m_file_stream->tellg(); + m_movi_start -= 4; + + m_movi_end = m_movi_start + some_list.m_size; + //if m_is_indx_present is set to true we should find index + if(m_is_indx_present) + { + //we are expecting to find index section after movi list + unsigned int indx_pos = (unsigned int)m_movi_start + 4; + indx_pos += (some_list.m_size - 4); + m_file_stream->seekg(indx_pos); + + RiffChunk index_chunk; + *m_file_stream >> index_chunk; + + if(m_file_stream && index_chunk.m_four_cc == IDX1_CC) + { + is_index_found = parseIndex(index_chunk.m_size, in_frame_list); + //we are not going anywhere else + } + else + { + printError(index_chunk, IDX1_CC); + } + } + //index not present or we were not able to find it + //parsing movi list + if(!is_index_found) + { + //not implemented + parseMovi(in_frame_list); + + fprintf(stderr, "Failed to parse avi: index was not found\n"); + //we are not going anywhere else + } + } + else + { + printError(some_list, MOVI_CC); + } + } + } + else + { + printError(hdrl_list, HDRL_CC); + } + + return in_frame_list.size() > 0; +} + +std::vector AVIReadContainer::readFrame(frame_iterator it) +{ + m_file_stream->seekg(it->first); + + RiffChunk chunk; + *(m_file_stream) >> chunk; + + std::vector result; + + result.reserve(chunk.m_size); + result.resize(chunk.m_size); + + m_file_stream->read(&(result[0]), chunk.m_size); // result.data() failed with MSVS2008 + + return result; +} + +bool AVIReadContainer::parseRiff(frame_list &m_mjpeg_frames_) +{ + bool result = false; + while(*m_file_stream) + { + RiffList riff_list; + + *m_file_stream >> riff_list; + + if( *m_file_stream && riff_list.m_riff_or_list_cc == RIFF_CC && + ((riff_list.m_list_type_cc == AVI_CC) | (riff_list.m_list_type_cc == AVIX_CC)) ) + { + unsigned long long int next_riff = m_file_stream->tellg(); + //RiffList::m_size includes fourCC field which we have already read + next_riff += (riff_list.m_size - 4); + + bool is_parsed = parseAvi(m_mjpeg_frames_, MJPEG); + result = result || is_parsed; + m_file_stream->seekg(next_riff); + } + else + { + break; + } + } + return result; +} + +void AVIReadContainer::printError(RiffList &list, unsigned int expected_fourcc) +{ + if(!m_file_stream) + { + fprintf(stderr, "Unexpected end of file while searching for %s list\n", fourccToString(expected_fourcc).c_str()); + } + else if(list.m_riff_or_list_cc != LIST_CC) + { + fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(LIST_CC).c_str(), fourccToString(list.m_riff_or_list_cc).c_str()); + } + else + { + fprintf(stderr, "Unexpected list type. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(list.m_list_type_cc).c_str()); + } +} + +void AVIReadContainer::printError(RiffChunk &chunk, unsigned int expected_fourcc) +{ + if(!m_file_stream) + { + fprintf(stderr, "Unexpected end of file while searching for %s chunk\n", fourccToString(expected_fourcc).c_str()); + } + else + { + fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(chunk.m_four_cc).c_str()); + } +} + +class BitStream +{ +public: + BitStream(); + ~BitStream() { close(); } + + bool open(const String& filename); + bool isOpened() const { return m_f != 0; } + void close(); + + void writeBlock(); + size_t getPos() const; + void putByte(int val); + void putBytes(const uchar* buf, int count); + + void putShort(int val); + void putInt(int val); + void jputShort(int val); + void patchInt(int val, size_t pos); + void jput(unsigned currval); + void jflush(unsigned currval, int bitIdx); + +protected: + std::vector m_buf; + uchar* m_start; + uchar* m_end; + uchar* m_current; + size_t m_pos; + bool m_is_opened; + FILE* m_f; +}; + +static const size_t DEFAULT_BLOCK_SIZE = (1 << 15); + +BitStream::BitStream() +{ + m_buf.resize(DEFAULT_BLOCK_SIZE + 1024); + m_start = &m_buf[0]; + m_end = m_start + DEFAULT_BLOCK_SIZE; + m_is_opened = false; + m_f = 0; + m_current = 0; + m_pos = 0; +} + +bool BitStream::open(const String& filename) +{ + close(); + m_f = fopen(filename.c_str(), "wb"); + if( !m_f ) + return false; + m_current = m_start; + m_pos = 0; + return true; +} + +void BitStream::close() +{ + writeBlock(); + if( m_f ) + fclose(m_f); + m_f = 0; +} + +void BitStream::writeBlock() +{ + size_t wsz0 = m_current - m_start; + if( wsz0 > 0 && m_f ) + { + size_t wsz = fwrite(m_start, 1, wsz0, m_f); + CV_Assert( wsz == wsz0 ); + } + m_pos += wsz0; + m_current = m_start; +} + +size_t BitStream::getPos() const { + return (size_t)(m_current - m_start) + m_pos; +} + +void BitStream::putByte(int val) +{ + *m_current++ = (uchar)val; + if( m_current >= m_end ) + writeBlock(); +} + +void BitStream::putBytes(const uchar* buf, int count) +{ + uchar* data = (uchar*)buf; + CV_Assert(m_f && data && m_current && count >= 0); + if( m_current >= m_end ) + writeBlock(); + + while( count ) + { + int l = (int)(m_end - m_current); + + if (l > count) + l = count; + + if( l > 0 ) + { + memcpy(m_current, data, l); + m_current += l; + data += l; + count -= l; + } + if( m_current >= m_end ) + writeBlock(); + } +} + +void BitStream::putShort(int val) +{ + m_current[0] = (uchar)val; + m_current[1] = (uchar)(val >> 8); + m_current += 2; + if( m_current >= m_end ) + writeBlock(); +} + +void BitStream::putInt(int val) +{ + m_current[0] = (uchar)val; + m_current[1] = (uchar)(val >> 8); + m_current[2] = (uchar)(val >> 16); + m_current[3] = (uchar)(val >> 24); + m_current += 4; + if( m_current >= m_end ) + writeBlock(); +} + +void BitStream::jputShort(int val) +{ + m_current[0] = (uchar)(val >> 8); + m_current[1] = (uchar)val; + m_current += 2; + if( m_current >= m_end ) + writeBlock(); +} + +void BitStream::patchInt(int val, size_t pos) +{ + if( pos >= m_pos ) + { + ptrdiff_t delta = pos - m_pos; + CV_Assert( delta < m_current - m_start ); + m_start[delta] = (uchar)val; + m_start[delta+1] = (uchar)(val >> 8); + m_start[delta+2] = (uchar)(val >> 16); + m_start[delta+3] = (uchar)(val >> 24); + } + else + { + long fpos = ftell(m_f); + fseek(m_f, (long)pos, SEEK_SET); + uchar buf[] = { (uchar)val, (uchar)(val >> 8), (uchar)(val >> 16), (uchar)(val >> 24) }; + fwrite(buf, 1, 4, m_f); + fseek(m_f, fpos, SEEK_SET); + } +} + +void BitStream::jput(unsigned currval) +{ + uchar v; + uchar* ptr = m_current; + v = (uchar)(currval >> 24); + *ptr++ = v; + if( v == 255 ) + *ptr++ = 0; + v = (uchar)(currval >> 16); + *ptr++ = v; + if( v == 255 ) + *ptr++ = 0; + v = (uchar)(currval >> 8); + *ptr++ = v; + if( v == 255 ) + *ptr++ = 0; + v = (uchar)currval; + *ptr++ = v; + if( v == 255 ) + *ptr++ = 0; + m_current = ptr; + if( m_current >= m_end ) + writeBlock(); +} + +void BitStream::jflush(unsigned currval, int bitIdx) +{ + uchar v; + uchar* ptr = m_current; + currval |= (1 << bitIdx)-1; + while( bitIdx < 32 ) + { + v = (uchar)(currval >> 24); + *ptr++ = v; + if( v == 255 ) + *ptr++ = 0; + currval <<= 8; + bitIdx += 8; + } + m_current = ptr; + if( m_current >= m_end ) + writeBlock(); +} + +AVIWriteContainer::AVIWriteContainer() : strm(makePtr()) +{ + outfps = 0; + height = 0; + width = 0; + channels = 0; + moviPointer = 0; + strm->close(); +} + +AVIWriteContainer::~AVIWriteContainer() { + strm->close(); + frameOffset.clear(); + frameSize.clear(); + AVIChunkSizeIndex.clear(); + frameNumIndexes.clear(); +} + +bool AVIWriteContainer::initContainer(const String& filename, double fps, Size size, bool iscolor) +{ + outfps = cvRound(fps); + width = size.width; + height = size.height; + channels = iscolor ? 3 : 1; + moviPointer = 0; + bool result = strm->open(filename); + return result; +} + +void AVIWriteContainer::startWriteAVI(int stream_count) +{ + startWriteChunk(RIFF_CC); + + strm->putInt(AVI_CC); + + startWriteChunk(LIST_CC); + + strm->putInt(HDRL_CC); + strm->putInt(AVIH_CC); + strm->putInt(AVIH_STRH_SIZE); + strm->putInt(cvRound(1e6 / outfps)); + strm->putInt(MAX_BYTES_PER_SEC); + strm->putInt(0); + strm->putInt(AVI_DWFLAG); + + frameNumIndexes.push_back(strm->getPos()); + + strm->putInt(0); + strm->putInt(0); + strm->putInt(stream_count); // number of streams + strm->putInt(SUG_BUFFER_SIZE); + strm->putInt(width); + strm->putInt(height); + strm->putInt(0); + strm->putInt(0); + strm->putInt(0); + strm->putInt(0); +} + +void AVIWriteContainer::writeStreamHeader(Codecs codec_) +{ + // strh + startWriteChunk(LIST_CC); + + strm->putInt(STRL_CC); + strm->putInt(STRH_CC); + strm->putInt(AVIH_STRH_SIZE); + strm->putInt(VIDS_CC); + switch (codec_) { + case MJPEG: + strm->putInt(MJPG_CC); + break; + } + strm->putInt(0); + strm->putInt(0); + strm->putInt(0); + strm->putInt(AVI_DWSCALE); + strm->putInt(outfps); + strm->putInt(0); + + frameNumIndexes.push_back(strm->getPos()); + + strm->putInt(0); + strm->putInt(SUG_BUFFER_SIZE); + strm->putInt(AVI_DWQUALITY); + strm->putInt(0); + strm->putShort(0); + strm->putShort(0); + strm->putShort(width); + strm->putShort(height); + + // strf (use the BITMAPINFOHEADER for video) + startWriteChunk(STRF_CC); + + strm->putInt(STRF_SIZE); + strm->putInt(width); + strm->putInt(height); + strm->putShort(1); // planes (1 means interleaved data (after decompression)) + + strm->putShort(8 * channels); // bits per pixel + switch (codec_) { + case MJPEG: + strm->putInt(MJPG_CC); + break; + } + strm->putInt(width * height * channels); + strm->putInt(0); + strm->putInt(0); + strm->putInt(0); + strm->putInt(0); + // Must be indx chunk + endWriteChunk(); // end strf + + endWriteChunk(); // end strl + + // odml + startWriteChunk(LIST_CC); + strm->putInt(ODML_CC); + startWriteChunk(DMLH_CC); + + frameNumIndexes.push_back(strm->getPos()); + + strm->putInt(0); + strm->putInt(0); + + endWriteChunk(); // end dmlh + endWriteChunk(); // end odml + + endWriteChunk(); // end hdrl + + // JUNK + startWriteChunk(JUNK_CC); + size_t pos = strm->getPos(); + for( ; pos < (size_t)JUNK_SEEK; pos += 4 ) + strm->putInt(0); + endWriteChunk(); // end JUNK + + // movi + startWriteChunk(LIST_CC); + moviPointer = strm->getPos(); + strm->putInt(MOVI_CC); +} + +void AVIWriteContainer::startWriteChunk(int fourcc) +{ + CV_Assert(fourcc != 0); + strm->putInt(fourcc); + + AVIChunkSizeIndex.push_back(strm->getPos()); + strm->putInt(0); +} + +void AVIWriteContainer::endWriteChunk() +{ + if( !AVIChunkSizeIndex.empty() ) + { + size_t currpos = strm->getPos(); + size_t pospos = AVIChunkSizeIndex.back(); + AVIChunkSizeIndex.pop_back(); + int chunksz = (int)(currpos - (pospos + 4)); + strm->patchInt(chunksz, pospos); + } +} + +int AVIWriteContainer::getAVIIndex(int stream_number, StreamType strm_type) { + char strm_indx[2]; + strm_indx[0] = '0' + static_cast(stream_number / 10); + strm_indx[1] = '0' + static_cast(stream_number % 10); + + switch (strm_type) { + case db: return CV_FOURCC(strm_indx[0], strm_indx[1], 'd', 'b'); + case dc: return CV_FOURCC(strm_indx[0], strm_indx[1], 'd', 'c'); + case pc: return CV_FOURCC(strm_indx[0], strm_indx[1], 'p', 'c'); + case wb: return CV_FOURCC(strm_indx[0], strm_indx[1], 'w', 'b'); + default: return CV_FOURCC(strm_indx[0], strm_indx[1], 'd', 'b'); + } +} + +void AVIWriteContainer::writeIndex(int stream_number, StreamType strm_type) +{ + // old style AVI index. Must be Open-DML index + startWriteChunk(IDX1_CC); + int nframes = (int)frameOffset.size(); + for( int i = 0; i < nframes; i++ ) + { + strm->putInt(getAVIIndex(stream_number, strm_type)); + strm->putInt(AVIIF_KEYFRAME); + strm->putInt((int)frameOffset[i]); + strm->putInt((int)frameSize[i]); + } + endWriteChunk(); // End idx1 +} + +void AVIWriteContainer::finishWriteAVI() +{ + int nframes = (int)frameOffset.size(); + // Record frames numbers to AVI Header + while (!frameNumIndexes.empty()) + { + size_t ppos = frameNumIndexes.back(); + frameNumIndexes.pop_back(); + strm->patchInt(nframes, ppos); + } + endWriteChunk(); // end RIFF +} + +bool AVIWriteContainer::isOpenedStream() const { return strm->isOpened(); } + +size_t AVIWriteContainer::getStreamPos() const { return strm->getPos(); } + +void AVIWriteContainer::jputStreamShort(int val) { strm->jputShort(val); } + +void AVIWriteContainer::putStreamBytes(const uchar *buf, int count) { strm->putBytes( buf, count ); } + +void AVIWriteContainer::putStreamByte(int val) { strm->putByte(val); } + +void AVIWriteContainer::jputStream(unsigned currval) { strm->jput(currval); } + +void AVIWriteContainer::jflushStream(unsigned currval, int bitIdx) { strm->jflush(currval, bitIdx); } + +} diff --git a/modules/videoio/test/test_container_avi.cpp b/modules/videoio/test/test_container_avi.cpp new file mode 100644 index 0000000000..d4400e3bdd --- /dev/null +++ b/modules/videoio/test/test_container_avi.cpp @@ -0,0 +1,87 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" +#include "opencv2/videoio/container_avi.private.hpp" +#include + +using namespace cv; + +namespace opencv_test +{ + +TEST(videoio_avi, good_MJPG) { + String filename = BunnyParameters::getFilename(".mjpg.avi"); + AVIReadContainer in; + in.initStream(filename); + frame_list frames; + ASSERT_TRUE(in.parseRiff(frames)); + EXPECT_EQ(frames.size(), static_cast(BunnyParameters::getCount())); + EXPECT_EQ(in.getWidth(), static_cast(BunnyParameters::getWidth())); + EXPECT_EQ(in.getHeight(), static_cast(BunnyParameters::getHeight())); + EXPECT_EQ(in.getFps(), static_cast(BunnyParameters::getFps())); +} + +TEST(videoio_avi, bad_MJPG) { + String filename = BunnyParameters::getFilename(".avi"); + AVIReadContainer in; + in.initStream(filename); + frame_list frames; + EXPECT_FALSE(in.parseRiff(frames)); + EXPECT_EQ(frames.size(), static_cast(0)); +} + +TEST(videoio_avi, basic) +{ + const String filename = cv::tempfile("test.avi"); + const double fps = 100; + const Size sz(800, 600); + const size_t count = 10; + const uchar data[count] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA}; + const Codecs codec = MJPEG; + { + AVIWriteContainer out; + ASSERT_TRUE(out.initContainer(filename, fps, sz, true)); + ASSERT_TRUE(out.isOpenedStream()); + EXPECT_EQ(out.getWidth(), sz.width); + EXPECT_EQ(out.getHeight(), sz.height); + EXPECT_EQ(out.getChannels(), 3); + + out.startWriteAVI(1); + { + out.writeStreamHeader(codec); // starts LIST chunk + size_t chunkPointer = out.getStreamPos(); + int avi_index = out.getAVIIndex(0, dc); + { + out.startWriteChunk(avi_index); + out.putStreamBytes(data, count); + size_t tempChunkPointer = out.getStreamPos(); + size_t moviPointer = out.getMoviPointer(); + out.pushFrameOffset(chunkPointer - moviPointer); + out.pushFrameSize(tempChunkPointer - chunkPointer - 8); + out.endWriteChunk(); + } + out.endWriteChunk(); // ends LIST chunk + } + out.writeIndex(0, dc); + out.finishWriteAVI(); + } + { + AVIReadContainer in; + in.initStream(filename); + frame_list frames; + ASSERT_TRUE(in.parseRiff(frames)); + EXPECT_EQ(in.getFps(), fps); + EXPECT_EQ(in.getWidth(), static_cast(sz.width)); + EXPECT_EQ(in.getHeight(), static_cast(sz.height)); + ASSERT_EQ(frames.size(), static_cast(1)); + std::vector actual = in.readFrame(frames.begin()); + ASSERT_EQ(actual.size(), count); + for (size_t i = 0; i < count; ++i) + EXPECT_EQ(actual.at(i), data[i]) << "at index " << i; + } + remove(filename.c_str()); +} + +} diff --git a/modules/videoio/test/test_precomp.hpp b/modules/videoio/test/test_precomp.hpp index 5d5ea036b6..8d9f5e0358 100644 --- a/modules/videoio/test/test_precomp.hpp +++ b/modules/videoio/test/test_precomp.hpp @@ -41,4 +41,18 @@ inline void generateFrame(int i, int FRAME_COUNT, cv::Mat & frame) #endif } +class BunnyParameters +{ +public: + inline static int getWidth() { return 672; }; + inline static int getHeight() { return 384; }; + inline static int getFps() { return 24; }; + inline static double getTime() { return 5.21; }; + inline static int getCount() { return cvRound(getFps() * getTime()); }; + inline static std::string getFilename(const std::string &ext) + { + return cvtest::TS::ptr()->get_data_path() + "video/big_buck_bunny" + ext; + } +}; + #endif diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index c2b11e6936..ef44aae266 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -151,13 +151,13 @@ typedef tuple Backend_Type_Params; class Videoio_Bunny : public Videoio_Test_Base, public testing::TestWithParam { + BunnyParameters bunny_param; public: Videoio_Bunny() { ext = get<0>(GetParam()); apiPref = get<1>(GetParam()); - - video_file = cvtest::TS::ptr()->get_data_path() + "video/big_buck_bunny." + ext; + video_file = BunnyParameters::getFilename(String(".") + ext); } void doFrameCountTest() { @@ -181,18 +181,12 @@ public: return; } - const int width_gt = 672; - const int height_gt = 384; - const int fps_gt = 24; - const double time_gt = 5.21; - const int count_gt = cvRound(fps_gt * time_gt); // 5.21 sec * 24 fps - - EXPECT_EQ(width_gt, cap.get(CAP_PROP_FRAME_WIDTH)); - EXPECT_EQ(height_gt, cap.get(CAP_PROP_FRAME_HEIGHT)); + EXPECT_EQ(bunny_param.getWidth() , cap.get(CAP_PROP_FRAME_WIDTH)); + EXPECT_EQ(bunny_param.getHeight(), cap.get(CAP_PROP_FRAME_HEIGHT)); double fps_prop = cap.get(CAP_PROP_FPS); if (fps_prop > 0) - EXPECT_NEAR(fps_prop, fps_gt, 1); + EXPECT_NEAR(fps_prop, bunny_param.getFps(), 1); else std::cout << "FPS is not available. SKIP check." << std::endl; @@ -204,7 +198,7 @@ public: { if (count_prop > 0) { - EXPECT_EQ(count_gt, count_prop); + EXPECT_EQ(bunny_param.getCount(), count_prop); } } @@ -215,13 +209,13 @@ public: cap >> frame; if (frame.empty()) break; - EXPECT_EQ(width_gt, frame.cols); - EXPECT_EQ(height_gt, frame.rows); + EXPECT_EQ(bunny_param.getWidth(), frame.cols); + EXPECT_EQ(bunny_param.getHeight(), frame.rows); count_actual += 1; } if (count_prop > 0) { - EXPECT_NEAR(count_gt, count_actual, 1); + EXPECT_NEAR(bunny_param.getCount(), count_actual, 1); } else std::cout << "Frames counter is not available. Actual frames: " << count_actual << ". SKIP check." << std::endl;