blob: a22c29e3024a9da0049f42e81ce199ba4bc6056a [file] [log] [blame]
// FlvHandler.cpp
#include "StdAfx.h"
#include "../../../C/CpuArch.h"
#include "Common/Buffer.h"
#include "Common/ComTry.h"
// #include "Common/Defs.h"
#include "Common/MyString.h"
#include "Windows/PropVariant.h"
#include "../Common/ProgressUtils.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamObjects.h"
#include "../Common/StreamUtils.h"
#define GetBe24(p) ( \
((UInt32)((const Byte *)(p))[0] << 16) | \
((UInt32)((const Byte *)(p))[1] << 8) | \
((const Byte *)(p))[2] )
#define Get16(p) GetBe16(p)
#define Get24(p) GetBe24(p)
#define Get32(p) GetBe32(p)
namespace NArchive {
namespace NFlv {
static const UInt32 kFileSizeMax = (UInt32)1 << 30;
static const int kNumChunksMax = (UInt32)1 << 23;
const UInt32 kTagHeaderSize = 11;
static const Byte kFlag_Video = 1;
static const Byte kFlag_Audio = 4;
static const Byte kType_Audio = 8;
static const Byte kType_Video = 9;
static const Byte kType_Meta = 18;
static const int kNumTypes = 19;
struct CItem
{
UInt32 Offset;
UInt32 Size;
// UInt32 Time;
Byte Type;
};
struct CItem2
{
Byte Type;
Byte SubType;
Byte Props;
bool SameSubTypes;
int NumChunks;
size_t Size;
CReferenceBuf *BufSpec;
CMyComPtr<IUnknown> RefBuf;
bool IsAudio() const { return Type == kType_Audio; }
};
class CHandler:
public IInArchive,
public IInArchiveGetStream,
public CMyUnknownImp
{
int _isRaw;
CMyComPtr<IInStream> _stream;
CObjectVector<CItem2> _items2;
// CByteBuffer _metadata;
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback);
AString GetComment();
public:
MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
INTERFACE_IInArchive(;)
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
};
STATPROPSTG kProps[] =
{
{ NULL, kpidSize, VT_UI8},
{ NULL, kpidNumBlocks, VT_UI4},
{ NULL, kpidComment, VT_BSTR}
};
/*
STATPROPSTG kArcProps[] =
{
{ NULL, kpidComment, VT_BSTR}
};
*/
IMP_IInArchive_Props
IMP_IInArchive_ArcProps_NO
static const char *g_AudioTypes[16] =
{
"pcm",
"adpcm",
"mp3",
"pcm_le",
"nellymoser16",
"nellymoser8",
"nellymoser",
"g711a",
"g711m",
"audio9",
"aac",
"speex",
"audio12",
"audio13",
"mp3",
"audio15"
};
static const char *g_VideoTypes[16] =
{
"video0",
"jpeg",
"h263",
"screen",
"vp6",
"vp6alpha",
"screen2",
"avc",
"video8",
"video9",
"video10",
"video11",
"video12",
"video13",
"video14",
"video15"
};
static const char *g_Rates[4] =
{
"5.5 kHz",
"11 kHz",
"22 kHz",
"44 kHz"
};
static void MyStrCat(char *d, const char *s)
{
MyStringCopy(d + MyStringLen(d), s);
}
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
NWindows::NCOM::CPropVariant prop;
const CItem2 &item = _items2[index];
switch(propID)
{
case kpidExtension:
prop = _isRaw ?
(item.IsAudio() ? g_AudioTypes[item.SubType] : g_VideoTypes[item.SubType]) :
(item.IsAudio() ? "audio.flv" : "video.flv");
break;
case kpidSize:
case kpidPackSize:
prop = (UInt64)item.Size;
break;
case kpidNumBlocks: prop = (UInt32)item.NumChunks; break;
case kpidComment:
{
char sz[64];
MyStringCopy(sz, (item.IsAudio() ? g_AudioTypes[item.SubType] : g_VideoTypes[item.SubType]) );
if (item.IsAudio())
{
MyStrCat(sz, " ");
MyStrCat(sz, g_Rates[(item.Props >> 2) & 3]);
MyStrCat(sz, (item.Props & 2) ? " 16-bit" : " 8-bit");
MyStrCat(sz, (item.Props & 1) ? " stereo" : " mono");
}
prop = sz;
break;
}
}
prop.Detach(value);
return S_OK;
}
/*
AString CHandler::GetComment()
{
const Byte *p = _metadata;
size_t size = _metadata.GetCapacity();
AString res;
if (size > 0)
{
p++;
size--;
for (;;)
{
if (size < 2)
break;
int len = Get16(p);
p += 2;
size -= 2;
if (len == 0 || (size_t)len > size)
break;
{
AString temp;
char *sz = temp.GetBuffer(len);
memcpy(sz, p, len);
sz[len] = 0;
temp.ReleaseBuffer();
if (!res.IsEmpty())
res += '\n';
res += temp;
}
p += len;
size -= len;
if (size < 1)
break;
Byte type = *p++;
size--;
bool ok = false;
switch(type)
{
case 0:
{
if (size < 8)
break;
ok = true;
Byte reverse[8];
for (int i = 0; i < 8; i++)
{
bool little_endian = 1;
if (little_endian)
reverse[i] = p[7 - i];
else
reverse[i] = p[i];
}
double d = *(double *)reverse;
char temp[32];
sprintf(temp, " = %.3f", d);
res += temp;
p += 8;
size -= 8;
break;
}
case 8:
{
if (size < 4)
break;
ok = true;
// UInt32 numItems = Get32(p);
p += 4;
size -= 4;
break;
}
}
if (!ok)
break;
}
}
return res;
}
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NWindows::NCOM::CPropVariant prop;
switch(propID)
{
case kpidComment: prop = GetComment(); break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
*/
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)
{
CRecordVector<CItem> items;
const UInt32 kHeaderSize = 13;
Byte header[kHeaderSize];
RINOK(ReadStream_FALSE(stream, header, kHeaderSize));
if (header[0] != 'F' ||
header[1] != 'L' ||
header[2] != 'V' ||
header[3] != 1 ||
(header[4] & 0xFA) != 0)
return S_FALSE;
UInt32 offset = Get32(header + 5);
if (offset != 9 || Get32(header + 9) != 0)
return S_FALSE;
offset += 4;
CByteBuffer inBuf;
size_t fileSize;
{
UInt64 fileSize64;
RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize64));
if (fileSize64 > kFileSizeMax)
return S_FALSE;
if (callback)
RINOK(callback->SetTotal(NULL, &fileSize64))
RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
fileSize = (size_t)fileSize64;
inBuf.SetCapacity(fileSize);
for (size_t pos = 0; pos < fileSize;)
{
UInt64 offset64 = pos;
if (callback)
RINOK(callback->SetCompleted(NULL, &offset64))
size_t rem = MyMin(fileSize - pos, (size_t)(1 << 20));
RINOK(ReadStream_FALSE(stream, inBuf + pos, rem));
pos += rem;
}
}
int lasts[kNumTypes];
int i;
for (i = 0; i < kNumTypes; i++)
lasts[i] = -1;
while (offset < fileSize)
{
CItem item;
item.Offset = offset;
const Byte *buf = inBuf + offset;
offset += kTagHeaderSize;
if (offset > fileSize)
return S_FALSE;
item.Type = buf[0];
UInt32 size = Get24(buf + 1);
if (size < 1)
return S_FALSE;
// item.Time = Get24(buf + 4);
// item.Time |= (UInt32)buf[7] << 24;
if (Get24(buf + 8) != 0) // streamID
return S_FALSE;
UInt32 curSize = kTagHeaderSize + size + 4;
item.Size = curSize;
offset += curSize - kTagHeaderSize;
if (offset > fileSize)
return S_FALSE;
if (Get32(buf + kTagHeaderSize + size) != kTagHeaderSize + size)
return S_FALSE;
// printf("\noffset = %6X type = %2d time = %6d size = %6d", (UInt32)offset, item.Type, item.Time, item.Size);
if (item.Type == kType_Meta)
{
// _metadata = item.Buf;
}
else
{
if (item.Type != kType_Audio && item.Type != kType_Video)
return S_FALSE;
if (items.Size() >= kNumChunksMax)
return S_FALSE;
Byte firstByte = buf[kTagHeaderSize];
Byte subType, props;
if (item.Type == kType_Audio)
{
subType = firstByte >> 4;
props = firstByte & 0xF;
}
else
{
subType = firstByte & 0xF;
props = firstByte >> 4;
}
int last = lasts[item.Type];
if (last < 0)
{
CItem2 item2;
item2.RefBuf = item2.BufSpec = new CReferenceBuf;
item2.Size = curSize;
item2.Type = item.Type;
item2.SubType = subType;
item2.Props = props;
item2.NumChunks = 1;
item2.SameSubTypes = true;
lasts[item.Type] = _items2.Add(item2);
}
else
{
CItem2 &item2 = _items2[last];
if (subType != item2.SubType)
item2.SameSubTypes = false;
item2.Size += curSize;
item2.NumChunks++;
}
items.Add(item);
}
}
_isRaw = (_items2.Size() == 1);
for (i = 0; i < _items2.Size(); i++)
{
CItem2 &item2 = _items2[i];
CByteBuffer &itemBuf = item2.BufSpec->Buf;
if (_isRaw)
{
if (!item2.SameSubTypes)
return S_FALSE;
itemBuf.SetCapacity((size_t)item2.Size - (kTagHeaderSize + 4 + 1) * item2.NumChunks);
item2.Size = 0;
}
else
{
itemBuf.SetCapacity(kHeaderSize + (size_t)item2.Size);
memcpy(itemBuf, header, kHeaderSize);
itemBuf[4] = item2.IsAudio() ? kFlag_Audio : kFlag_Video;
item2.Size = kHeaderSize;
}
}
for (i = 0; i < items.Size(); i++)
{
const CItem &item = items[i];
CItem2 &item2 = _items2[lasts[item.Type]];
size_t size = item.Size;
const Byte *src = inBuf + item.Offset;
if (_isRaw)
{
src += kTagHeaderSize + 1;
size -= (kTagHeaderSize + 4 + 1);
}
memcpy(item2.BufSpec->Buf + item2.Size, src, size);
item2.Size += size;
}
return S_OK;
}
STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)
{
COM_TRY_BEGIN
Close();
HRESULT res;
try
{
res = Open2(inStream, callback);
if (res == S_OK)
_stream = inStream;
}
catch(...) { res = S_FALSE; }
if (res != S_OK)
{
Close();
return S_FALSE;
}
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::Close()
{
_stream.Release();
_items2.Clear();
// _metadata.SetCapacity(0);
return S_OK;
}
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
*numItems = _items2.Size();
return S_OK;
}
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Int32 testMode, IArchiveExtractCallback *extractCallback)
{
COM_TRY_BEGIN
bool allFilesMode = (numItems == (UInt32)-1);
if (allFilesMode)
numItems = _items2.Size();
if (numItems == 0)
return S_OK;
UInt64 totalSize = 0;
UInt32 i;
for (i = 0; i < numItems; i++)
totalSize += _items2[allFilesMode ? i : indices[i]].Size;
extractCallback->SetTotal(totalSize);
totalSize = 0;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
for (i = 0; i < numItems; i++)
{
lps->InSize = lps->OutSize = totalSize;
RINOK(lps->SetCur());
CMyComPtr<ISequentialOutStream> outStream;
Int32 askMode = testMode ?
NExtract::NAskMode::kTest :
NExtract::NAskMode::kExtract;
UInt32 index = allFilesMode ? i : indices[i];
const CItem2 &item = _items2[index];
RINOK(extractCallback->GetStream(index, &outStream, askMode));
totalSize += item.Size;
if (!testMode && !outStream)
continue;
RINOK(extractCallback->PrepareOperation(askMode));
if (outStream)
{
RINOK(WriteStream(outStream, item.BufSpec->Buf, item.BufSpec->Buf.GetCapacity()));
}
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
}
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
COM_TRY_BEGIN
*stream = 0;
CBufInStream *streamSpec = new CBufInStream;
CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
streamSpec->Init(_items2[index].BufSpec);
*stream = streamTemp.Detach();
return S_OK;
COM_TRY_END
}
static IInArchive *CreateArc() { return new CHandler; }
static CArcInfo g_ArcInfo =
{ L"FLV", L"flv", 0, 0xD6, { 'F', 'L', 'V' }, 3, false, CreateArc, 0 };
REGISTER_ARC(Flv)
}}