// 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 RefBuf; bool IsAudio() const { return Type == kType_Audio; } }; class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { int _isRaw; CMyComPtr _stream; CObjectVector _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 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 progress = lps; lps->Init(extractCallback, false); for (i = 0; i < numItems; i++) { lps->InSize = lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr 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 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) }}