/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2015, OpenCV Foundation, all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of Intel Corporation may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/ #include "precomp.hpp" #include #include 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: virtual ~MotionJpegCapture(); virtual double getProperty(int) const; virtual bool setProperty(int, double); virtual bool grabFrame(); virtual bool retrieveFrame(int, OutputArray); virtual bool isOpened() const; virtual int getCaptureDomain() { return CAP_ANY; } // Return the type of the capture object: CAP_VFW, etc... MotionJpegCapture(const String&); bool open(const String&); void close(); protected: bool parseRiff(MjpegInputStream& in_str); inline uint64_t getFramePos() const; std::vector readFrame(frame_iterator it); MjpegInputStream m_file_stream; bool m_is_first_frame; frame_list m_mjpeg_frames; frame_iterator m_frame_iterator; Mat m_current_frame; //frame width/height and fps could be different for //each frame/stream. At the moment we suppose that they //stays the same within single avi file. uint32_t m_frame_width; uint32_t m_frame_height; double m_fps; }; uint64_t MotionJpegCapture::getFramePos() const { if(m_is_first_frame) return 0; if(m_frame_iterator == m_mjpeg_frames.end()) return m_mjpeg_frames.size(); return m_frame_iterator - m_mjpeg_frames.begin() + 1; } bool MotionJpegCapture::setProperty(int property, double value) { if(property == CAP_PROP_POS_FRAMES) { if(int(value) == 0) { m_is_first_frame = true; m_frame_iterator = m_mjpeg_frames.end(); return true; } else if(m_mjpeg_frames.size() > value) { m_frame_iterator = m_mjpeg_frames.begin() + int(value - 1); m_is_first_frame = false; return true; } } return false; } double MotionJpegCapture::getProperty(int property) const { switch(property) { case CAP_PROP_POS_FRAMES: return (double)getFramePos(); case CAP_PROP_POS_AVI_RATIO: return double(getFramePos())/m_mjpeg_frames.size(); case CAP_PROP_FRAME_WIDTH: return (double)m_frame_width; case CAP_PROP_FRAME_HEIGHT: return (double)m_frame_height; case CAP_PROP_FPS: return m_fps; case CAP_PROP_FOURCC: return (double)CV_FOURCC('M','J','P','G'); case CAP_PROP_FRAME_COUNT: return (double)m_mjpeg_frames.size(); case CAP_PROP_FORMAT: return 0; default: return 0; } } 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()) { if(m_is_first_frame) { m_is_first_frame = false; m_frame_iterator = m_mjpeg_frames.begin(); } else { ++m_frame_iterator; } } return m_frame_iterator != m_mjpeg_frames.end(); } bool MotionJpegCapture::retrieveFrame(int, OutputArray output_frame) { if(m_frame_iterator != m_mjpeg_frames.end()) { std::vector data = readFrame(m_frame_iterator); if(data.size()) { m_current_frame = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_COLOR | IMREAD_IGNORE_ORIENTATION); } m_current_frame.copyTo(output_frame); return true; } return false; } MotionJpegCapture::~MotionJpegCapture() { close(); } MotionJpegCapture::MotionJpegCapture(const String& filename) { open(filename); } bool MotionJpegCapture::isOpened() const { return m_mjpeg_frames.size() > 0; } void MotionJpegCapture::close() { m_file_stream.close(); m_frame_iterator = m_mjpeg_frames.end(); } bool MotionJpegCapture::open(const String& filename) { close(); m_file_stream.open(filename); m_frame_iterator = m_mjpeg_frames.end(); m_is_first_frame = true; if(!parseRiff(m_file_stream)) { close(); } 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)); if( mjdecoder->isOpened() ) return mjdecoder; return Ptr(); } }