mirror of
https://github.com/opencv/opencv.git
synced 2024-11-26 04:00:30 +08:00
Merge pull request #10145 from allnes:vedeoio_avi_container
This commit is contained in:
commit
30b33a4413
@ -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
|
||||
|
@ -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 <deque>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/*
|
||||
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;
|
||||
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<VideoInputStream> 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<char> 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<VideoInputStream> 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<BitStream> strm;
|
||||
int outfps;
|
||||
int width, height, channels;
|
||||
size_t moviPointer;
|
||||
std::vector<size_t> frameOffset, frameSize, AVIChunkSizeIndex, frameNumIndexes;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CONTAINER_AVI_HPP
|
@ -40,652 +40,11 @@
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include <deque>
|
||||
#include <stdint.h>
|
||||
#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'(<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 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<char> readFrame(frame_iterator it);
|
||||
|
||||
MjpegInputStream m_file_stream;
|
||||
Ptr<AVIReadContainer> 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<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[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<char> data = readFrame(m_frame_iterator);
|
||||
std::vector<char> 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<AVIReadContainer>();
|
||||
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<AVIReadContainer>();
|
||||
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<IVideoCapture> createMotionJpegCapture(const String& filename)
|
||||
{
|
||||
Ptr<MotionJpegCapture> mjdecoder(new MotionJpegCapture(filename));
|
||||
|
@ -40,8 +40,12 @@
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/videoio/container_avi.private.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
#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<uchar> 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<size_t> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
1017
modules/videoio/src/container_avi.cpp
Normal file
1017
modules/videoio/src/container_avi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
87
modules/videoio/test/test_container_avi.cpp
Normal file
87
modules/videoio/test/test_container_avi.cpp
Normal file
@ -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 <cstdio>
|
||||
|
||||
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<unsigned>(BunnyParameters::getCount()));
|
||||
EXPECT_EQ(in.getWidth(), static_cast<unsigned>(BunnyParameters::getWidth()));
|
||||
EXPECT_EQ(in.getHeight(), static_cast<unsigned>(BunnyParameters::getHeight()));
|
||||
EXPECT_EQ(in.getFps(), static_cast<unsigned>(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<unsigned>(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<unsigned>(sz.width));
|
||||
EXPECT_EQ(in.getHeight(), static_cast<unsigned>(sz.height));
|
||||
ASSERT_EQ(frames.size(), static_cast<unsigned>(1));
|
||||
std::vector<char> 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());
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -151,13 +151,13 @@ typedef tuple<string, int> Backend_Type_Params;
|
||||
|
||||
class Videoio_Bunny : public Videoio_Test_Base, public testing::TestWithParam<Backend_Type_Params>
|
||||
{
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user