|
|
|
@ -40,13 +40,881 @@ |
|
|
|
|
//M*/
|
|
|
|
|
|
|
|
|
|
#include "precomp.hpp" |
|
|
|
|
#include <deque> |
|
|
|
|
#include <stdint.h> |
|
|
|
|
|
|
|
|
|
namespace cv |
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
Ptr<IVideoCapture> createMotionJpegCapture(const String&) |
|
|
|
|
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; // heigth 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'(<Main AVI Header>) |
|
|
|
|
LIST ('strl' |
|
|
|
|
'strh'(<Stream header>) |
|
|
|
|
'strf'(<Stream format>) |
|
|
|
|
[ 'strd'(<Additional header data>) ] |
|
|
|
|
[ 'strn'(<Stream name>) ] |
|
|
|
|
[ 'indx'(<Odml index data>) ] |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
[LIST ('strl' ...)] |
|
|
|
|
[LIST ('strl' ...)] |
|
|
|
|
... |
|
|
|
|
[LIST ('odml' |
|
|
|
|
'dmlh'(<ODML header data>) |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
] |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
[LIST ('INFO' ...)] |
|
|
|
|
[JUNK] |
|
|
|
|
LIST ('movi' |
|
|
|
|
{{xxdb|xxdc|xxpc|xxwb}(<Data>) | LIST ('rec ' |
|
|
|
|
{xxdb|xxdc|xxpc|xxwb}(<Data>) |
|
|
|
|
{xxdb|xxdc|xxpc|xxwb}(<Data>) |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
... |
|
|
|
|
} |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
['idx1' (<AVI Index>) ] |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
{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<uint64_t, uint32_t> > 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 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_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; |
|
|
|
|
m_width = avi_hdr.dwWidth; |
|
|
|
|
m_height = avi_hdr.dwWidth; |
|
|
|
|
|
|
|
|
|
//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<char> 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<char> MotionJpegCapture::readFrame(frame_iterator it) |
|
|
|
|
{ |
|
|
|
|
m_file_stream.seekg(it->first); |
|
|
|
|
|
|
|
|
|
RiffChunk chunk; |
|
|
|
|
m_file_stream >> chunk; |
|
|
|
|
|
|
|
|
|
std::vector<char> result; |
|
|
|
|
|
|
|
|
|
result.reserve(chunk.m_size); |
|
|
|
|
result.resize(chunk.m_size); |
|
|
|
|
|
|
|
|
|
m_file_stream.read(result.data(), chunk.m_size); |
|
|
|
|
|
|
|
|
|
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<char> data = readFrame(m_frame_iterator); |
|
|
|
|
|
|
|
|
|
if(data.size()) |
|
|
|
|
{ |
|
|
|
|
m_current_frame = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_COLOR); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<IVideoCapture> createMotionJpegCapture(const String& filename) |
|
|
|
|
{ |
|
|
|
|
return Ptr<IVideoCapture>(); |
|
|
|
|
Ptr<MotionJpegCapture> mjdecoder(new MotionJpegCapture(filename)); |
|
|
|
|
if( mjdecoder->isOpened() ) |
|
|
|
|
return mjdecoder; |
|
|
|
|
return Ptr<MotionJpegCapture>(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|