1701 lines
47 KiB
C++
1701 lines
47 KiB
C++
/*
|
|
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "avi_file.h"
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "critical_section_wrapper.h"
|
|
#include "file_wrapper.h"
|
|
#include "list_wrapper.h"
|
|
#include "trace.h"
|
|
|
|
// http://msdn2.microsoft.com/en-us/library/ms779636.aspx
|
|
// A chunk has the following form:
|
|
// ckID ckSize ckData
|
|
// where ckID is a FOURCC that identifies the data contained in the
|
|
// chunk, ckData is a 4-byte value giving the size of the data in
|
|
// ckData, and ckData is zero or more bytes of data. The data is always
|
|
// padded to nearest WORD boundary. ckSize gives the size of the valid
|
|
// data in the chunk; it does not include the padding, the size of
|
|
// ckID, or the size of ckSize.
|
|
//http://msdn2.microsoft.com/en-us/library/ms779632.aspx
|
|
//NOTE: Workaround to make MPEG4 files play on WMP. MPEG files
|
|
// place the config parameters efter the BITMAPINFOHEADER and
|
|
// *NOT* in the 'strd'!
|
|
// http://msdn.microsoft.com/en-us/library/dd183375.aspx
|
|
// http://msdn.microsoft.com/en-us/library/dd183376.aspx
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
static const WebRtc_UWord32 kAvifHasindex = 0x00000010;
|
|
static const WebRtc_UWord32 kAvifMustuseindex = 0x00000020;
|
|
static const WebRtc_UWord32 kAvifIsinterleaved = 0x00000100;
|
|
static const WebRtc_UWord32 kAvifTrustcktype = 0x00000800;
|
|
static const WebRtc_UWord32 kAvifWascapturefile = 0x00010000;
|
|
}
|
|
|
|
|
|
AviFile::AVIMAINHEADER::AVIMAINHEADER()
|
|
: fcc( 0),
|
|
cb( 0),
|
|
dwMicroSecPerFrame( 0),
|
|
dwMaxBytesPerSec( 0),
|
|
dwPaddingGranularity( 0),
|
|
dwFlags( 0),
|
|
dwTotalFrames( 0),
|
|
dwInitialFrames( 0),
|
|
dwStreams( 0),
|
|
dwSuggestedBufferSize(0),
|
|
dwWidth( 0),
|
|
dwHeight( 0)
|
|
{
|
|
dwReserved[0] = 0;
|
|
dwReserved[1] = 0;
|
|
dwReserved[2] = 0;
|
|
dwReserved[3] = 0;
|
|
}
|
|
|
|
AVISTREAMHEADER::AVISTREAMHEADER()
|
|
: fcc( 0),
|
|
cb( 0),
|
|
fccType( 0),
|
|
fccHandler( 0),
|
|
dwFlags( 0),
|
|
wPriority( 0),
|
|
wLanguage( 0),
|
|
dwInitialFrames( 0),
|
|
dwScale( 0),
|
|
dwRate( 0),
|
|
dwStart( 0),
|
|
dwLength( 0),
|
|
dwSuggestedBufferSize(0),
|
|
dwQuality( 0),
|
|
dwSampleSize( 0)
|
|
{
|
|
rcFrame.left = 0;
|
|
rcFrame.top = 0;
|
|
rcFrame.right = 0;
|
|
rcFrame.bottom = 0;
|
|
}
|
|
|
|
BITMAPINFOHEADER::BITMAPINFOHEADER()
|
|
: biSize( 0),
|
|
biWidth( 0),
|
|
biHeight( 0),
|
|
biPlanes( 0),
|
|
biBitCount( 0),
|
|
biCompression( 0),
|
|
biSizeImage( 0),
|
|
biXPelsPerMeter(0),
|
|
biYPelsPerMeter(0),
|
|
biClrUsed( 0),
|
|
biClrImportant( 0)
|
|
{
|
|
}
|
|
|
|
WAVEFORMATEX::WAVEFORMATEX()
|
|
: wFormatTag( 0),
|
|
nChannels( 0),
|
|
nSamplesPerSec( 0),
|
|
nAvgBytesPerSec(0),
|
|
nBlockAlign( 0),
|
|
wBitsPerSample( 0),
|
|
cbSize( 0)
|
|
{
|
|
}
|
|
|
|
AviFile::AVIINDEXENTRY::AVIINDEXENTRY(WebRtc_UWord32 inckid,
|
|
WebRtc_UWord32 indwFlags,
|
|
WebRtc_UWord32 indwChunkOffset,
|
|
WebRtc_UWord32 indwChunkLength)
|
|
: ckid(inckid),
|
|
dwFlags(indwFlags),
|
|
dwChunkOffset(indwChunkOffset),
|
|
dwChunkLength(indwChunkLength)
|
|
{
|
|
}
|
|
|
|
AviFile::AviFile()
|
|
{
|
|
_crit = CriticalSectionWrapper::CreateCriticalSection();
|
|
_indexList = new ListWrapper();
|
|
|
|
ResetMembers();
|
|
}
|
|
|
|
AviFile::~AviFile()
|
|
{
|
|
Close();
|
|
|
|
delete _indexList;
|
|
delete[] _videoCodecConfigParams;
|
|
delete _crit;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::Open(AVIStreamType streamType, const char* fileName,
|
|
bool loop)
|
|
{
|
|
WEBRTC_TRACE(kTraceStateInfo, kTraceVideo, -1, "OpenAVIFile(%s)",
|
|
fileName);
|
|
_crit->Enter();
|
|
|
|
if (_aviMode != NotSet)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
_aviMode = Read;
|
|
|
|
if (!fileName)
|
|
{
|
|
_crit->Leave();
|
|
WEBRTC_TRACE(kTraceError, kTraceVideo, -1, "\tfileName not valid!");
|
|
return -1;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// fopen does not support wide characters on Windows, ergo _wfopen.
|
|
wchar_t wideFileName[FileWrapper::kMaxFileNameSize];
|
|
wideFileName[0] = 0;
|
|
MultiByteToWideChar(CP_UTF8,0,fileName, -1, // convert the whole string
|
|
wideFileName, FileWrapper::kMaxFileNameSize);
|
|
|
|
_aviFile = _wfopen(wideFileName, L"rb");
|
|
#else
|
|
_aviFile = fopen(fileName, "rb");
|
|
#endif
|
|
|
|
if (!_aviFile)
|
|
{
|
|
_crit->Leave();
|
|
WEBRTC_TRACE(kTraceError, kTraceVideo, -1, "Could not open file!");
|
|
return -1;
|
|
}
|
|
|
|
// ReadRIFF verifies that the file is AVI and figures out the file length.
|
|
WebRtc_Word32 err = ReadRIFF();
|
|
if (err)
|
|
{
|
|
if (_aviFile)
|
|
{
|
|
fclose(_aviFile);
|
|
_aviFile = NULL;
|
|
}
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
err = ReadHeaders();
|
|
if (err)
|
|
{
|
|
if (_aviFile)
|
|
{
|
|
fclose(_aviFile);
|
|
_aviFile = NULL;
|
|
}
|
|
_crit->Leave();
|
|
WEBRTC_TRACE(kTraceError, kTraceVideo, -1,
|
|
"Unsupported or corrupt AVI format");
|
|
return -1;
|
|
}
|
|
|
|
_dataStartByte = _bytesRead;
|
|
_reading = true;
|
|
_openedAs = streamType;
|
|
_loop = loop;
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::Close()
|
|
{
|
|
_crit->Enter();
|
|
switch (_aviMode)
|
|
{
|
|
case Read:
|
|
CloseRead();
|
|
break;
|
|
case Write:
|
|
CloseWrite();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_videoCodecConfigParams)
|
|
{
|
|
delete [] _videoCodecConfigParams;
|
|
_videoCodecConfigParams = 0;
|
|
}
|
|
ResetMembers();
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_UWord32 AviFile::MakeFourCc(WebRtc_UWord8 ch0, WebRtc_UWord8 ch1,
|
|
WebRtc_UWord8 ch2, WebRtc_UWord8 ch3)
|
|
{
|
|
return ((WebRtc_UWord32)(WebRtc_UWord8)(ch0) |
|
|
((WebRtc_UWord32)(WebRtc_UWord8)(ch1) << 8) |
|
|
((WebRtc_UWord32)(WebRtc_UWord8)(ch2) << 16) |
|
|
((WebRtc_UWord32)(WebRtc_UWord8)(ch3) << 24 ));
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::GetVideoStreamInfo(AVISTREAMHEADER& videoStreamHeader,
|
|
BITMAPINFOHEADER& bitmapInfo,
|
|
char* codecConfigParameters,
|
|
WebRtc_Word32& configLength)
|
|
{
|
|
_crit->Enter();
|
|
if (!_reading && !_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
memcpy(&videoStreamHeader, &_videoStreamHeader, sizeof(_videoStreamHeader));
|
|
memcpy(&bitmapInfo, &_videoFormatHeader, sizeof(_videoFormatHeader));
|
|
|
|
if (_videoConfigParameters && configLength <= _videoConfigLength)
|
|
{
|
|
memcpy(codecConfigParameters, _videoConfigParameters,
|
|
_videoConfigLength);
|
|
configLength = _videoConfigLength;
|
|
}
|
|
else
|
|
{
|
|
configLength = 0;
|
|
}
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::GetDuration(WebRtc_Word32& durationMs)
|
|
{
|
|
_crit->Enter();
|
|
if (_videoStreamHeader.dwRate==0 || _videoStreamHeader.dwScale==0)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
durationMs = _videoStreamHeader.dwLength * 1000 /
|
|
(_videoStreamHeader.dwRate/_videoStreamHeader.dwScale);
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::GetAudioStreamInfo(WAVEFORMATEX& waveHeader)
|
|
{
|
|
_crit->Enter();
|
|
if (_aviMode != Read)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (!_reading && !_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
memcpy(&waveHeader, &_audioFormatHeader, sizeof(_audioFormatHeader));
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAudio(const WebRtc_UWord8* data,
|
|
WebRtc_Word32 length)
|
|
{
|
|
_crit->Enter();
|
|
size_t newBytesWritten = _bytesWritten;
|
|
|
|
if (_aviMode != Write)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (!_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (!_writeAudioStream)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
// Start of chunk.
|
|
const WebRtc_UWord32 chunkOffset = ftell(_aviFile) - _moviListOffset;
|
|
_bytesWritten += PutLE32(_audioStreamDataChunkPrefix);
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t chunkSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutBuffer(data, length);
|
|
|
|
const long chunkSize = PutLE32LengthFromCurrent(
|
|
static_cast<long>(chunkSizeMark));
|
|
|
|
// Make sure that the chunk is aligned on 2 bytes (= 1 sample).
|
|
if (chunkSize % 2)
|
|
{
|
|
_bytesWritten += PutByte(0);
|
|
}
|
|
// End of chunk
|
|
|
|
// Save chunk information for use when closing file.
|
|
AddChunkToIndexList(_audioStreamDataChunkPrefix, 0, // No flags.
|
|
chunkOffset, chunkSize);
|
|
|
|
++_audioFrames;
|
|
newBytesWritten = _bytesWritten - newBytesWritten;
|
|
_crit->Leave();
|
|
return static_cast<WebRtc_Word32>(newBytesWritten);
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteVideo(const WebRtc_UWord8* data,
|
|
WebRtc_Word32 length)
|
|
{
|
|
_crit->Enter();
|
|
size_t newBytesWritten = _bytesWritten;
|
|
if (_aviMode != Write)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (!_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (!_writeVideoStream)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
// Start of chunk.
|
|
const WebRtc_UWord32 chunkOffset = ftell(_aviFile) - _moviListOffset;
|
|
_bytesWritten += PutLE32(_videoStreamDataChunkPrefix);
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t chunkSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutBuffer(data, length);
|
|
|
|
const long chunkSize = PutLE32LengthFromCurrent(
|
|
static_cast<long>(chunkSizeMark));
|
|
|
|
// Make sure that the chunk is aligned on 2 bytes (= 1 sample).
|
|
if (chunkSize % 2)
|
|
{
|
|
//Pad one byte, to WORD align.
|
|
_bytesWritten += PutByte(0);
|
|
}
|
|
//End chunk!
|
|
AddChunkToIndexList(_videoStreamDataChunkPrefix, 0, // No flags.
|
|
chunkOffset, static_cast<WebRtc_UWord32>(chunkSize));
|
|
|
|
++_videoFrames;
|
|
newBytesWritten = _bytesWritten - newBytesWritten;
|
|
_crit->Leave();
|
|
return static_cast<WebRtc_Word32>(newBytesWritten);
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::PrepareDataChunkHeaders()
|
|
{
|
|
// 00 video stream, 01 audio stream.
|
|
// db uncompresses video, dc compressed video, wb WAV audio
|
|
if (_writeVideoStream)
|
|
{
|
|
if (strncmp((const char*) &_videoStreamHeader.fccHandler, "I420", 4) ==
|
|
0)
|
|
{
|
|
_videoStreamDataChunkPrefix = MakeFourCc('0', '0', 'd', 'b');
|
|
}
|
|
else
|
|
{
|
|
_videoStreamDataChunkPrefix = MakeFourCc('0', '0', 'd', 'c');
|
|
}
|
|
_audioStreamDataChunkPrefix = MakeFourCc('0', '1', 'w', 'b');
|
|
}
|
|
else
|
|
{
|
|
_audioStreamDataChunkPrefix = MakeFourCc('0', '0', 'w', 'b');
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadMoviSubChunk(WebRtc_UWord8* data,
|
|
WebRtc_Word32& length,
|
|
WebRtc_UWord32 tag1,
|
|
WebRtc_UWord32 tag2)
|
|
{
|
|
if (!_reading)
|
|
{
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1,
|
|
"AviFile::ReadMoviSubChunk(): File not open!");
|
|
length = 0;
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 size;
|
|
bool isEOFReached = false;
|
|
// Try to read one data chunk header
|
|
while (true)
|
|
{
|
|
// TODO (hellner): what happens if an empty AVI file is opened with
|
|
// _loop set to true? Seems like this while-loop would never exit!
|
|
|
|
// tag = db uncompresses video, dc compressed video or wb WAV audio.
|
|
WebRtc_UWord32 tag;
|
|
_bytesRead += GetLE32(tag);
|
|
_bytesRead += GetLE32(size);
|
|
|
|
const WebRtc_Word32 eof = feof(_aviFile);
|
|
if (!eof)
|
|
{
|
|
if (tag == tag1)
|
|
{
|
|
// Supported tag found.
|
|
break;
|
|
}
|
|
else if ((tag == tag2) && (tag2 != 0))
|
|
{
|
|
// Supported tag found.
|
|
break;
|
|
}
|
|
|
|
// Jump to next chunk. The size is in bytes but chunks are aligned
|
|
// on 2 byte boundaries.
|
|
const WebRtc_UWord32 seekSize = (size % 2) ? size + 1 : size;
|
|
const WebRtc_Word32 err = fseek(_aviFile, seekSize, SEEK_CUR);
|
|
|
|
if (err)
|
|
{
|
|
isEOFReached = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isEOFReached = true;
|
|
}
|
|
|
|
if (isEOFReached)
|
|
{
|
|
clearerr(_aviFile);
|
|
|
|
if (_loop)
|
|
{
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1,
|
|
"AviFile::ReadMoviSubChunk(): Reached end of AVI\
|
|
data file, starting from the beginning.");
|
|
|
|
fseek(_aviFile, static_cast<long>(_dataStartByte), SEEK_SET);
|
|
|
|
_bytesRead = _dataStartByte;
|
|
_framesRead = 0;
|
|
isEOFReached = false;
|
|
}
|
|
else
|
|
{
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1,
|
|
"AviFile::ReadMoviSubChunk(): Reached end of AVI\
|
|
file!");
|
|
length = 0;
|
|
return -1;
|
|
}
|
|
}
|
|
_bytesRead += size;
|
|
}
|
|
|
|
if (static_cast<WebRtc_Word32>(size) > length)
|
|
{
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1,
|
|
"AviFile::ReadMoviSubChunk(): AVI read buffer too small!");
|
|
|
|
// Jump to next chunk. The size is in bytes but chunks are aligned
|
|
// on 2 byte boundaries.
|
|
const WebRtc_UWord32 seekSize = (size % 2) ? size + 1 : size;
|
|
fseek(_aviFile, seekSize, SEEK_CUR);
|
|
_bytesRead += seekSize;
|
|
length = 0;
|
|
return -1;
|
|
}
|
|
_bytesRead += GetBuffer(data, size);
|
|
|
|
// The size is in bytes but chunks are aligned on 2 byte boundaries.
|
|
if (size % 2)
|
|
{
|
|
WebRtc_UWord8 dummy_byte;
|
|
_bytesRead += GetByte(dummy_byte);
|
|
}
|
|
length = size;
|
|
++_framesRead;
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadAudio(WebRtc_UWord8* data, WebRtc_Word32& length)
|
|
{
|
|
_crit->Enter();
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1, "AviFile::ReadAudio()");
|
|
|
|
if (_aviMode != Read)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (_openedAs != AVI_AUDIO)
|
|
{
|
|
length = 0;
|
|
_crit->Leave();
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1, "File not open as audio!");
|
|
return -1;
|
|
}
|
|
|
|
const WebRtc_Word32 ret = ReadMoviSubChunk(
|
|
data,
|
|
length,
|
|
StreamAndTwoCharCodeToTag(_audioStream.streamNumber, "wb"));
|
|
|
|
_crit->Leave();
|
|
return ret;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadVideo(WebRtc_UWord8* data, WebRtc_Word32& length)
|
|
{
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1, "AviFile::ReadVideo()");
|
|
|
|
_crit->Enter();
|
|
if (_aviMode != Read)
|
|
{
|
|
//Has to be Read!
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (_openedAs != AVI_VIDEO)
|
|
{
|
|
length = 0;
|
|
_crit->Leave();
|
|
WEBRTC_TRACE(kTraceDebug, kTraceVideo, -1, "File not open as video!");
|
|
return -1;
|
|
}
|
|
|
|
const WebRtc_Word32 ret = ReadMoviSubChunk(
|
|
data,
|
|
length,
|
|
StreamAndTwoCharCodeToTag(_videoStream.streamNumber, "dc"),
|
|
StreamAndTwoCharCodeToTag(_videoStream.streamNumber, "db"));
|
|
_crit->Leave();
|
|
return ret;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::Create(const char* fileName)
|
|
{
|
|
_crit->Enter();
|
|
if (_aviMode != Write)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
if (!_writeVideoStream && !_writeAudioStream)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
if (_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// fopen does not support wide characters on Windows, ergo _wfopen.
|
|
wchar_t wideFileName[FileWrapper::kMaxFileNameSize];
|
|
wideFileName[0] = 0;
|
|
|
|
MultiByteToWideChar(CP_UTF8,0,fileName, -1, // convert the whole string
|
|
wideFileName, FileWrapper::kMaxFileNameSize);
|
|
|
|
_aviFile = _wfopen(wideFileName, L"w+b");
|
|
if (!_aviFile)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
#else
|
|
_aviFile = fopen(fileName, "w+b");
|
|
if (!_aviFile)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
WriteRIFF();
|
|
WriteHeaders();
|
|
|
|
_created = true;
|
|
|
|
PrepareDataChunkHeaders();
|
|
ClearIndexList();
|
|
WriteMoviStart();
|
|
_aviMode = Write;
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::CreateVideoStream(
|
|
const AVISTREAMHEADER& videoStreamHeader,
|
|
const BITMAPINFOHEADER& bitMapInfoHeader,
|
|
const WebRtc_UWord8* codecConfigParams,
|
|
WebRtc_Word32 codecConfigParamsLength)
|
|
{
|
|
_crit->Enter();
|
|
if (_aviMode == Read)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
if (_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
_aviMode = Write;
|
|
_writeVideoStream = true;
|
|
|
|
_videoStreamHeader = videoStreamHeader;
|
|
_videoFormatHeader = bitMapInfoHeader;
|
|
|
|
if (codecConfigParams && codecConfigParamsLength > 0)
|
|
{
|
|
if (_videoCodecConfigParams)
|
|
{
|
|
delete [] _videoCodecConfigParams;
|
|
_videoCodecConfigParams = 0;
|
|
}
|
|
|
|
_videoCodecConfigParams = new WebRtc_UWord8[codecConfigParamsLength];
|
|
_videoCodecConfigParamsLength = codecConfigParamsLength;
|
|
|
|
memcpy(_videoCodecConfigParams, codecConfigParams,
|
|
_videoCodecConfigParamsLength);
|
|
}
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::CreateAudioStream(
|
|
const AVISTREAMHEADER& audioStreamHeader,
|
|
const WAVEFORMATEX& waveFormatHeader)
|
|
{
|
|
_crit->Enter();
|
|
|
|
if (_aviMode == Read)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
if (_created)
|
|
{
|
|
_crit->Leave();
|
|
return -1;
|
|
}
|
|
|
|
_aviMode = Write;
|
|
_writeAudioStream = true;
|
|
_audioStreamHeader = audioStreamHeader;
|
|
_audioFormatHeader = waveFormatHeader;
|
|
_crit->Leave();
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteRIFF()
|
|
{
|
|
const WebRtc_UWord32 riffTag = MakeFourCc('R', 'I', 'F', 'F');
|
|
_bytesWritten += PutLE32(riffTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
_riffSizeMark = _bytesWritten;
|
|
|
|
const WebRtc_UWord32 aviTag = MakeFourCc('A', 'V', 'I', ' ');
|
|
_bytesWritten += PutLE32(aviTag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
WebRtc_Word32 AviFile::WriteHeaders()
|
|
{
|
|
// Main AVI header list.
|
|
const WebRtc_UWord32 listTag = MakeFourCc('L', 'I', 'S', 'T');
|
|
_bytesWritten += PutLE32(listTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t listhdrlSizeMark = _bytesWritten;
|
|
|
|
const WebRtc_UWord32 hdrlTag = MakeFourCc('h', 'd', 'r', 'l');
|
|
_bytesWritten += PutLE32(hdrlTag);
|
|
|
|
WriteAVIMainHeader();
|
|
WriteAVIStreamHeaders();
|
|
|
|
const long hdrlLen = PutLE32LengthFromCurrent(
|
|
static_cast<long>(listhdrlSizeMark));
|
|
|
|
// Junk chunk to align on 2048 boundry (CD-ROM sector boundary).
|
|
const WebRtc_UWord32 junkTag = MakeFourCc('J', 'U', 'N', 'K');
|
|
_bytesWritten += PutLE32(junkTag);
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t junkSizeMark = _bytesWritten;
|
|
|
|
const WebRtc_UWord32 junkBufferSize =
|
|
0x800 // 2048 byte alignment
|
|
- 12 // RIFF SIZE 'AVI '
|
|
- 8 // LIST SIZE
|
|
- hdrlLen //
|
|
- 8 // JUNK SIZE
|
|
- 12; // LIST SIZE 'MOVI'
|
|
|
|
// TODO (hellner): why not just fseek here?
|
|
WebRtc_UWord8* junkBuffer = new WebRtc_UWord8[junkBufferSize];
|
|
memset(junkBuffer, 0, junkBufferSize);
|
|
_bytesWritten += PutBuffer(junkBuffer, junkBufferSize);
|
|
delete [] junkBuffer;
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(junkSizeMark));
|
|
// End of JUNK chunk.
|
|
// End of main AVI header list.
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIMainHeader()
|
|
{
|
|
const WebRtc_UWord32 avihTag = MakeFourCc('a', 'v', 'i', 'h');
|
|
_bytesWritten += PutLE32(avihTag);
|
|
_bytesWritten += PutLE32(14 * sizeof(WebRtc_UWord32));
|
|
|
|
const WebRtc_UWord32 scale = _videoStreamHeader.dwScale ?
|
|
_videoStreamHeader.dwScale : 1;
|
|
const WebRtc_UWord32 microSecPerFrame = 1000000 /
|
|
(_videoStreamHeader.dwRate / scale);
|
|
_bytesWritten += PutLE32(microSecPerFrame);
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
|
|
WebRtc_UWord32 numStreams = 0;
|
|
if (_writeVideoStream)
|
|
{
|
|
++numStreams;
|
|
}
|
|
if (_writeAudioStream)
|
|
{
|
|
++numStreams;
|
|
}
|
|
|
|
if (numStreams == 1)
|
|
{
|
|
_bytesWritten += PutLE32(
|
|
kAvifTrustcktype
|
|
| kAvifHasindex
|
|
| kAvifWascapturefile);
|
|
}
|
|
else
|
|
{
|
|
_bytesWritten += PutLE32(
|
|
kAvifTrustcktype
|
|
| kAvifHasindex
|
|
| kAvifWascapturefile
|
|
| kAvifIsinterleaved);
|
|
}
|
|
|
|
_totNumFramesMark = _bytesWritten;
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(numStreams);
|
|
|
|
if (_writeVideoStream)
|
|
{
|
|
_bytesWritten += PutLE32(
|
|
_videoStreamHeader.dwSuggestedBufferSize);
|
|
_bytesWritten += PutLE32(
|
|
_videoStreamHeader.rcFrame.right-_videoStreamHeader.rcFrame.left);
|
|
_bytesWritten += PutLE32(
|
|
_videoStreamHeader.rcFrame.bottom-_videoStreamHeader.rcFrame.top);
|
|
} else {
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
}
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
_bytesWritten += PutLE32(0);
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIStreamHeaders()
|
|
{
|
|
if (_writeVideoStream)
|
|
{
|
|
WriteAVIVideoStreamHeaders();
|
|
}
|
|
if (_writeAudioStream)
|
|
{
|
|
WriteAVIAudioStreamHeaders();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIVideoStreamHeaders()
|
|
{
|
|
const WebRtc_UWord32 listTag = MakeFourCc('L', 'I', 'S', 'T');
|
|
_bytesWritten += PutLE32(listTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t liststrlSizeMark = _bytesWritten;
|
|
|
|
const WebRtc_UWord32 hdrlTag = MakeFourCc('s', 't', 'r', 'l');
|
|
_bytesWritten += PutLE32(hdrlTag);
|
|
|
|
WriteAVIVideoStreamHeaderChunks();
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(liststrlSizeMark));
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIVideoStreamHeaderChunks()
|
|
{
|
|
// Start of strh
|
|
const WebRtc_UWord32 strhTag = MakeFourCc('s', 't', 'r', 'h');
|
|
_bytesWritten += PutLE32(strhTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strhSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutLE32(_videoStreamHeader.fccType);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.fccHandler);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwFlags);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.wPriority);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.wLanguage);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwInitialFrames);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwScale);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwRate);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwStart);
|
|
|
|
_videoStreamLengthMark = _bytesWritten;
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwLength);
|
|
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwSuggestedBufferSize);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwQuality);
|
|
_bytesWritten += PutLE32(_videoStreamHeader.dwSampleSize);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.rcFrame.left);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.rcFrame.top);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.rcFrame.right);
|
|
_bytesWritten += PutLE16(_videoStreamHeader.rcFrame.bottom);
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strhSizeMark));
|
|
// End of strh
|
|
|
|
// Start of strf
|
|
const WebRtc_UWord32 strfTag = MakeFourCc('s', 't', 'r', 'f');
|
|
_bytesWritten += PutLE32(strfTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strfSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biSize);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biWidth);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biHeight);
|
|
_bytesWritten += PutLE16(_videoFormatHeader.biPlanes);
|
|
_bytesWritten += PutLE16(_videoFormatHeader.biBitCount);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biCompression);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biSizeImage);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biXPelsPerMeter);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biYPelsPerMeter);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biClrUsed);
|
|
_bytesWritten += PutLE32(_videoFormatHeader.biClrImportant);
|
|
|
|
const bool isMpegFile = _videoStreamHeader.fccHandler ==
|
|
AviFile::MakeFourCc('M','4','S','2');
|
|
if (isMpegFile)
|
|
{
|
|
if (_videoCodecConfigParams && _videoCodecConfigParamsLength > 0)
|
|
{
|
|
_bytesWritten += PutBuffer(_videoCodecConfigParams,
|
|
_videoCodecConfigParamsLength);
|
|
}
|
|
}
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strfSizeMark));
|
|
// End of strf
|
|
|
|
if ( _videoCodecConfigParams
|
|
&& (_videoCodecConfigParamsLength > 0)
|
|
&& !isMpegFile)
|
|
{
|
|
// Write strd, unless it's an MPEG file
|
|
const WebRtc_UWord32 strdTag = MakeFourCc('s', 't', 'r', 'd');
|
|
_bytesWritten += PutLE32(strdTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strdSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutBuffer(_videoCodecConfigParams,
|
|
_videoCodecConfigParamsLength);
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strdSizeMark));
|
|
// End of strd
|
|
}
|
|
|
|
// Start of strn
|
|
const WebRtc_UWord32 strnTag = MakeFourCc('s', 't', 'r', 'n');
|
|
_bytesWritten += PutLE32(strnTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strnSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutBufferZ("WebRtc.avi ");
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strnSizeMark));
|
|
// End of strd
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIAudioStreamHeaders()
|
|
{
|
|
// Start of LIST
|
|
WebRtc_UWord32 listTag = MakeFourCc('L', 'I', 'S', 'T');
|
|
_bytesWritten += PutLE32(listTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t liststrlSizeMark = _bytesWritten;
|
|
|
|
WebRtc_UWord32 hdrlTag = MakeFourCc('s', 't', 'r', 'l');
|
|
_bytesWritten += PutLE32(hdrlTag);
|
|
|
|
WriteAVIAudioStreamHeaderChunks();
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(liststrlSizeMark));
|
|
//End of LIST
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteAVIAudioStreamHeaderChunks()
|
|
{
|
|
// Start of strh
|
|
const WebRtc_UWord32 strhTag = MakeFourCc('s', 't', 'r', 'h');
|
|
_bytesWritten += PutLE32(strhTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strhSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutLE32(_audioStreamHeader.fccType);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.fccHandler);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwFlags);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.wPriority);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.wLanguage);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwInitialFrames);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwScale);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwRate);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwStart);
|
|
|
|
_audioStreamLengthMark = _bytesWritten;
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwLength);
|
|
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwSuggestedBufferSize);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwQuality);
|
|
_bytesWritten += PutLE32(_audioStreamHeader.dwSampleSize);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.rcFrame.left);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.rcFrame.top);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.rcFrame.right);
|
|
_bytesWritten += PutLE16(_audioStreamHeader.rcFrame.bottom);
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strhSizeMark));
|
|
// End of strh
|
|
|
|
// Start of strf
|
|
const WebRtc_UWord32 strfTag = MakeFourCc('s', 't', 'r', 'f');
|
|
_bytesWritten += PutLE32(strfTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strfSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutLE16(_audioFormatHeader.wFormatTag);
|
|
_bytesWritten += PutLE16(_audioFormatHeader.nChannels);
|
|
_bytesWritten += PutLE32(_audioFormatHeader.nSamplesPerSec);
|
|
_bytesWritten += PutLE32(_audioFormatHeader.nAvgBytesPerSec);
|
|
_bytesWritten += PutLE16(_audioFormatHeader.nBlockAlign);
|
|
_bytesWritten += PutLE16(_audioFormatHeader.wBitsPerSample);
|
|
_bytesWritten += PutLE16(_audioFormatHeader.cbSize);
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strfSizeMark));
|
|
// End end of strf.
|
|
|
|
// Audio doesn't have strd.
|
|
|
|
// Start of strn
|
|
const WebRtc_UWord32 strnTag = MakeFourCc('s', 't', 'r', 'n');
|
|
_bytesWritten += PutLE32(strnTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t strnSizeMark = _bytesWritten;
|
|
|
|
_bytesWritten += PutBufferZ("WebRtc.avi ");
|
|
|
|
PutLE32LengthFromCurrent(static_cast<long>(strnSizeMark));
|
|
// End of strd.
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::WriteMoviStart()
|
|
{
|
|
// Create template movi list. Fill out size when known (i.e. when closing
|
|
// file).
|
|
const WebRtc_UWord32 listTag = MakeFourCc('L', 'I', 'S', 'T');
|
|
_bytesWritten += PutLE32(listTag);
|
|
|
|
_bytesWritten += PutLE32(0); //Size! Change later!
|
|
_moviSizeMark = _bytesWritten;
|
|
_moviListOffset = ftell(_aviFile);
|
|
|
|
const WebRtc_UWord32 moviTag = MakeFourCc('m', 'o', 'v', 'i');
|
|
_bytesWritten += PutLE32(moviTag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t AviFile::PutByte(WebRtc_UWord8 byte)
|
|
{
|
|
return fwrite(&byte, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord8),
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::PutLE16(WebRtc_UWord16 word)
|
|
{
|
|
return fwrite(&word, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord16),
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::PutLE32(WebRtc_UWord32 word)
|
|
{
|
|
return fwrite(&word, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord32),
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::PutBuffer(const WebRtc_UWord8* str, size_t size)
|
|
{
|
|
return fwrite(str, sizeof(WebRtc_UWord8), size,
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::PutBufferZ(const char* str)
|
|
{
|
|
// Include NULL charachter, hence the + 1
|
|
return PutBuffer(reinterpret_cast<const WebRtc_UWord8*>(str),
|
|
strlen(str) + 1);
|
|
}
|
|
|
|
long AviFile::PutLE32LengthFromCurrent(long startPos)
|
|
{
|
|
const long endPos = ftell(_aviFile);
|
|
WebRtc_Word32 error = fseek(_aviFile, startPos - 4, SEEK_SET);
|
|
const long len = endPos - startPos;
|
|
PutLE32(len);
|
|
error = fseek(_aviFile, endPos, SEEK_SET);
|
|
return len;
|
|
}
|
|
|
|
void AviFile::PutLE32AtPos(long pos, WebRtc_UWord32 word)
|
|
{
|
|
const long currPos = ftell(_aviFile);
|
|
WebRtc_Word32 error = fseek(_aviFile, pos, SEEK_SET);
|
|
PutLE32(word);
|
|
error = fseek(_aviFile, currPos, SEEK_SET);
|
|
}
|
|
|
|
void AviFile::CloseRead()
|
|
{
|
|
if (_aviFile)
|
|
{
|
|
fclose(_aviFile);
|
|
_aviFile = NULL;
|
|
}
|
|
}
|
|
|
|
void AviFile::CloseWrite()
|
|
{
|
|
if (_created)
|
|
{
|
|
// Update everything that isn't known until the file is closed. The
|
|
// marks indicate where in the headers this update should be.
|
|
PutLE32LengthFromCurrent(static_cast<long>(_moviSizeMark));
|
|
|
|
PutLE32AtPos(static_cast<long>(_totNumFramesMark), _videoFrames);
|
|
|
|
if (_writeVideoStream)
|
|
{
|
|
PutLE32AtPos(static_cast<long>(_videoStreamLengthMark),
|
|
_videoFrames);
|
|
}
|
|
|
|
if (_writeAudioStream)
|
|
{
|
|
PutLE32AtPos(static_cast<long>(_audioStreamLengthMark),
|
|
_audioFrames);
|
|
}
|
|
|
|
WriteIndex();
|
|
PutLE32LengthFromCurrent(static_cast<long>(_riffSizeMark));
|
|
ClearIndexList();
|
|
|
|
if (_aviFile)
|
|
{
|
|
fclose(_aviFile);
|
|
_aviFile = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AviFile::ResetMembers()
|
|
{
|
|
_aviFile = NULL;
|
|
|
|
memset(&_aviHeader, 0, sizeof(AVIMAINHEADER));
|
|
memset(&_videoStreamHeader, 0, sizeof(AVISTREAMHEADER));
|
|
memset(&_audioStreamHeader, 0, sizeof(AVISTREAMHEADER));
|
|
memset(&_videoFormatHeader, 0, sizeof(BITMAPINFOHEADER));
|
|
memset(&_audioFormatHeader, 0, sizeof(WAVEFORMATEX));
|
|
memset(_videoConfigParameters, 0, CODEC_CONFIG_LENGTH);
|
|
memset(_videoStreamName, 0, STREAM_NAME_LENGTH);
|
|
memset(_audioStreamName, 0, STREAM_NAME_LENGTH);
|
|
memset(&_videoStream, 0, sizeof(AVIStream));
|
|
memset(&_audioStream, 0, sizeof(AVIStream));
|
|
|
|
_nrStreams = 0;
|
|
_aviLength = 0;
|
|
_dataLength = 0;
|
|
_bytesRead = 0;
|
|
_dataStartByte = 0;
|
|
_framesRead = 0;
|
|
_videoFrames = 0;
|
|
_audioFrames = 0;
|
|
|
|
_reading = false;
|
|
_openedAs = AVI_AUDIO;
|
|
_loop = false;
|
|
_writing = false;
|
|
|
|
_bytesWritten = 0;
|
|
|
|
_riffSizeMark = 0;
|
|
_moviSizeMark = 0;
|
|
_totNumFramesMark = 0;
|
|
_videoStreamLengthMark = 0;
|
|
_audioStreamLengthMark = 0;
|
|
|
|
_writeAudioStream = false;
|
|
_writeVideoStream = false;
|
|
|
|
_aviMode = NotSet;
|
|
_videoCodecConfigParams = 0;
|
|
_videoCodecConfigParamsLength = 0;
|
|
|
|
_videoStreamDataChunkPrefix = 0;
|
|
_audioStreamDataChunkPrefix = 0;
|
|
|
|
_created = false;
|
|
|
|
_moviListOffset = 0;
|
|
|
|
_videoConfigLength = 0;
|
|
}
|
|
|
|
size_t AviFile::GetByte(WebRtc_UWord8& word)
|
|
{
|
|
return fread(&word, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord8), _aviFile);
|
|
}
|
|
|
|
size_t AviFile::GetLE16(WebRtc_UWord16& word)
|
|
{
|
|
return fread(&word, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord16),
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::GetLE32(WebRtc_UWord32& word)
|
|
{
|
|
return fread(&word, sizeof(WebRtc_UWord8), sizeof(WebRtc_UWord32),
|
|
_aviFile);
|
|
}
|
|
|
|
size_t AviFile::GetBuffer(WebRtc_UWord8* str, size_t size)
|
|
{
|
|
return fread(str, sizeof(WebRtc_UWord8), size, _aviFile);
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadRIFF()
|
|
{
|
|
WebRtc_UWord32 tag;
|
|
_bytesRead = GetLE32(tag);
|
|
if (tag != MakeFourCc('R', 'I', 'F', 'F'))
|
|
{
|
|
WEBRTC_TRACE(kTraceError, kTraceVideo, -1, "Not a RIFF file!");
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 size;
|
|
_bytesRead += GetLE32(size);
|
|
_aviLength = size;
|
|
|
|
_bytesRead += GetLE32(tag);
|
|
if (tag != MakeFourCc('A', 'V', 'I', ' '))
|
|
{
|
|
WEBRTC_TRACE(kTraceError, kTraceVideo, -1, "Not an AVI file!");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadHeaders()
|
|
{
|
|
WebRtc_UWord32 tag;
|
|
_bytesRead += GetLE32(tag);
|
|
WebRtc_UWord32 size;
|
|
_bytesRead += GetLE32(size);
|
|
|
|
if (tag != MakeFourCc('L', 'I', 'S', 'T'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 listTag;
|
|
_bytesRead += GetLE32(listTag);
|
|
if (listTag != MakeFourCc('h', 'd', 'r', 'l'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_Word32 err = ReadAVIMainHeader();
|
|
if (err)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadAVIMainHeader()
|
|
{
|
|
_bytesRead += GetLE32(_aviHeader.fcc);
|
|
_bytesRead += GetLE32(_aviHeader.cb);
|
|
_bytesRead += GetLE32(_aviHeader.dwMicroSecPerFrame);
|
|
_bytesRead += GetLE32(_aviHeader.dwMaxBytesPerSec);
|
|
_bytesRead += GetLE32(_aviHeader.dwPaddingGranularity);
|
|
_bytesRead += GetLE32(_aviHeader.dwFlags);
|
|
_bytesRead += GetLE32(_aviHeader.dwTotalFrames);
|
|
_bytesRead += GetLE32(_aviHeader.dwInitialFrames);
|
|
_bytesRead += GetLE32(_aviHeader.dwStreams);
|
|
_bytesRead += GetLE32(_aviHeader.dwSuggestedBufferSize);
|
|
_bytesRead += GetLE32(_aviHeader.dwWidth);
|
|
_bytesRead += GetLE32(_aviHeader.dwHeight);
|
|
_bytesRead += GetLE32(_aviHeader.dwReserved[0]);
|
|
_bytesRead += GetLE32(_aviHeader.dwReserved[1]);
|
|
_bytesRead += GetLE32(_aviHeader.dwReserved[2]);
|
|
_bytesRead += GetLE32(_aviHeader.dwReserved[3]);
|
|
|
|
if (_aviHeader.fcc != MakeFourCc('a', 'v', 'i', 'h'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (_aviHeader.dwFlags & kAvifMustuseindex)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
bool readVideoStreamHeader = false;
|
|
bool readAudioStreamHeader = false;
|
|
unsigned int streamsRead = 0;
|
|
while (_aviHeader.dwStreams > streamsRead)
|
|
{
|
|
WebRtc_UWord32 strltag;
|
|
_bytesRead += GetLE32(strltag);
|
|
WebRtc_UWord32 strlsize;
|
|
_bytesRead += GetLE32(strlsize);
|
|
const long endSeekPos = ftell(_aviFile) +
|
|
static_cast<WebRtc_Word32>(strlsize);
|
|
|
|
if (strltag != MakeFourCc('L', 'I', 'S', 'T'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 listTag;
|
|
_bytesRead += GetLE32(listTag);
|
|
if (listTag != MakeFourCc('s', 't', 'r', 'l'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 chunktag;
|
|
_bytesRead += GetLE32(chunktag);
|
|
WebRtc_UWord32 chunksize;
|
|
_bytesRead += GetLE32(chunksize);
|
|
|
|
if (chunktag != MakeFourCc('s', 't', 'r', 'h'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
AVISTREAMHEADER tmpStreamHeader;
|
|
tmpStreamHeader.fcc = chunktag;
|
|
tmpStreamHeader.cb = chunksize;
|
|
|
|
_bytesRead += GetLE32(tmpStreamHeader.fccType);
|
|
_bytesRead += GetLE32(tmpStreamHeader.fccHandler);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwFlags);
|
|
_bytesRead += GetLE16(tmpStreamHeader.wPriority);
|
|
_bytesRead += GetLE16(tmpStreamHeader.wLanguage);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwInitialFrames);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwScale);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwRate);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwStart);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwLength);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwSuggestedBufferSize);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwQuality);
|
|
_bytesRead += GetLE32(tmpStreamHeader.dwSampleSize);
|
|
|
|
WebRtc_UWord16 left;
|
|
_bytesRead += GetLE16(left);
|
|
tmpStreamHeader.rcFrame.left = left;
|
|
WebRtc_UWord16 top;
|
|
_bytesRead += GetLE16(top);
|
|
tmpStreamHeader.rcFrame.top = top;
|
|
WebRtc_UWord16 right;
|
|
_bytesRead += GetLE16(right);
|
|
tmpStreamHeader.rcFrame.right = right;
|
|
WebRtc_UWord16 bottom;
|
|
_bytesRead += GetLE16(bottom);
|
|
tmpStreamHeader.rcFrame.bottom = bottom;
|
|
|
|
if (!readVideoStreamHeader
|
|
&& (tmpStreamHeader.fccType == MakeFourCc('v', 'i', 'd', 's')))
|
|
{
|
|
_videoStreamHeader = tmpStreamHeader; //Bitwise copy is OK!
|
|
const WebRtc_Word32 err = ReadAVIVideoStreamHeader(endSeekPos);
|
|
if (err)
|
|
{
|
|
return -1;
|
|
}
|
|
// Make sure there actually is video data in the file...
|
|
if (_videoStreamHeader.dwLength == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
readVideoStreamHeader = true;
|
|
} else if(!readAudioStreamHeader &&
|
|
(tmpStreamHeader.fccType == MakeFourCc('a', 'u', 'd', 's'))) {
|
|
_audioStreamHeader = tmpStreamHeader;
|
|
const WebRtc_Word32 err = ReadAVIAudioStreamHeader(endSeekPos);
|
|
if (err)
|
|
{
|
|
return -1;
|
|
}
|
|
readAudioStreamHeader = true;
|
|
}
|
|
else
|
|
{
|
|
fseek(_aviFile, endSeekPos, SEEK_SET);
|
|
_bytesRead += endSeekPos;
|
|
}
|
|
|
|
++streamsRead;
|
|
}
|
|
|
|
if (!readVideoStreamHeader && !readAudioStreamHeader)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
WebRtc_UWord32 tag;
|
|
_bytesRead += GetLE32(tag);
|
|
WebRtc_UWord32 size;
|
|
_bytesRead += GetLE32(size);
|
|
|
|
if (tag == MakeFourCc('J', 'U', 'N', 'K'))
|
|
{
|
|
fseek(_aviFile, size, SEEK_CUR);
|
|
_bytesRead += size;
|
|
_bytesRead += GetLE32(tag);
|
|
_bytesRead += GetLE32(size);
|
|
}
|
|
if (tag != MakeFourCc('L', 'I', 'S', 'T'))
|
|
{
|
|
return -1;
|
|
}
|
|
WebRtc_UWord32 listTag;
|
|
_bytesRead += GetLE32(listTag);
|
|
if (listTag != MakeFourCc('m', 'o', 'v', 'i'))
|
|
{
|
|
return -1;
|
|
}
|
|
_dataLength = size;
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadAVIVideoStreamHeader(WebRtc_Word32 endpos)
|
|
{
|
|
WebRtc_UWord32 chunktag;
|
|
_bytesRead += GetLE32(chunktag);
|
|
WebRtc_UWord32 chunksize;
|
|
_bytesRead += GetLE32(chunksize);
|
|
|
|
if (chunktag != MakeFourCc('s', 't', 'r', 'f'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
_bytesRead += GetLE32(_videoFormatHeader.biSize);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biWidth);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biHeight);
|
|
_bytesRead += GetLE16(_videoFormatHeader.biPlanes);
|
|
_bytesRead += GetLE16(_videoFormatHeader.biBitCount);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biCompression);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biSizeImage);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biXPelsPerMeter);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biYPelsPerMeter);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biClrUsed);
|
|
_bytesRead += GetLE32(_videoFormatHeader.biClrImportant);
|
|
|
|
if (chunksize > _videoFormatHeader.biSize)
|
|
{
|
|
const WebRtc_Word32 size = chunksize - _videoFormatHeader.biSize;
|
|
const WebRtc_Word32 readSize = (size > CODEC_CONFIG_LENGTH) ?
|
|
CODEC_CONFIG_LENGTH : size;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_videoConfigParameters), readSize);
|
|
_videoConfigLength = readSize;
|
|
WebRtc_Word32 skipSize = chunksize - _videoFormatHeader.biSize -
|
|
readSize;
|
|
if (skipSize > 0)
|
|
{
|
|
fseek(_aviFile, skipSize, SEEK_CUR);
|
|
_bytesRead += skipSize;
|
|
}
|
|
}
|
|
|
|
while (static_cast<long>(_bytesRead) < endpos)
|
|
{
|
|
WebRtc_UWord32 chunktag;
|
|
_bytesRead += GetLE32(chunktag);
|
|
WebRtc_UWord32 chunksize;
|
|
_bytesRead += GetLE32(chunksize);
|
|
|
|
if (chunktag == MakeFourCc('s', 't', 'r', 'n'))
|
|
{
|
|
WebRtc_Word32 size = (chunksize > STREAM_NAME_LENGTH) ?
|
|
STREAM_NAME_LENGTH : chunksize;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_videoStreamName), size);
|
|
}
|
|
else if (chunktag == MakeFourCc('s', 't', 'r', 'd'))
|
|
{
|
|
WebRtc_Word32 size = (chunksize > CODEC_CONFIG_LENGTH) ?
|
|
CODEC_CONFIG_LENGTH : chunksize;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_videoConfigParameters), size);
|
|
_videoConfigLength = size;
|
|
}
|
|
else
|
|
{
|
|
fseek(_aviFile, chunksize, SEEK_CUR);
|
|
_bytesRead += chunksize;
|
|
}
|
|
|
|
if (feof(_aviFile))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
_videoStream.streamType = AviFile::AVI_VIDEO;
|
|
_videoStream.streamNumber = _nrStreams++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_Word32 AviFile::ReadAVIAudioStreamHeader(WebRtc_Word32 endpos)
|
|
{
|
|
WebRtc_UWord32 chunktag;
|
|
_bytesRead += GetLE32(chunktag);
|
|
WebRtc_UWord32 chunksize;
|
|
_bytesRead += GetLE32(chunksize);
|
|
|
|
if (chunktag != MakeFourCc('s', 't', 'r', 'f'))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
const size_t startRead = _bytesRead;
|
|
_bytesRead += GetLE16(_audioFormatHeader.wFormatTag);
|
|
_bytesRead += GetLE16(_audioFormatHeader.nChannels);
|
|
_bytesRead += GetLE32(_audioFormatHeader.nSamplesPerSec);
|
|
_bytesRead += GetLE32(_audioFormatHeader.nAvgBytesPerSec);
|
|
_bytesRead += GetLE16(_audioFormatHeader.nBlockAlign);
|
|
_bytesRead += GetLE16(_audioFormatHeader.wBitsPerSample);
|
|
_bytesRead += GetLE16(_audioFormatHeader.cbSize);
|
|
|
|
const WebRtc_Word32 diffRead = chunksize - (_bytesRead - startRead);
|
|
if (diffRead > 0)
|
|
{
|
|
size_t size = (diffRead > CODEC_CONFIG_LENGTH) ?
|
|
CODEC_CONFIG_LENGTH : diffRead;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_audioConfigParameters), size);
|
|
}
|
|
|
|
while (static_cast<long>(_bytesRead) < endpos)
|
|
{
|
|
WebRtc_UWord32 chunktag;
|
|
_bytesRead += GetLE32(chunktag);
|
|
WebRtc_UWord32 chunksize;
|
|
_bytesRead += GetLE32(chunksize);
|
|
|
|
if (chunktag == MakeFourCc('s', 't', 'r', 'n'))
|
|
{
|
|
WebRtc_Word32 size = (chunksize > STREAM_NAME_LENGTH) ?
|
|
STREAM_NAME_LENGTH : chunksize;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_audioStreamName), size);
|
|
}
|
|
else if (chunktag == MakeFourCc('s', 't', 'r', 'd'))
|
|
{
|
|
WebRtc_Word32 size = (chunksize > CODEC_CONFIG_LENGTH) ?
|
|
CODEC_CONFIG_LENGTH : chunksize;
|
|
_bytesRead += GetBuffer(
|
|
reinterpret_cast<WebRtc_UWord8*>(_audioConfigParameters), size);
|
|
}
|
|
else
|
|
{
|
|
fseek(_aviFile, chunksize, SEEK_CUR);
|
|
_bytesRead += chunksize;
|
|
}
|
|
|
|
if (feof(_aviFile))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
_audioStream.streamType = AviFile::AVI_AUDIO;
|
|
_audioStream.streamNumber = _nrStreams++;
|
|
return 0;
|
|
}
|
|
|
|
WebRtc_UWord32 AviFile::StreamAndTwoCharCodeToTag(WebRtc_Word32 streamNum,
|
|
const char* twoCharCode)
|
|
{
|
|
WebRtc_UWord8 a = '0';
|
|
WebRtc_UWord8 b;
|
|
switch (streamNum)
|
|
{
|
|
case 1:
|
|
b = '1';
|
|
break;
|
|
case 2:
|
|
b = '2';
|
|
break;
|
|
default:
|
|
b = '0';
|
|
}
|
|
return MakeFourCc(a, b, twoCharCode[0], twoCharCode[1]);
|
|
}
|
|
|
|
void AviFile::ClearIndexList()
|
|
{
|
|
while (!_indexList->Empty())
|
|
{
|
|
ListItem* listItem = _indexList->First();
|
|
if (listItem == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
AVIINDEXENTRY* item = static_cast<AVIINDEXENTRY*>(listItem->GetItem());
|
|
if (item != NULL)
|
|
{
|
|
delete item;
|
|
}
|
|
_indexList->PopFront();
|
|
}
|
|
}
|
|
|
|
void AviFile::AddChunkToIndexList(WebRtc_UWord32 inChunkId,
|
|
WebRtc_UWord32 inFlags,
|
|
WebRtc_UWord32 inOffset,
|
|
WebRtc_UWord32 inSize)
|
|
{
|
|
_indexList->PushBack(new AVIINDEXENTRY(inChunkId, inFlags, inOffset,
|
|
inSize));
|
|
}
|
|
|
|
void AviFile::WriteIndex()
|
|
{
|
|
const WebRtc_UWord32 idxTag = MakeFourCc('i', 'd', 'x', '1');
|
|
_bytesWritten += PutLE32(idxTag);
|
|
|
|
// Size is unknown at this point. Update later.
|
|
_bytesWritten += PutLE32(0);
|
|
const size_t idxChunkSize = _bytesWritten;
|
|
|
|
for (ListItem* listItem = _indexList->First();
|
|
listItem != NULL;
|
|
listItem = _indexList->Next(listItem))
|
|
{
|
|
const AVIINDEXENTRY* item =
|
|
static_cast<AVIINDEXENTRY*>(listItem->GetItem());
|
|
if (item != NULL)
|
|
{
|
|
_bytesWritten += PutLE32(item->ckid);
|
|
_bytesWritten += PutLE32(item->dwFlags);
|
|
_bytesWritten += PutLE32(item->dwChunkOffset);
|
|
_bytesWritten += PutLE32(item->dwChunkLength);
|
|
}
|
|
}
|
|
PutLE32LengthFromCurrent(static_cast<long>(idxChunkSize));
|
|
}
|
|
} // namespace webrtc
|