// Copyright Epic Games, Inc. All Rights Reserved. #include "BufferedDataReader.h" #include "InfoLog.h" DECLARE_LOG_CATEGORY_EXTERN(LogElectraBufferReader, Log, All); DEFINE_LOG_CATEGORY(LogElectraBufferReader); namespace Electra { enum EBufferedDataReaderError { FileTooShort = 1, ReadAssetDataFailed = 2, }; void FBufferedDataReader::CreateNewArea(int64 NumBytes, int64 FromOffset) { if (TotalDataSize >= 0 && FromOffset + NumBytes > TotalDataSize) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("CreateNewArea(): clamp request to end of file size")); NumBytes = TotalDataSize - FromOffset; } TUniquePtr NewArea = MakeUnique(); NewArea->Data = (const uint8*) FMemory::Malloc(NumBytes); if (NewArea->Data) { NewArea->Size = NumBytes; NewArea->StartOffset = FromOffset; int64 TotalSize = -1; int64 NumRead = DataProvider->OnReadAssetData((void*) NewArea->Data, NumBytes, FromOffset, &TotalSize); UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("CreateNewArea(): read %lld bytes from offset %lld; total file size = %lld, received %lld bytes"), (long long int)NumBytes, (long long int)FromOffset, (long long int)TotalSize, (long long int)NumRead); if (TotalSize >= 0) { TotalDataSize = TotalSize; } if (NumRead >= 0) { if (NumRead < NumBytes || (TotalSize >= 0 && FromOffset + NumRead >= TotalSize)) { NewArea->bEOS = true; } // Any existing area before this one can't be at EOS any more. for(auto &a : Areas) { if (a->StartOffset < FromOffset) { a->bEOS = false; } else { break; } } NewArea->Size = NumRead; BytesRemainingInArea = NewArea->Size; CurrentArea = NewArea.Get(); Areas.Emplace(MoveTemp(NewArea)); Areas.Sort([](const TUniquePtr& a, const TUniquePtr& b) { return a->StartOffset < b->StartOffset; }); } else { IDataProvider::EError Error = static_cast(NumRead); if (Error == IDataProvider::EError::Failed) { LastError.SetError(UEMEDIA_ERROR_READ_ERROR).SetFacility(Facility::EFacility::BufferedDataReader).SetCode(EBufferedDataReaderError::ReadAssetDataFailed).SetMessage(TEXT("OnReadAssetData() failed")); } } } } bool FBufferedDataReader::EnlargeCurrentAreaBy(int64 NumBytesToAdd) { // Reading past the known end of the file? if (TotalDataSize >= 0 && CurrentArea->StartOffset + CurrentArea->Size + NumBytesToAdd > TotalDataSize) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("EnlargeCurrentAreaBy(): clamp request of %lld to %lld at end of file"), (long long int)NumBytesToAdd, (long long int)(TotalDataSize - (CurrentArea->StartOffset + CurrentArea->Size))); NumBytesToAdd = TotalDataSize - (CurrentArea->StartOffset + CurrentArea->Size); } // Check if there is an area following the current one and whether we would reach into it. int32 CurrentIndex = Areas.IndexOfByPredicate([&](const TUniquePtr& e) { return e.Get() == CurrentArea; }); check(CurrentIndex != INDEX_NONE); if (CurrentIndex == INDEX_NONE) { return false; } FArea* Next = Areas.Num() > CurrentIndex+1 ? Areas[CurrentIndex+1].Get() : nullptr; int64 ReallocSize = CurrentArea->Size + NumBytesToAdd; // Will we reach into the next area if we enlarge this one by the given amount? if (Next && CurrentArea->StartOffset + CurrentArea->Size + NumBytesToAdd >= Next->StartOffset) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("EnlargeCurrentAreaBy(): reaching into area of offset %lld, clamping"), (long long int)Next->StartOffset); // Yes. Trim the amount to read to avoid overlap. NumBytesToAdd = Next->StartOffset - (CurrentArea->StartOffset + CurrentArea->Size); // We need one large block for the current block plus the amount to read plus the next block. ReallocSize = CurrentArea->Size + NumBytesToAdd + Next->Size; } else { // No, the areas will remain separate. No need to consolidate after reading. Next = nullptr; } CurrentArea->Data = (const uint8*) FMemory::Realloc((void*)CurrentArea->Data, ReallocSize); if (CurrentArea->Data) { int64 TotalSize = -1; int64 NumRead = DataProvider->OnReadAssetData((void*)(CurrentArea->Data + CurrentArea->Size), NumBytesToAdd, CurrentArea->StartOffset + CurrentArea->Size, &TotalSize); UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("CreateNewArea(): read %lld bytes from offset %lld; total file size = %lld, received %lld bytes"), (long long int)NumBytesToAdd, (long long int)(CurrentArea->StartOffset + CurrentArea->Size), (long long int)TotalSize, (long long int)NumRead); if (TotalSize >= 0) { TotalDataSize = TotalSize; } if (NumRead >= 0) { if (NumRead < NumBytesToAdd || (TotalSize >= 0 && CurrentArea->StartOffset + CurrentArea->Size + NumRead >= TotalSize)) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("EnlargeCurrentAreaBy(): reached the end of the file")); CurrentArea->bEOS = true; } CurrentArea->Size += NumRead; BytesRemainingInArea += NumRead; // Consolidate forward? if (Next) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("EnlargeCurrentAreaBy(): merging blocks from offsets %lld and %lld into one"), (long long int)CurrentArea->StartOffset, (long long int)Next->StartOffset); FMemory::Memcpy((void*)(CurrentArea->Data + CurrentArea->Size), Next->Data, Next->Size); CurrentArea->bEOS = Next->bEOS; CurrentArea->Size += Next->Size; BytesRemainingInArea += Next->Size; Areas.RemoveAt(CurrentIndex + 1); } return true; } else { IDataProvider::EError Error = static_cast(NumRead); if (Error == IDataProvider::EError::Failed) { LastError.SetError(UEMEDIA_ERROR_READ_ERROR).SetFacility(Facility::EFacility::BufferedDataReader).SetCode(EBufferedDataReaderError::ReadAssetDataFailed).SetMessage(TEXT("OnReadAssetData() failed")); } } } return false; } bool FBufferedDataReader::PrepareToRead(int64 NumBytes) { // No area? if (!CurrentArea) { // Create a new area and fill it with data. CreateNewArea(NumBytes > kDefaultReadSize ? NumBytes: kDefaultReadSize, CurrentOffset); UpdateReadDataPointer(); return CurrentArea != nullptr; } // Not enough data in the area? while(BytesRemainingInArea < NumBytes && !CurrentArea->bEOS) { // Enlarge the area by at least a default size to avoid repeated resizes. if (!EnlargeCurrentAreaBy((NumBytes > kDefaultReadSize ? NumBytes: kDefaultReadSize) - BytesRemainingInArea)) { return false; } } if (BytesRemainingInArea < NumBytes) { return false; } UpdateReadDataPointer(); return true; } bool FBufferedDataReader::IsAtEOS() { if (CurrentArea) { return BytesRemainingInArea == 0 && CurrentArea->bEOS; } return false; } bool FBufferedDataReader::SkipOver(int64 NumBytes) { #if 1 return SeekTo(CurrentOffset + NumBytes); #else if (!PrepareToRead(NumBytes)) { return false; } if (BytesRemainingInArea < NumBytes) { LastError.SetError(UEMEDIA_ERROR_READ_ERROR).SetFacility(Facility::EFacility::BufferedDataReader).SetCode(EBufferedDataReaderError::FileTooShort).SetMessage(TEXT("File too short to skip requested amount of bytes")); return false; } BytesRemainingInArea -= NumBytes; CurrentOffset += NumBytes; return true; #endif } bool FBufferedDataReader::ReadByteArray(TArray& OutValue, int64 NumBytes) { if (!PrepareToRead(NumBytes)) { return false; } NumBytes = NumBytes <= BytesRemainingInArea ? NumBytes : BytesRemainingInArea; OutValue.AddUninitialized(NumBytes); FMemory::Memcpy(OutValue.GetData(), ReadDataPointer, NumBytes); CurrentOffset += NumBytes; BytesRemainingInArea -= NumBytes; return true; } bool FBufferedDataReader::PeekU8(uint8& OutValue) { if (!PrepareToRead(sizeof(uint8))) { return false; } OutValue = *ReadDataPointer; return true; } bool FBufferedDataReader::PeekU16BE(uint16& OutValue) { if (!PrepareToRead(sizeof(uint16))) { return false; } const uint16* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = Utils::EndianSwap(*rp); #else OutValue = *rp; #endif return true; } bool FBufferedDataReader::PeekU32BE(uint32& OutValue) { if (!PrepareToRead(sizeof(uint32))) { return false; } const uint32* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = Utils::EndianSwap(*rp); #else OutValue = *rp; #endif return true; } bool FBufferedDataReader::PeekU64BE(uint64& OutValue) { if (!PrepareToRead(sizeof(uint64))) { return false; } const uint64* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = Utils::EndianSwap(*rp); #else OutValue = *rp; #endif return true; } bool FBufferedDataReader::PeekU16LE(uint16& OutValue) { if (!PrepareToRead(sizeof(uint16))) { return false; } const uint16* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = *rp; #else OutValue = Utils::EndianSwap(*rp); #endif return true; } bool FBufferedDataReader::PeekU32LE(uint32& OutValue) { if (!PrepareToRead(sizeof(uint32))) { return false; } const uint32* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = *rp; #else OutValue = Utils::EndianSwap(*rp); #endif return true; } bool FBufferedDataReader::PeekU64LE(uint64& OutValue) { if (!PrepareToRead(sizeof(uint64))) { return false; } const uint64* rp = reinterpret_cast(ReadDataPointer); #if PLATFORM_LITTLE_ENDIAN OutValue = *rp; #else OutValue = Utils::EndianSwap(*rp); #endif return true; } bool FBufferedDataReader::ReadU8(uint8& OutValue) { if (PeekU8(OutValue)) { CurrentOffset += sizeof(uint8); BytesRemainingInArea -= sizeof(uint8); return true; } return false; } bool FBufferedDataReader::ReadU16LE(uint16& OutValue) { if (PeekU16LE(OutValue)) { CurrentOffset += sizeof(uint16); BytesRemainingInArea -= sizeof(uint16); return true; } return false; } bool FBufferedDataReader::ReadU32LE(uint32& OutValue) { if (PeekU32LE(OutValue)) { CurrentOffset += sizeof(uint32); BytesRemainingInArea -= sizeof(uint32); return true; } return false; } bool FBufferedDataReader::ReadU64LE(uint64& OutValue) { if (PeekU64LE(OutValue)) { CurrentOffset += sizeof(uint64); BytesRemainingInArea -= sizeof(uint64); return true; } return false; } bool FBufferedDataReader::ReadU16BE(uint16& OutValue) { if (PeekU16BE(OutValue)) { CurrentOffset += sizeof(uint16); BytesRemainingInArea -= sizeof(uint16); return true; } return false; } bool FBufferedDataReader::ReadU32BE(uint32& OutValue) { if (PeekU32BE(OutValue)) { CurrentOffset += sizeof(uint32); BytesRemainingInArea -= sizeof(uint32); return true; } return false; } bool FBufferedDataReader::ReadU64BE(uint64& OutValue) { if (PeekU64BE(OutValue)) { CurrentOffset += sizeof(uint64); BytesRemainingInArea -= sizeof(uint64); return true; } return false; } bool FBufferedDataReader::SeekTo(int64 AbsolutePosition) { if (TotalDataSize >= 0 && AbsolutePosition > TotalDataSize) { UE_LOG(LogElectraBufferReader, VeryVerbose, TEXT("SeekTo(): Seeking beyond the end of the file")); return false; } // Check if we have the area at the target position. FArea* DestinationArea = nullptr; for(int32 i=0,iMax=Areas.Num(); i= Areas[i]->StartOffset && AbsolutePosition <= Areas[i]->StartOffset+Areas[i]->Size) { DestinationArea = Areas[i].Get(); break; } } if (DestinationArea) { CurrentArea = DestinationArea; CurrentOffset = AbsolutePosition; BytesRemainingInArea = DestinationArea->StartOffset + DestinationArea->Size - AbsolutePosition; UpdateReadDataPointer(); } else { // Area not loaded yet. Set data pointers to zero so the next read will // load in the area at that location. CurrentArea = nullptr; CurrentOffset = AbsolutePosition; BytesRemainingInArea = 0; ReadDataPointer = nullptr; } return true; } } // namespace Electra