| // 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) |
| |
| }} |