Перейти к содержимому
Zone of Games Forum
NightGuardianX

Утилита для импорта озвучки из Metro Awakening

Рекомендованные сообщения

Всем привет, я новенький на этом форуме, так что прошу сильно не бить.

Недавно занялся озвучкой игровых проектов, в последний раз взгляд упал на свежевыпущенную Metro (она на unreal engine 5). Увидел пост по Pacific Drive, хотел уточнить — есть ли что-то универсальное или схожее для извлечения звуков? 

Поделиться сообщением


Ссылка на сообщение
В 06.11.2024 в 13:14, NightGuardianX сказал:

есть ли что-то универсальное или схожее для извлечения звуков?

Для анрила есть Fmodel чтобы доставать файлы из pak и utoc/ucas

Звуки часто в формате wwise (.wem контейнеры)

Для конвертации звука в wav есть, например, vgmstream

В играх начиная от unreal 4.27+ звук может быть в Bink Audio (.binka контейнер)

Для конвертации звука в wav из Bink Audio есть binkadec (vgmstream не понимает .binka контейнер от unreal)

.binka контейнер от unreal (ABEU — Unreal Engine Bink Audio) не путать со старым binka контейнером от Miles Sound System (1FCB)

 

Metro Awakening на unreal 5.2.1

ресурсы в IOStore файлах (папка Metro Awakening\Impact\Content\Paks\)

не шифрованные, сжатые Oodle

звук Unreal Engine Bink Audio в нескольких utoc/ucas:

по пути Impact\Content\Maps\ — сюжет

и Impact\Content\Assets\Audio\VO\ — стандартные фразы

субтитры в Impact\Content\L10N (папка ru — русские, uk — украинские) в uasset с постфиксом “_Sub”

английские субтитры в папках рядом со звуком

Fmodel умеет экспортировать звук в .binka: нужно два раза кликнуть на uasset звука и в папке экспорта будет соответствующий binka файл

Чтобы Fmodel экспортировал сразу в wav, нужно закинуть binkadec.exe в папку .data в папке экспорта (Output Directory в настройках)

 

 

 

Изменено пользователем xoixa

Поделиться сообщением


Ссылка на сообщение
Скрытый текст

извлечь папку субтитров, например Impact\Content\L10N\ru:

в Fmodel (пкм на папку > Export Raw Data (.uasset) )

в папке экспорта, рядом с папкой ru, создать файл Sub.bms со следующим содержимым:


#Metro Awakening subtitles text export
#Quickbms script

get SubName basename
get Size asize
log MEMORY_FILE 0 0
put SubName string  MEMORY_FILE
put 0x09 byte MEMORY_FILE // tab

string SubName - 4 // remove _Sub postfix

//parse uasset file
get Null long
get Offset long
math Offset + 120
goto Offset

for
	//read string
	get StrSize long
	if StrSize < 0 // unicode
		math StrSize * -2
		getDString Str StrSize
		set Str unicode Str
	else
		getDString Str StrSize
	endif
	
	put Str string MEMORY_FILE
	savepos Pos

	//try next string
	math Pos + 100
	if Pos > Size		
		put 0x0D byte MEMORY_FILE
		break // end 
	else
		put 0x6e5c short MEMORY_FILE // \n
		goto Pos
		continue // read next string
	endif
next

#write to file
get MFSize asize MEMORY_FILE
append
encryption replace "\x00" "" //remove Zero bytes
log Sub.txt 0 MFSize MEMORY_FILE

там же создать файл export_sub.bat со следующим содержимым:


@echo off
for /R %%1 in (*_Sub.uasset) do quickbms.exe -o Sub.bms %%1
@pause

положить рядом quickbms.exe и запустить export_sub.bat

субтитры будут в файле Subs.txt

для английских субтитров эти же файлы поместить в папку Impact\Content\Assets\Audio\VO и Impact\Content\Maps\

скачать русские субтитры

 

Скрытый текст

извлечь все папки Impact\Content\Maps из всех utoc/ucas:

в Fmodel (пкм на папку > Export Raw Data (.uasset) )

 

в папке экспорта, в папке Maps, создать файл export_binka.bms со следующим содержимым:


#Metro Awakening bink audio export
#Quickbms script
#Use on uasset file
#uasset has binka header
#ubulk is audio data

get NAME basename
string NAME + ".binka"
findloc OFFSET binary "ABEU" // magick
get SIZE asize
math SIZE - OFFSET

open FDDE ubulk 1 Exist
if Exist = 0 //no ubulk
	log NAME OFFSET SIZE
else // has ubulk
	math SIZE - 8 //size of ubulk two times
	log NAME OFFSET SIZE
	
	get SIZE asize 1
	append
	log NAME 0 SIZE 1 //append ubulk audio data
endif 

там же создать файл towav.bat со следующим содержимым:


@echo off
if not exist wav mkdir wav
for /R %%1 in (*.uasset) do quickbms -o export_binka.bms %%1
for /R %%1 in (*.binka) do binkadec -i %%1 -o wav/%%~n1.wav
del *.binka
@pause

положить рядом quickbms.exe и binkadec.exe  и запустить towav.bat

готовые файлы будут в папке wav

повторить с папкой Impact\Content\Assets\Audio\VO\  из pakchunk0-Windows utoc/ucas

 

Изменено пользователем xoixa

Поделиться сообщением


Ссылка на сообщение
В 08.11.2024 в 18:39, NightGuardianX сказал:

@xoixa Спасибо, буду разбираться!

Только тут, нашел что-то рабочее. А как запаковать обратно, чтобы в игру добавить. Мне только как wav обратно в .uasset и .bulk. Хочу сделать озвучку для небольшой игры Scholar's Mate.

Изменено пользователем vananagornyi

Поделиться сообщением


Ссылка на сообщение
В 05.01.2025 в 15:16, vananagornyi сказал:

А как запаковать обратно, чтобы в игру добавить. Мне только как wav обратно в .uasset и .bulk. Хочу сделать озвучку для небольшой игры Scholar's Mate.

Как wav не получится. Нужно конвертировать обратно в .binka контейнер от unreal

Насколько я знаю, это можно сделать только в самом Unreal Engine из egs

Есть исходники декодера, на основе которых, можно(наверное), написать энкодер

Скрытый текст

#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iostream>

#include <cassert>

#include <fstream>

#include <argparse/argparse.hpp>
#include <memory>
#include <string>
#include <vector>

#include "binka_ue_decode.h"
#include "binka_ue_file_header.h"

using int16 = int16_t;
using int32 = int32_t;

constexpr int MAX_BINK_AUDIO_CHANNELS = 16;
inline uint32_t Align32(size_t size) { return (uint32_t)((size + 31) & ~31); }

inline void *PTR_ADD(void *ptr, uint32_t off) {
  return (void *)(((uint8_t *)(ptr)) + (off));
}

struct BinkAudioDecoder {
  // # of low level bink audio streams (max 2 chans each)
  uint8 StreamCount;
  // # of current bytes in the res
  uint32 OutputReservoirValidBytes;
  // # max size of the res.
  uint32 OutputReservoirTotalBytes;
  // offsets to the various pieces of the decoder
  uint16 ToDeinterlaceBufferOffset32;
  uint16 ToSeekTableOffset32;
  uint16 ToOutputReservoirOffset32;
  // # of entries in the seek table - the entries are byte sizes, so
  // when decoding the seek table we convert to file offsets. This means
  // we actually have +1 entries in the decoded seek table due to the standard
  // "span -> edge" count thing.
  uint16 SeekTableCount;
  // # of low level bink blocks one seek table entry spans
  uint16 FramesPerSeekTableEntry;
  // # of frames we need to eat before outputting data due to a sample
  // accurate seek.
  uint16 ConsumeFrameCount;

  uint8 *StreamChannels() { return (uint8 *)(this + 1); }
  void **Decoders() {
    return (void **)PTR_ADD(
        this, Align32(sizeof(uint8) * StreamCount + sizeof(BinkAudioDecoder)));
  }
  uint8 *OutputReservoir() {
    return (uint8 *)(PTR_ADD(this, ToOutputReservoirOffset32 * 32U));
  }
  int16 *DeinterlaceBuffer() {
    return (int16 *)(PTR_ADD(this, ToDeinterlaceBufferOffset32 * 32U));
  }
  uint32 *
  SeekTable() // [SeekTableCount + 1] if SeekTableCount != 0, otherwise invalid.
  {
    return (uint32 *)(PTR_ADD(this, ToSeekTableOffset32 * 32U));
  }
};

struct audioinfo {
  int SampleRate;
  int TrueSampleCount;
  int SampleDataSize;
  int NumChannels;
  float Duration;
  int AudioDataOffset;

public:
  friend std::string to_string(const audioinfo &info) {
    std::string str = "";
    str += "SampleRate: " + std::to_string(info.SampleRate) + "\n";
    str += "TrueSampleCount: " + std::to_string(info.TrueSampleCount) + "\n";
    str += "SampleDataSize: " + std::to_string(info.SampleDataSize) + "\n";
    str += "NumChannels: " + std::to_string(info.NumChannels) + "\n";
    str += "Duration: " + std::to_string(info.Duration) + "\n";
    str += "AudioDataOffset: " + std::to_string(info.AudioDataOffset) + "\n";
    return str;
  }
};

bool ParseHeader(const uint8 *InSrcBufferData, uint32 InSrcBufferDataSize,
                 struct audioinfo *info) {
  auto SrcBufferData = InSrcBufferData;
  auto SrcBufferDataSize = InSrcBufferDataSize;
  auto SrcBufferOffset = 0;
  auto CurrentSampleCount = 0;

  assert(InSrcBufferDataSize >= sizeof(BinkAudioFileHeader));
  if (InSrcBufferDataSize < sizeof(BinkAudioFileHeader)) {
    return false;
  }

  BinkAudioFileHeader *Header = (BinkAudioFileHeader *)InSrcBufferData;
  if (Header->tag != 'UEBA') {
    return false;
  }
  if (Header->version != 1) {
    return false;
  }

  auto SampleRate = Header->rate;
  auto TrueSampleCount = Header->sample_count;
  auto NumChannels = Header->channels;
  auto MaxCompSpaceNeeded = Header->max_comp_space_needed;

  uint32 SeekTableSize = Header->seek_table_entry_count * sizeof(uint16);
  if (sizeof(BinkAudioFileHeader) + SeekTableSize > InSrcBufferDataSize) {
    return false;
  }

  // Store the offset to where the audio data begins
  auto AudioDataOffset = sizeof(BinkAudioFileHeader) + SeekTableSize;

  // Write out the the header info
  if (info) {
    info->SampleRate = Header->rate;
    info->NumChannels = Header->channels;
    info->SampleDataSize =
        Header->sample_count * info->NumChannels * sizeof(int16);
    info->Duration = (float)Header->sample_count / info->SampleRate;
    info->AudioDataOffset = AudioDataOffset;
    info->TrueSampleCount = TrueSampleCount;
  }

  return true;
}

bool CreateDecoder(const uint8_t *SrcBufferData, uint32_t SrcBufferDataSize,
                   uint32_t NumChannels, uint32_t SampleRate,
                   uint8_t *&RawMemory, BinkAudioDecoder *&Decoder,
                   uint32_t &SrcBufferOffset) {

  auto GetMaxFrameSizeSamples = [SampleRate]() -> uint32_t {
    if (SampleRate >= 44100) {
      return 1920;
    } else if (SampleRate >= 22050) {
      return 960;
    } else {
      return 480;
    }
  };
  UEBinkAudioDecodeInterface *BinkInterface = nullptr;

  BinkInterface = UnrealBinkAudioDecodeInterface();

  if (BinkInterface == nullptr) {
    return false; // only happens if we dont have libs.
  }

  BinkAudioFileHeader *Header = (BinkAudioFileHeader *)SrcBufferData;

  // Bink is max stereo per stream
  uint32 StreamCount = (NumChannels + 1) >> 1;

  // Figure memory for buffers:

  // Deinterlace - buffer space for interleaving multi stream binks in to a
  // standard interleaved format.
  uint32 DeinterlaceMemory = 0;
  if (StreamCount > 1) {
    DeinterlaceMemory = GetMaxFrameSizeSamples() * sizeof(int16) * 2;
  }

  // Space to store a decoded block.
  uint32 OutputReservoirMemory =
      NumChannels * GetMaxFrameSizeSamples() * sizeof(int16);

  // Space for the decoder state
  uint32 DecoderMemoryTotal = 0;
  uint32 DecoderMemoryPerStream[MAX_BINK_AUDIO_CHANNELS / 2];
  {
    uint32 RemnChannels = NumChannels;
    for (uint32 StreamIndex = 0; StreamIndex < StreamCount; StreamIndex++) {
      uint32 StreamChannels = RemnChannels;
      if (StreamChannels > 2) {
        StreamChannels = 2;
      }
      RemnChannels -= StreamChannels;
      DecoderMemoryPerStream[StreamIndex] =
          BinkInterface->MemoryFn(SampleRate, StreamChannels);
      DecoderMemoryTotal += DecoderMemoryPerStream[StreamIndex];
    }
  }

  // Space for the decoder pointers
  uint32 PtrMemory = Align32(sizeof(void *) * StreamCount);

  // Space for ourselves + the channel count for each stream.
  uint32 StructMemory =
      Align32(sizeof(BinkAudioDecoder) + sizeof(uint8) * StreamCount);

  // Space for decoded seek table
  uint32 SeekTableMemory = 0;
  if (Header->seek_table_entry_count) {
    SeekTableMemory =
        Align32(sizeof(uint32) * (Header->seek_table_entry_count + 1));
  }

  uint32 TotalMemory = DecoderMemoryTotal + PtrMemory + StructMemory +
                       OutputReservoirMemory + DeinterlaceMemory +
                       SeekTableMemory;

  //
  // Allocate and save offsets
  //
  RawMemory = (uint8 *)malloc(TotalMemory);
  memset(RawMemory, 0, TotalMemory);

  Decoder = (BinkAudioDecoder *)RawMemory;

  Decoder->StreamCount = StreamCount;
  Decoder->OutputReservoirTotalBytes = OutputReservoirMemory;
  Decoder->SeekTableCount = Header->seek_table_entry_count;
  Decoder->FramesPerSeekTableEntry = Header->blocks_per_seek_table_entry;

  // See layout discussion in class declaration
  void **Decoders = Decoder->Decoders();
  uint8 *CurrentMemory = (uint8 *)PTR_ADD(Decoders, PtrMemory);
  uint8 *Channels = Decoder->StreamChannels();

  // Init decoders
  {
    uint8 RemnChannels = NumChannels;
    for (uint32 StreamIndex = 0; StreamIndex < StreamCount; StreamIndex++) {
      uint32 StreamChannels = RemnChannels;
      if (StreamChannels > 2) {
        StreamChannels = 2;
      }
      RemnChannels -= StreamChannels;

      Channels[StreamIndex] = StreamChannels;
      Decoders[StreamIndex] = (void **)CurrentMemory;
      CurrentMemory += DecoderMemoryPerStream[StreamIndex];
      BinkInterface->OpenFn(Decoders[StreamIndex], SampleRate, StreamChannels,
                            true, true);
    }
  }

  Decoder->ToOutputReservoirOffset32 =
      (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += OutputReservoirMemory;

  Decoder->ToDeinterlaceBufferOffset32 =
      (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += DeinterlaceMemory;

  Decoder->ToSeekTableOffset32 = (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += SeekTableMemory;

  // Decode the seek table
  if (Decoder->SeekTableCount) {
    uint32 *SeekTable = Decoder->SeekTable();
    uint16 *EncodedSeekTable =
        (uint16 *)(SrcBufferData + sizeof(BinkAudioFileHeader));
    uint32 CurrentSeekOffset = 0;

    // the seek table has deltas from last, and we want absolutes
    for (uint32 i = 0; i < Decoder->SeekTableCount; i++) {
      SeekTable[i] = CurrentSeekOffset;
      CurrentSeekOffset += EncodedSeekTable[i];
    }
    SeekTable[Decoder->SeekTableCount] = CurrentSeekOffset;
  }

  SrcBufferOffset = sizeof(BinkAudioFileHeader) +
                    Header->seek_table_entry_count * sizeof(uint16);
  return true;
}

int Decode(BinkAudioDecoder *&Decoder, const uint8 *CompressedData,
           const int32 CompressedDataSize, uint32_t NumChannels,
           uint32_t SampleRate, const uint16_t MaxCompSpaceNeeded,
           uint8 *OutPCMData, const int32 OutputPCMDataSize) {
  auto GetMaxFrameSizeSamples = [SampleRate]() -> uint32_t {
    if (SampleRate >= 44100) {
      return 1920;
    } else if (SampleRate >= 22050) {
      return 960;
    } else {
      return 480;
    }
  };
  UEBinkAudioDecodeInterface *BinkInterface = 0;

  BinkInterface = UnrealBinkAudioDecodeInterface();

  if (BinkInterface == 0) {
    return 0; // only happens if we dont have libs.
  }

  uint32 RemnOutputPCMDataSize = OutputPCMDataSize;
  uint32 RemnCompressedDataSize = CompressedDataSize;
  const uint8 *CompressedDataEnd = CompressedData + CompressedDataSize;

  //
  // In the event we need to copy to a stack buffer, we alloca() it here so
  // that it's not inside the loop (for static analysis). We don't touch the
  // memory until we need it so it's just a couple instructions for the
  // alloca().
  // (+8 for max block header size)
  uint8 *StackBlockBuffer =
      (uint8 *)alloca(MaxCompSpaceNeeded + BINK_UE_DECODER_END_INPUT_SPACE + 8);

  const uint32 DecodeSize =
      GetMaxFrameSizeSamples() * sizeof(int16) * NumChannels;
  while (RemnOutputPCMDataSize) {
    //
    // Drain the output reservoir before attempting a decode.
    //
    if (Decoder->OutputReservoirValidBytes) {
      uint32 CopyBytes = Decoder->OutputReservoirValidBytes;
      if (CopyBytes > RemnOutputPCMDataSize) {
        CopyBytes = RemnOutputPCMDataSize;
      }

      memcpy(OutPCMData, Decoder->OutputReservoir(), CopyBytes);

      // We use memmove here because we expect it to be very rare that we
      // don't consume the entire output reservoir in a call, so it's not
      // worth managing a cursor. Just move down the remnants, which we expect
      // to be zero.
      if (Decoder->OutputReservoirValidBytes != CopyBytes) {
        std::memmove(Decoder->OutputReservoir(),
                     Decoder->OutputReservoir() + CopyBytes,
                     Decoder->OutputReservoirValidBytes - CopyBytes);
      }
      Decoder->OutputReservoirValidBytes -= CopyBytes;

      RemnOutputPCMDataSize -= CopyBytes;
      OutPCMData += CopyBytes;

      if (RemnOutputPCMDataSize == 0) {
        // we filled entirely from the output reservoir
        break;
      }
    }

    if (RemnCompressedDataSize == 0) {
      // This is the normal termination condition
      break;
    }

    

    if (BinkAudioValidateBlock(MaxCompSpaceNeeded, CompressedData,
                               RemnCompressedDataSize) !=
        BINK_AUDIO_BLOCK_VALID) {
      // The splitting system should ensure that we only ever get complete
      // blocks - so this is bizarre.
      // UE_LOG(LogBinkAudioDecoder, Warning,
      //        TEXT("Got weird buffer, validate returned %d"),
      //        BinkAudioValidateBlock(MaxCompSpaceNeeded, CompressedData,
      //                               RemnCompressedDataSize));
      
      std::cout << "Got weird buffer, validate returned "
                << std::to_string(BinkAudioValidateBlock(
                       MaxCompSpaceNeeded, CompressedData,
                       RemnCompressedDataSize))
                << std::endl;
      break;
    }

    uint8 const *BlockStart = 0;
    uint8 const *BlockEnd = 0;
    uint32 TrimToSampleCount = 0;
    BinkAudioCrackBlock(CompressedData, &BlockStart, &BlockEnd,
                        &TrimToSampleCount);
    uint8 const *BlockBase = CompressedData;

    //
    // We need to make sure there's room available for Bink to read past the
    // end of the buffer (for vector decoding). If there's not, we need to
    // copy to a temp buffer.
    //
    bool HasRoomForDecode =
        (CompressedDataEnd - BlockEnd) > BINK_UE_DECODER_END_INPUT_SPACE;
    if (HasRoomForDecode == false) {
      // This looks weird, but in order for the advancement logic to work,
      // we need to replicate the entire block including the header.
      size_t BlockOffset = BlockStart - BlockBase;
      size_t BlockSize = BlockEnd - BlockBase;
      if (BlockSize > MaxCompSpaceNeeded + 8) // +8 for max block header size
      {
        // UE_LOG(LogBinkAudioDecoder, Error,
        //        TEXT("BAD! Validated block exceeds header max block size (%d
        //        "
        //             "vs %d)"),
        //        BlockSize, MaxCompSpaceNeeded);
        std::cout << "BAD! Validated block exceeds header max block size ("
                  << BlockSize << " vs " << MaxCompSpaceNeeded << ")"
                  << std::endl;
        // bErrorStateLatch = true;
        break;
      }

      memcpy(StackBlockBuffer, BlockBase, BlockSize);

      // this is technically not needed, but just so that any analysis shows
      // that we've initialized all the memory we touch.
      memset(StackBlockBuffer + BlockSize, 0, BINK_UE_DECODER_END_INPUT_SPACE);

      BlockBase = StackBlockBuffer;
      BlockStart = StackBlockBuffer + BlockOffset;
      BlockEnd = StackBlockBuffer + BlockSize;
    }

    //
    // If we're a simple single stream and we have enough output space,
    // just decode directly in to our destination to avoid some
    // copies.
    //
    // We also have to have a "simple" decode - i.e. aligned and no trimming.
    //
    if (Decoder->StreamCount == 1 && DecodeSize <= RemnOutputPCMDataSize &&
        TrimToSampleCount == ~0U && (((size_t)OutPCMData) & 0xf) == 0 &&
        Decoder->ConsumeFrameCount == 0) {
      uint32 DecodedBytes =
          BinkInterface->DecodeFn(Decoder->Decoders()[0], OutPCMData,
                                  RemnOutputPCMDataSize, &BlockStart, BlockEnd);

      if (DecodedBytes == 0) {

        // This means that our block check above succeeded and we still failed
        // - corrupted data!
        return 0;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);

      OutPCMData += DecodedBytes;
      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;
      RemnOutputPCMDataSize -= DecodedBytes;
      continue;
    }

    // Otherwise we route through the output reservoir.
    if (Decoder->StreamCount == 1) {
      uint32 DecodedBytes = BinkInterface->DecodeFn(
          Decoder->Decoders()[0], Decoder->OutputReservoir(),
          Decoder->OutputReservoirTotalBytes, &BlockStart, BlockEnd);
      if (DecodedBytes == 0) {

        // This means that our block check above succeeded and we still failed
        // - corrupted data!
        return 0;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);

      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;

      Decoder->OutputReservoirValidBytes = DecodedBytes;
    } else {
      // multistream requires interlacing the stereo/mono streams
      int16 *DeinterlaceBuffer = Decoder->DeinterlaceBuffer();
      uint8 *CurrentOutputReservoir = Decoder->OutputReservoir();

      uint32 LocalNumChannels = NumChannels;
      for (uint32 i = 0; i < Decoder->StreamCount; i++) {
        uint32 StreamChannels = Decoder->StreamChannels()[i];

        uint32 DecodedBytes = BinkInterface->DecodeFn(
            Decoder->Decoders()[i], (uint8 *)DeinterlaceBuffer,
            GetMaxFrameSizeSamples() * sizeof(int16) * 2, &BlockStart,
            BlockEnd);

        if (DecodedBytes == 0) {
          // This means that our block check above succeeded and we still
          // failed - corrupted data!
          return 0;
        }

        // deinterleave in to the output reservoir.
        if (StreamChannels == 1) {
          int16 *Read = DeinterlaceBuffer;
          int16 *Write = (int16 *)CurrentOutputReservoir;
          uint32 Frames = DecodedBytes / sizeof(int16);
          for (uint32 FrameIndex = 0; FrameIndex < Frames; FrameIndex++) {
            Write[LocalNumChannels * FrameIndex] = Read[FrameIndex];
          }
        } else {
          // stereo int16 pairs
          int32 *Read = (int32 *)DeinterlaceBuffer;
          int16 *Write = (int16 *)CurrentOutputReservoir;
          uint32 Frames = DecodedBytes / sizeof(int32);
          for (uint32 FrameIndex = 0; FrameIndex < Frames; FrameIndex++) {
            *(int32 *)(Write + LocalNumChannels * FrameIndex) =
                Read[FrameIndex];
          }
        }

        CurrentOutputReservoir += sizeof(int16) * StreamChannels;

        Decoder->OutputReservoirValidBytes += DecodedBytes;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);
      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;
    } // end if multi stream

    // Check if we are trimming the tail due to EOF
    if (TrimToSampleCount != ~0U) {
      // Ignore the tail samples by just dropping our reservoir size
      Decoder->OutputReservoirValidBytes =
          TrimToSampleCount * sizeof(int16) * NumChannels;
    }

    // Check if we need to eat some frames due to a sample-accurate seek.
    if (Decoder->ConsumeFrameCount) {
      const uint32 BytesPerFrame = sizeof(int16) * NumChannels;
      uint32 ValidFrames = Decoder->OutputReservoirValidBytes / BytesPerFrame;
      if (Decoder->ConsumeFrameCount < ValidFrames) {
        memmove(Decoder->OutputReservoir(),
                Decoder->OutputReservoir() +
                    Decoder->ConsumeFrameCount * BytesPerFrame,
                (ValidFrames - Decoder->ConsumeFrameCount) * BytesPerFrame);
        Decoder->OutputReservoirValidBytes -=
            Decoder->ConsumeFrameCount * BytesPerFrame;
      } else {
        Decoder->OutputReservoirValidBytes = 0;
      }
      Decoder->ConsumeFrameCount = 0;
    }
    // Fall through to the next loop to copy the decoded pcm data out of the
    // reservoir.
  } // while need output pcm data

  // We get here if we filled the output buffer or not.
  // FDecodeResult Result;
  // Result.NumPcmBytesProduced = OutputPCMDataSize - RemnOutputPCMDataSize;
  // Result.NumAudioFramesProduced =
  //     Result.NumPcmBytesProduced / (sizeof(int16) * NumChannels);
  // Result.NumCompressedBytesConsumed =
  //     CompressedDataSize - RemnCompressedDataSize;
  std::cout << "NumPcmBytesProduced: "
            << OutputPCMDataSize - RemnOutputPCMDataSize << std::endl;
  // std::cout << "NumAudioFramesProduced: " << Result.NumAudioFramesProduced
  // << std::endl; std::cout << "NumCompressedBytesConsumed: " <<
  // Result.NumCompressedBytesConsumed << std::endl;

  return OutputPCMDataSize - RemnOutputPCMDataSize;
}

std::string description = 
R"(binkadec: experimental Unreal Engine 4/5 "UEBA/.binka" audio decoder

Tested with:
  - THE FINALS (UE5.0))";
int main(int argc, char **argv) {
  // binkadec -i input.bink -o output.s16 -f raw
  argparse::ArgumentParser program("binkadec");
  program.add_description(description);
  program.add_argument("-i", "--input").required().help("input file");
  program.add_argument("-o", "--output").required().help("output file");
  program.add_argument("-v", "--verbose")
      .default_value(false)
      .implicit_value(true)
      .help("verbose output");
  //format: raw/wav
  program.add_argument("-f", "--format")
      .default_value("raw")
      .help("output format(raw)");

  try {
    program.parse_args(argc, argv);
  } catch (const std::runtime_error &err) {
    std::cout << err.what() << std::endl;
    std::cout << program;
    exit(0);
  }
  std::string input = program.get<std::string>("--input");
  std::filesystem::path input_path(input);
  std::string output = program.get<std::string>("--output");
  std::filesystem::path output_path(output);
  bool verbose = program.get<bool>("--verbose");
  if (verbose) {
    std::cout << "Input: " << input_path << std::endl;
    std::cout << "Output: " << output_path << std::endl;
  }
  std::ifstream input_file(input_path, std::ios::binary);
  if (!input_file.is_open()) {
    std::cout << "Failed to open input file" << std::endl;
    exit(0);
  }
  std::shared_ptr<uint8_t> input_data;
  uint32_t input_data_size;
  {
    input_file.seekg(0, std::ios::end);
    input_data_size = input_file.tellg();
    input_file.seekg(0, std::ios::beg);
    input_data = std::shared_ptr<uint8_t>(new uint8_t[input_data_size],
                                          std::default_delete<uint8_t[]>());
    input_file.read((char *)input_data.get(), input_data_size);
  }

  struct audioinfo info;
  if (!ParseHeader(input_data.get(), input_data_size, &info)) {
    std::cout << "Failed to parse header" << std::endl;
    exit(0);
  }
  if (verbose) {
    std::cout << to_string(info) << std::endl;
  }else{
    std::cout << "SampleRate: " << info.SampleRate << std::endl;
    std::cout << "NumChannels: " << info.NumChannels << std::endl;
    std::cout << "Duration: " << info.Duration << std::endl;
  }

  uint8_t *RawMemory = nullptr;
  BinkAudioDecoder *Decoder = nullptr;
  uint32_t SrcBufferOffset = 0;
  if (!CreateDecoder(input_data.get(), input_data_size, info.NumChannels,
                     info.SampleRate, RawMemory, Decoder, SrcBufferOffset)) {
    std::cout << "Failed to create decoder" << std::endl;
    exit(0);
  }

  if (verbose) {
    std::cout << "RawMemory: " << (void *)RawMemory << std::endl;
    std::cout << "Decoder: " << (void *)Decoder << std::endl;
    std::cout << "SrcBufferOffset: " << SrcBufferOffset << std::endl;
    std::cout << "Decoder->StreamCount: " << Decoder->StreamCount << std::endl;
    std::cout << "Decoder->OutputReservoirValidBytes: "
              << Decoder->OutputReservoirValidBytes << std::endl;
    std::cout << "Decoder->OutputReservoirTotalBytes: "
              << Decoder->OutputReservoirTotalBytes << std::endl;
    std::cout << "Decoder->ToDeinterlaceBufferOffset32: "
              << Decoder->ToDeinterlaceBufferOffset32 << std::endl;
    std::cout << "Decoder->ToSeekTableOffset32: "
              << Decoder->ToSeekTableOffset32 << std::endl;
    std::cout << "Decoder->ToOutputReservoirOffset32: "
              << Decoder->ToOutputReservoirOffset32 << std::endl;
    std::cout << "Decoder->SeekTableCount: " << Decoder->SeekTableCount
              << std::endl;
    std::cout << "Decoder->FramesPerSeekTableEntry: "
              << Decoder->FramesPerSeekTableEntry << std::endl;
    std::cout << "Decoder->ConsumeFrameCount: " << Decoder->ConsumeFrameCount
              << std::endl;
  }

  //malloc for compressed data — new buffer
  std::unique_ptr<uint8_t> compressedData =
      std::unique_ptr<uint8_t>(new uint8_t[input_data_size - SrcBufferOffset]);
  //Copy compressed data, but skip anything from "SEEK" to 0x9999.
  //Does not know why it is needed because Decoder->SeekTableCount is 0.
  bool in_skip = false;
  size_t dst_offset = 0;

  for (size_t i = SrcBufferOffset; i < input_data_size; i++) {
    if (!in_skip && i + 4 < input_data_size &&
        std::memcmp(&input_data.get()[i], "SEEK", 4) == 0) {
      in_skip = true;
      i += 3; // Skip "SEEK"
    } else if (in_skip && i + 2 < input_data_size && input_data.get()[i] == 0x99 &&
               input_data.get()[i + 1] == 0x99) {
      in_skip = false;
      compressedData.get()[dst_offset++] = 0x99;
      compressedData.get()[dst_offset++] = 0x99;
      i += 1; // Skip 0x9999
    } else if (!in_skip) {
      compressedData.get()[dst_offset++] = input_data.get()[i];
    }
  }

  if (verbose) {
    std::cout << "compressedDataSize: " << dst_offset << std::endl;
  }


  uint32_t NumChannels = info.NumChannels;
  uint32_t SampleRate = info.SampleRate;
  uint16_t MaxCompSpaceNeeded = info.SampleDataSize;
  uint8_t *OutPCMData = (uint8_t *)malloc(info.SampleDataSize);
  int32_t OutputPCMDataSize = info.SampleDataSize;
  int32_t DecodedBytes =
      Decode(Decoder, compressedData.get(), dst_offset, NumChannels, SampleRate,
             MaxCompSpaceNeeded, OutPCMData, OutputPCMDataSize);
  if (DecodedBytes == 0) {
    std::cout << "Failed to decode" << std::endl;
    exit(0);
  }
  std::ofstream output_file(output_path, std::ios::binary);
  if (!output_file.is_open()) {
    std::cout << "Failed to open output file" << std::endl;
    exit(0);
  }
  output_file.write((char *)OutPCMData, DecodedBytes);
  output_file.close();
  input_file.close();
  std::cout << "Done. Play the result with:" << std::endl;
  std::cout << "ffplay -f s16le -ar " << SampleRate << " -ac "
            << NumChannels << " -i " << output_path << std::endl;
}

 

 

Поделиться сообщением


Ссылка на сообщение
В 28.01.2025 в 18:08, xoixa сказал:

Как wav не получится. Нужно конвертировать обратно в .binka контейнер от unreal

Насколько я знаю, это можно сделать только в самом Unreal Engine из egs

Есть исходники декодера, на основе которых, можно(наверное), написать энкодер

  main.cpp (Показать содержимое)


#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iostream>

#include <cassert>

#include <fstream>

#include <argparse/argparse.hpp>
#include <memory>
#include <string>
#include <vector>

#include "binka_ue_decode.h"
#include "binka_ue_file_header.h"

using int16 = int16_t;
using int32 = int32_t;

constexpr int MAX_BINK_AUDIO_CHANNELS = 16;
inline uint32_t Align32(size_t size) { return (uint32_t)((size + 31) & ~31); }

inline void *PTR_ADD(void *ptr, uint32_t off) {
  return (void *)(((uint8_t *)(ptr)) + (off));
}

struct BinkAudioDecoder {
  // # of low level bink audio streams (max 2 chans each)
  uint8 StreamCount;
  // # of current bytes in the res
  uint32 OutputReservoirValidBytes;
  // # max size of the res.
  uint32 OutputReservoirTotalBytes;
  // offsets to the various pieces of the decoder
  uint16 ToDeinterlaceBufferOffset32;
  uint16 ToSeekTableOffset32;
  uint16 ToOutputReservoirOffset32;
  // # of entries in the seek table - the entries are byte sizes, so
  // when decoding the seek table we convert to file offsets. This means
  // we actually have +1 entries in the decoded seek table due to the standard
  // "span -> edge" count thing.
  uint16 SeekTableCount;
  // # of low level bink blocks one seek table entry spans
  uint16 FramesPerSeekTableEntry;
  // # of frames we need to eat before outputting data due to a sample
  // accurate seek.
  uint16 ConsumeFrameCount;

  uint8 *StreamChannels() { return (uint8 *)(this + 1); }
  void **Decoders() {
    return (void **)PTR_ADD(
        this, Align32(sizeof(uint8) * StreamCount + sizeof(BinkAudioDecoder)));
  }
  uint8 *OutputReservoir() {
    return (uint8 *)(PTR_ADD(this, ToOutputReservoirOffset32 * 32U));
  }
  int16 *DeinterlaceBuffer() {
    return (int16 *)(PTR_ADD(this, ToDeinterlaceBufferOffset32 * 32U));
  }
  uint32 *
  SeekTable() // [SeekTableCount + 1] if SeekTableCount != 0, otherwise invalid.
  {
    return (uint32 *)(PTR_ADD(this, ToSeekTableOffset32 * 32U));
  }
};

struct audioinfo {
  int SampleRate;
  int TrueSampleCount;
  int SampleDataSize;
  int NumChannels;
  float Duration;
  int AudioDataOffset;

public:
  friend std::string to_string(const audioinfo &info) {
    std::string str = "";
    str += "SampleRate: " + std::to_string(info.SampleRate) + "\n";
    str += "TrueSampleCount: " + std::to_string(info.TrueSampleCount) + "\n";
    str += "SampleDataSize: " + std::to_string(info.SampleDataSize) + "\n";
    str += "NumChannels: " + std::to_string(info.NumChannels) + "\n";
    str += "Duration: " + std::to_string(info.Duration) + "\n";
    str += "AudioDataOffset: " + std::to_string(info.AudioDataOffset) + "\n";
    return str;
  }
};

bool ParseHeader(const uint8 *InSrcBufferData, uint32 InSrcBufferDataSize,
                 struct audioinfo *info) {
  auto SrcBufferData = InSrcBufferData;
  auto SrcBufferDataSize = InSrcBufferDataSize;
  auto SrcBufferOffset = 0;
  auto CurrentSampleCount = 0;

  assert(InSrcBufferDataSize >= sizeof(BinkAudioFileHeader));
  if (InSrcBufferDataSize < sizeof(BinkAudioFileHeader)) {
    return false;
  }

  BinkAudioFileHeader *Header = (BinkAudioFileHeader *)InSrcBufferData;
  if (Header->tag != 'UEBA') {
    return false;
  }
  if (Header->version != 1) {
    return false;
  }

  auto SampleRate = Header->rate;
  auto TrueSampleCount = Header->sample_count;
  auto NumChannels = Header->channels;
  auto MaxCompSpaceNeeded = Header->max_comp_space_needed;

  uint32 SeekTableSize = Header->seek_table_entry_count * sizeof(uint16);
  if (sizeof(BinkAudioFileHeader) + SeekTableSize > InSrcBufferDataSize) {
    return false;
  }

  // Store the offset to where the audio data begins
  auto AudioDataOffset = sizeof(BinkAudioFileHeader) + SeekTableSize;

  // Write out the the header info
  if (info) {
    info->SampleRate = Header->rate;
    info->NumChannels = Header->channels;
    info->SampleDataSize =
        Header->sample_count * info->NumChannels * sizeof(int16);
    info->Duration = (float)Header->sample_count / info->SampleRate;
    info->AudioDataOffset = AudioDataOffset;
    info->TrueSampleCount = TrueSampleCount;
  }

  return true;
}

bool CreateDecoder(const uint8_t *SrcBufferData, uint32_t SrcBufferDataSize,
                   uint32_t NumChannels, uint32_t SampleRate,
                   uint8_t *&RawMemory, BinkAudioDecoder *&Decoder,
                   uint32_t &SrcBufferOffset) {

  auto GetMaxFrameSizeSamples = [SampleRate]() -> uint32_t {
    if (SampleRate >= 44100) {
      return 1920;
    } else if (SampleRate >= 22050) {
      return 960;
    } else {
      return 480;
    }
  };
  UEBinkAudioDecodeInterface *BinkInterface = nullptr;

  BinkInterface = UnrealBinkAudioDecodeInterface();

  if (BinkInterface == nullptr) {
    return false; // only happens if we dont have libs.
  }

  BinkAudioFileHeader *Header = (BinkAudioFileHeader *)SrcBufferData;

  // Bink is max stereo per stream
  uint32 StreamCount = (NumChannels + 1) >> 1;

  // Figure memory for buffers:

  // Deinterlace - buffer space for interleaving multi stream binks in to a
  // standard interleaved format.
  uint32 DeinterlaceMemory = 0;
  if (StreamCount > 1) {
    DeinterlaceMemory = GetMaxFrameSizeSamples() * sizeof(int16) * 2;
  }

  // Space to store a decoded block.
  uint32 OutputReservoirMemory =
      NumChannels * GetMaxFrameSizeSamples() * sizeof(int16);

  // Space for the decoder state
  uint32 DecoderMemoryTotal = 0;
  uint32 DecoderMemoryPerStream[MAX_BINK_AUDIO_CHANNELS / 2];
  {
    uint32 RemnChannels = NumChannels;
    for (uint32 StreamIndex = 0; StreamIndex < StreamCount; StreamIndex++) {
      uint32 StreamChannels = RemnChannels;
      if (StreamChannels > 2) {
        StreamChannels = 2;
      }
      RemnChannels -= StreamChannels;
      DecoderMemoryPerStream[StreamIndex] =
          BinkInterface->MemoryFn(SampleRate, StreamChannels);
      DecoderMemoryTotal += DecoderMemoryPerStream[StreamIndex];
    }
  }

  // Space for the decoder pointers
  uint32 PtrMemory = Align32(sizeof(void *) * StreamCount);

  // Space for ourselves + the channel count for each stream.
  uint32 StructMemory =
      Align32(sizeof(BinkAudioDecoder) + sizeof(uint8) * StreamCount);

  // Space for decoded seek table
  uint32 SeekTableMemory = 0;
  if (Header->seek_table_entry_count) {
    SeekTableMemory =
        Align32(sizeof(uint32) * (Header->seek_table_entry_count + 1));
  }

  uint32 TotalMemory = DecoderMemoryTotal + PtrMemory + StructMemory +
                       OutputReservoirMemory + DeinterlaceMemory +
                       SeekTableMemory;

  //
  // Allocate and save offsets
  //
  RawMemory = (uint8 *)malloc(TotalMemory);
  memset(RawMemory, 0, TotalMemory);

  Decoder = (BinkAudioDecoder *)RawMemory;

  Decoder->StreamCount = StreamCount;
  Decoder->OutputReservoirTotalBytes = OutputReservoirMemory;
  Decoder->SeekTableCount = Header->seek_table_entry_count;
  Decoder->FramesPerSeekTableEntry = Header->blocks_per_seek_table_entry;

  // See layout discussion in class declaration
  void **Decoders = Decoder->Decoders();
  uint8 *CurrentMemory = (uint8 *)PTR_ADD(Decoders, PtrMemory);
  uint8 *Channels = Decoder->StreamChannels();

  // Init decoders
  {
    uint8 RemnChannels = NumChannels;
    for (uint32 StreamIndex = 0; StreamIndex < StreamCount; StreamIndex++) {
      uint32 StreamChannels = RemnChannels;
      if (StreamChannels > 2) {
        StreamChannels = 2;
      }
      RemnChannels -= StreamChannels;

      Channels[StreamIndex] = StreamChannels;
      Decoders[StreamIndex] = (void **)CurrentMemory;
      CurrentMemory += DecoderMemoryPerStream[StreamIndex];
      BinkInterface->OpenFn(Decoders[StreamIndex], SampleRate, StreamChannels,
                            true, true);
    }
  }

  Decoder->ToOutputReservoirOffset32 =
      (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += OutputReservoirMemory;

  Decoder->ToDeinterlaceBufferOffset32 =
      (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += DeinterlaceMemory;

  Decoder->ToSeekTableOffset32 = (uint16)((CurrentMemory - RawMemory) / 32);
  CurrentMemory += SeekTableMemory;

  // Decode the seek table
  if (Decoder->SeekTableCount) {
    uint32 *SeekTable = Decoder->SeekTable();
    uint16 *EncodedSeekTable =
        (uint16 *)(SrcBufferData + sizeof(BinkAudioFileHeader));
    uint32 CurrentSeekOffset = 0;

    // the seek table has deltas from last, and we want absolutes
    for (uint32 i = 0; i < Decoder->SeekTableCount; i++) {
      SeekTable[i] = CurrentSeekOffset;
      CurrentSeekOffset += EncodedSeekTable[i];
    }
    SeekTable[Decoder->SeekTableCount] = CurrentSeekOffset;
  }

  SrcBufferOffset = sizeof(BinkAudioFileHeader) +
                    Header->seek_table_entry_count * sizeof(uint16);
  return true;
}

int Decode(BinkAudioDecoder *&Decoder, const uint8 *CompressedData,
           const int32 CompressedDataSize, uint32_t NumChannels,
           uint32_t SampleRate, const uint16_t MaxCompSpaceNeeded,
           uint8 *OutPCMData, const int32 OutputPCMDataSize) {
  auto GetMaxFrameSizeSamples = [SampleRate]() -> uint32_t {
    if (SampleRate >= 44100) {
      return 1920;
    } else if (SampleRate >= 22050) {
      return 960;
    } else {
      return 480;
    }
  };
  UEBinkAudioDecodeInterface *BinkInterface = 0;

  BinkInterface = UnrealBinkAudioDecodeInterface();

  if (BinkInterface == 0) {
    return 0; // only happens if we dont have libs.
  }

  uint32 RemnOutputPCMDataSize = OutputPCMDataSize;
  uint32 RemnCompressedDataSize = CompressedDataSize;
  const uint8 *CompressedDataEnd = CompressedData + CompressedDataSize;

  //
  // In the event we need to copy to a stack buffer, we alloca() it here so
  // that it's not inside the loop (for static analysis). We don't touch the
  // memory until we need it so it's just a couple instructions for the
  // alloca().
  // (+8 for max block header size)
  uint8 *StackBlockBuffer =
      (uint8 *)alloca(MaxCompSpaceNeeded + BINK_UE_DECODER_END_INPUT_SPACE + 8);

  const uint32 DecodeSize =
      GetMaxFrameSizeSamples() * sizeof(int16) * NumChannels;
  while (RemnOutputPCMDataSize) {
    //
    // Drain the output reservoir before attempting a decode.
    //
    if (Decoder->OutputReservoirValidBytes) {
      uint32 CopyBytes = Decoder->OutputReservoirValidBytes;
      if (CopyBytes > RemnOutputPCMDataSize) {
        CopyBytes = RemnOutputPCMDataSize;
      }

      memcpy(OutPCMData, Decoder->OutputReservoir(), CopyBytes);

      // We use memmove here because we expect it to be very rare that we
      // don't consume the entire output reservoir in a call, so it's not
      // worth managing a cursor. Just move down the remnants, which we expect
      // to be zero.
      if (Decoder->OutputReservoirValidBytes != CopyBytes) {
        std::memmove(Decoder->OutputReservoir(),
                     Decoder->OutputReservoir() + CopyBytes,
                     Decoder->OutputReservoirValidBytes - CopyBytes);
      }
      Decoder->OutputReservoirValidBytes -= CopyBytes;

      RemnOutputPCMDataSize -= CopyBytes;
      OutPCMData += CopyBytes;

      if (RemnOutputPCMDataSize == 0) {
        // we filled entirely from the output reservoir
        break;
      }
    }

    if (RemnCompressedDataSize == 0) {
      // This is the normal termination condition
      break;
    }

    

    if (BinkAudioValidateBlock(MaxCompSpaceNeeded, CompressedData,
                               RemnCompressedDataSize) !=
        BINK_AUDIO_BLOCK_VALID) {
      // The splitting system should ensure that we only ever get complete
      // blocks - so this is bizarre.
      // UE_LOG(LogBinkAudioDecoder, Warning,
      //        TEXT("Got weird buffer, validate returned %d"),
      //        BinkAudioValidateBlock(MaxCompSpaceNeeded, CompressedData,
      //                               RemnCompressedDataSize));
      
      std::cout << "Got weird buffer, validate returned "
                << std::to_string(BinkAudioValidateBlock(
                       MaxCompSpaceNeeded, CompressedData,
                       RemnCompressedDataSize))
                << std::endl;
      break;
    }

    uint8 const *BlockStart = 0;
    uint8 const *BlockEnd = 0;
    uint32 TrimToSampleCount = 0;
    BinkAudioCrackBlock(CompressedData, &BlockStart, &BlockEnd,
                        &TrimToSampleCount);
    uint8 const *BlockBase = CompressedData;

    //
    // We need to make sure there's room available for Bink to read past the
    // end of the buffer (for vector decoding). If there's not, we need to
    // copy to a temp buffer.
    //
    bool HasRoomForDecode =
        (CompressedDataEnd - BlockEnd) > BINK_UE_DECODER_END_INPUT_SPACE;
    if (HasRoomForDecode == false) {
      // This looks weird, but in order for the advancement logic to work,
      // we need to replicate the entire block including the header.
      size_t BlockOffset = BlockStart - BlockBase;
      size_t BlockSize = BlockEnd - BlockBase;
      if (BlockSize > MaxCompSpaceNeeded + 8) // +8 for max block header size
      {
        // UE_LOG(LogBinkAudioDecoder, Error,
        //        TEXT("BAD! Validated block exceeds header max block size (%d
        //        "
        //             "vs %d)"),
        //        BlockSize, MaxCompSpaceNeeded);
        std::cout << "BAD! Validated block exceeds header max block size ("
                  << BlockSize << " vs " << MaxCompSpaceNeeded << ")"
                  << std::endl;
        // bErrorStateLatch = true;
        break;
      }

      memcpy(StackBlockBuffer, BlockBase, BlockSize);

      // this is technically not needed, but just so that any analysis shows
      // that we've initialized all the memory we touch.
      memset(StackBlockBuffer + BlockSize, 0, BINK_UE_DECODER_END_INPUT_SPACE);

      BlockBase = StackBlockBuffer;
      BlockStart = StackBlockBuffer + BlockOffset;
      BlockEnd = StackBlockBuffer + BlockSize;
    }

    //
    // If we're a simple single stream and we have enough output space,
    // just decode directly in to our destination to avoid some
    // copies.
    //
    // We also have to have a "simple" decode - i.e. aligned and no trimming.
    //
    if (Decoder->StreamCount == 1 && DecodeSize <= RemnOutputPCMDataSize &&
        TrimToSampleCount == ~0U && (((size_t)OutPCMData) & 0xf) == 0 &&
        Decoder->ConsumeFrameCount == 0) {
      uint32 DecodedBytes =
          BinkInterface->DecodeFn(Decoder->Decoders()[0], OutPCMData,
                                  RemnOutputPCMDataSize, &BlockStart, BlockEnd);

      if (DecodedBytes == 0) {

        // This means that our block check above succeeded and we still failed
        // - corrupted data!
        return 0;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);

      OutPCMData += DecodedBytes;
      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;
      RemnOutputPCMDataSize -= DecodedBytes;
      continue;
    }

    // Otherwise we route through the output reservoir.
    if (Decoder->StreamCount == 1) {
      uint32 DecodedBytes = BinkInterface->DecodeFn(
          Decoder->Decoders()[0], Decoder->OutputReservoir(),
          Decoder->OutputReservoirTotalBytes, &BlockStart, BlockEnd);
      if (DecodedBytes == 0) {

        // This means that our block check above succeeded and we still failed
        // - corrupted data!
        return 0;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);

      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;

      Decoder->OutputReservoirValidBytes = DecodedBytes;
    } else {
      // multistream requires interlacing the stereo/mono streams
      int16 *DeinterlaceBuffer = Decoder->DeinterlaceBuffer();
      uint8 *CurrentOutputReservoir = Decoder->OutputReservoir();

      uint32 LocalNumChannels = NumChannels;
      for (uint32 i = 0; i < Decoder->StreamCount; i++) {
        uint32 StreamChannels = Decoder->StreamChannels()[i];

        uint32 DecodedBytes = BinkInterface->DecodeFn(
            Decoder->Decoders()[i], (uint8 *)DeinterlaceBuffer,
            GetMaxFrameSizeSamples() * sizeof(int16) * 2, &BlockStart,
            BlockEnd);

        if (DecodedBytes == 0) {
          // This means that our block check above succeeded and we still
          // failed - corrupted data!
          return 0;
        }

        // deinterleave in to the output reservoir.
        if (StreamChannels == 1) {
          int16 *Read = DeinterlaceBuffer;
          int16 *Write = (int16 *)CurrentOutputReservoir;
          uint32 Frames = DecodedBytes / sizeof(int16);
          for (uint32 FrameIndex = 0; FrameIndex < Frames; FrameIndex++) {
            Write[LocalNumChannels * FrameIndex] = Read[FrameIndex];
          }
        } else {
          // stereo int16 pairs
          int32 *Read = (int32 *)DeinterlaceBuffer;
          int16 *Write = (int16 *)CurrentOutputReservoir;
          uint32 Frames = DecodedBytes / sizeof(int32);
          for (uint32 FrameIndex = 0; FrameIndex < Frames; FrameIndex++) {
            *(int32 *)(Write + LocalNumChannels * FrameIndex) =
                Read[FrameIndex];
          }
        }

        CurrentOutputReservoir += sizeof(int16) * StreamChannels;

        Decoder->OutputReservoirValidBytes += DecodedBytes;
      }

      uint32 InputConsumed = (uint32)(BlockStart - BlockBase);
      CompressedData += InputConsumed;
      RemnCompressedDataSize -= InputConsumed;
    } // end if multi stream

    // Check if we are trimming the tail due to EOF
    if (TrimToSampleCount != ~0U) {
      // Ignore the tail samples by just dropping our reservoir size
      Decoder->OutputReservoirValidBytes =
          TrimToSampleCount * sizeof(int16) * NumChannels;
    }

    // Check if we need to eat some frames due to a sample-accurate seek.
    if (Decoder->ConsumeFrameCount) {
      const uint32 BytesPerFrame = sizeof(int16) * NumChannels;
      uint32 ValidFrames = Decoder->OutputReservoirValidBytes / BytesPerFrame;
      if (Decoder->ConsumeFrameCount < ValidFrames) {
        memmove(Decoder->OutputReservoir(),
                Decoder->OutputReservoir() +
                    Decoder->ConsumeFrameCount * BytesPerFrame,
                (ValidFrames - Decoder->ConsumeFrameCount) * BytesPerFrame);
        Decoder->OutputReservoirValidBytes -=
            Decoder->ConsumeFrameCount * BytesPerFrame;
      } else {
        Decoder->OutputReservoirValidBytes = 0;
      }
      Decoder->ConsumeFrameCount = 0;
    }
    // Fall through to the next loop to copy the decoded pcm data out of the
    // reservoir.
  } // while need output pcm data

  // We get here if we filled the output buffer or not.
  // FDecodeResult Result;
  // Result.NumPcmBytesProduced = OutputPCMDataSize - RemnOutputPCMDataSize;
  // Result.NumAudioFramesProduced =
  //     Result.NumPcmBytesProduced / (sizeof(int16) * NumChannels);
  // Result.NumCompressedBytesConsumed =
  //     CompressedDataSize - RemnCompressedDataSize;
  std::cout << "NumPcmBytesProduced: "
            << OutputPCMDataSize - RemnOutputPCMDataSize << std::endl;
  // std::cout << "NumAudioFramesProduced: " << Result.NumAudioFramesProduced
  // << std::endl; std::cout << "NumCompressedBytesConsumed: " <<
  // Result.NumCompressedBytesConsumed << std::endl;

  return OutputPCMDataSize - RemnOutputPCMDataSize;
}

std::string description = 
R"(binkadec: experimental Unreal Engine 4/5 "UEBA/.binka" audio decoder

Tested with:
  - THE FINALS (UE5.0))";
int main(int argc, char **argv) {
  // binkadec -i input.bink -o output.s16 -f raw
  argparse::ArgumentParser program("binkadec");
  program.add_description(description);
  program.add_argument("-i", "--input").required().help("input file");
  program.add_argument("-o", "--output").required().help("output file");
  program.add_argument("-v", "--verbose")
      .default_value(false)
      .implicit_value(true)
      .help("verbose output");
  //format: raw/wav
  program.add_argument("-f", "--format")
      .default_value("raw")
      .help("output format(raw)");

  try {
    program.parse_args(argc, argv);
  } catch (const std::runtime_error &err) {
    std::cout << err.what() << std::endl;
    std::cout << program;
    exit(0);
  }
  std::string input = program.get<std::string>("--input");
  std::filesystem::path input_path(input);
  std::string output = program.get<std::string>("--output");
  std::filesystem::path output_path(output);
  bool verbose = program.get<bool>("--verbose");
  if (verbose) {
    std::cout << "Input: " << input_path << std::endl;
    std::cout << "Output: " << output_path << std::endl;
  }
  std::ifstream input_file(input_path, std::ios::binary);
  if (!input_file.is_open()) {
    std::cout << "Failed to open input file" << std::endl;
    exit(0);
  }
  std::shared_ptr<uint8_t> input_data;
  uint32_t input_data_size;
  {
    input_file.seekg(0, std::ios::end);
    input_data_size = input_file.tellg();
    input_file.seekg(0, std::ios::beg);
    input_data = std::shared_ptr<uint8_t>(new uint8_t[input_data_size],
                                          std::default_delete<uint8_t[]>());
    input_file.read((char *)input_data.get(), input_data_size);
  }

  struct audioinfo info;
  if (!ParseHeader(input_data.get(), input_data_size, &info)) {
    std::cout << "Failed to parse header" << std::endl;
    exit(0);
  }
  if (verbose) {
    std::cout << to_string(info) << std::endl;
  }else{
    std::cout << "SampleRate: " << info.SampleRate << std::endl;
    std::cout << "NumChannels: " << info.NumChannels << std::endl;
    std::cout << "Duration: " << info.Duration << std::endl;
  }

  uint8_t *RawMemory = nullptr;
  BinkAudioDecoder *Decoder = nullptr;
  uint32_t SrcBufferOffset = 0;
  if (!CreateDecoder(input_data.get(), input_data_size, info.NumChannels,
                     info.SampleRate, RawMemory, Decoder, SrcBufferOffset)) {
    std::cout << "Failed to create decoder" << std::endl;
    exit(0);
  }

  if (verbose) {
    std::cout << "RawMemory: " << (void *)RawMemory << std::endl;
    std::cout << "Decoder: " << (void *)Decoder << std::endl;
    std::cout << "SrcBufferOffset: " << SrcBufferOffset << std::endl;
    std::cout << "Decoder->StreamCount: " << Decoder->StreamCount << std::endl;
    std::cout << "Decoder->OutputReservoirValidBytes: "
              << Decoder->OutputReservoirValidBytes << std::endl;
    std::cout << "Decoder->OutputReservoirTotalBytes: "
              << Decoder->OutputReservoirTotalBytes << std::endl;
    std::cout << "Decoder->ToDeinterlaceBufferOffset32: "
              << Decoder->ToDeinterlaceBufferOffset32 << std::endl;
    std::cout << "Decoder->ToSeekTableOffset32: "
              << Decoder->ToSeekTableOffset32 << std::endl;
    std::cout << "Decoder->ToOutputReservoirOffset32: "
              << Decoder->ToOutputReservoirOffset32 << std::endl;
    std::cout << "Decoder->SeekTableCount: " << Decoder->SeekTableCount
              << std::endl;
    std::cout << "Decoder->FramesPerSeekTableEntry: "
              << Decoder->FramesPerSeekTableEntry << std::endl;
    std::cout << "Decoder->ConsumeFrameCount: " << Decoder->ConsumeFrameCount
              << std::endl;
  }

  //malloc for compressed data — new buffer
  std::unique_ptr<uint8_t> compressedData =
      std::unique_ptr<uint8_t>(new uint8_t[input_data_size - SrcBufferOffset]);
  //Copy compressed data, but skip anything from "SEEK" to 0x9999.
  //Does not know why it is needed because Decoder->SeekTableCount is 0.
  bool in_skip = false;
  size_t dst_offset = 0;

  for (size_t i = SrcBufferOffset; i < input_data_size; i++) {
    if (!in_skip && i + 4 < input_data_size &&
        std::memcmp(&input_data.get()[i], "SEEK", 4) == 0) {
      in_skip = true;
      i += 3; // Skip "SEEK"
    } else if (in_skip && i + 2 < input_data_size && input_data.get()[i] == 0x99 &&
               input_data.get()[i + 1] == 0x99) {
      in_skip = false;
      compressedData.get()[dst_offset++] = 0x99;
      compressedData.get()[dst_offset++] = 0x99;
      i += 1; // Skip 0x9999
    } else if (!in_skip) {
      compressedData.get()[dst_offset++] = input_data.get()[i];
    }
  }

  if (verbose) {
    std::cout << "compressedDataSize: " << dst_offset << std::endl;
  }


  uint32_t NumChannels = info.NumChannels;
  uint32_t SampleRate = info.SampleRate;
  uint16_t MaxCompSpaceNeeded = info.SampleDataSize;
  uint8_t *OutPCMData = (uint8_t *)malloc(info.SampleDataSize);
  int32_t OutputPCMDataSize = info.SampleDataSize;
  int32_t DecodedBytes =
      Decode(Decoder, compressedData.get(), dst_offset, NumChannels, SampleRate,
             MaxCompSpaceNeeded, OutPCMData, OutputPCMDataSize);
  if (DecodedBytes == 0) {
    std::cout << "Failed to decode" << std::endl;
    exit(0);
  }
  std::ofstream output_file(output_path, std::ios::binary);
  if (!output_file.is_open()) {
    std::cout << "Failed to open output file" << std::endl;
    exit(0);
  }
  output_file.write((char *)OutPCMData, DecodedBytes);
  output_file.close();
  input_file.close();
  std::cout << "Done. Play the result with:" << std::endl;
  std::cout << "ffplay -f s16le -ar " << SampleRate << " -ac "
            << NumChannels << " -i " << output_path << std::endl;
}

 

 

Да, спасибо. Я что-то сделал. Но без Unreal Engine Audio Binka SDK не получается — для компиляции нужны:

#include "binka_ue_encode.h"
#include "binka_ue_file_header.h"
Изменено пользователем vananagornyi
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

ребята кто очень хочет озвучку
давайте навалимся на synthvoiceru
https://boosty.to/synthvoiceru
пишите под постами чтоб они организовали сбор

Поделиться сообщением


Ссылка на сообщение
В 07.11.2024 в 22:32, xoixa сказал:

Для анрила есть Fmodel чтобы доставать файлы из pak и utoc/ucas

Звуки часто в формате wwise (.wem контейнеры)

Для конвертации звука в wav есть, например, vgmstream

В играх начиная от unreal 4.27+ звук может быть в Bink Audio (.binka контейнер)

Для конвертации звука в wav из Bink Audio есть binkadec (vgmstream не понимает .binka контейнер от unreal)

.binka контейнер от unreal (ABEU — Unreal Engine Bink Audio) не путать со старым binka контейнером от Miles Sound System (1FCB)

 

Metro Awakening на unreal 5.2.1

ресурсы в IOStore файлах (папка Metro Awakening\Impact\Content\Paks\)

не шифрованные, сжатые Oodle

звук Unreal Engine Bink Audio в нескольких utoc/ucas:

по пути Impact\Content\Maps\ — сюжет

и Impact\Content\Assets\Audio\VO\ — стандартные фразы

субтитры в Impact\Content\L10N (папка ru — русские, uk — украинские) в uasset с постфиксом “_Sub”

английские субтитры в папках рядом со звуком

Fmodel умеет экспортировать звук в .binka: нужно два раза кликнуть на uasset звука и в папке экспорта будет соответствующий binka файл

Чтобы Fmodel экспортировал сразу в wav, нужно закинуть binkadec.exe в папку .data в папке экспорта (Output Directory в настройках)

@xoixa подскажи пожалуйста, а обратно как из wav в binka конвертить? Я при помощи binkadec в wav форматнул, перевёл на русский, теперь хочу обратно в binka, потом сложить в проект UE5, запечь и сделать uasset\uexp\ubulk. Я так делал в UE4 правда с wwise. Но как обратно в binka конвертить — вообще нигде инфы нет...

Поделиться сообщением


Ссылка на сообщение
16 часов назад, nekkk сказал:

Вот, попробуйте, я сделал это с помощью IA.

Here it is, give it a try, I made it with the help of an IA.
https://www.mediafire.com/file/ffq5wbu1ebx4grm/binkaenc.exe/file

SEEK блоки не создает если использовать —seek-table и от какой версии Unreal Engine взято SDK?
Вот пример с SEEK: VO_BK_1_1_Tina_01_Tina.binka
И за саму программу большое спасибо.

Изменено пользователем ponaromixxx

Поделиться сообщением


Ссылка на сообщение
1 hour ago, ponaromixxx said:

SEEK does not create blocks if you use —seek-table and from which version of Unreal Engine is the SDK taken?
Here is an example with SEEK: VO_BK_1_1_Tina_01_Tina.binka
And many thanks for the program itself.

Encoder действительно что-то создает, если вы используете —seek-table, но это определенно отличается от предоставленного вами примера.

Я постараюсь исправить это, но если вы тоже хотите помочь, я могу отправить вам исходный код в приватной форме.

Кстати, я использовал SDK 5.5.4.

 

Encoder does create something if you use —seek-table, but it is definitely different from the example you provided.

I will try to correct it, but if you want to help too I can send you the source code in private.

By the way, I have used the SDK 5.5.4.

Поделиться сообщением


Ссылка на сообщение
4 часа назад, nekkk сказал:

Encoder действительно что-то создает, если вы используете —seek-table, но это определенно отличается от предоставленного вами примера.

Я постараюсь исправить это, но если вы тоже хотите помочь, я могу отправить вам исходный код в приватной форме.

Кстати, я использовал SDK 5.5.4.

 

Encoder does create something if you use —seek-table, but it is definitely different from the example you provided.

I will try to correct it, but if you want to help too I can send you the source code in private.

By the way, I have used the SDK 5.5.4.

Я тоже начинал писать Encoder по SDK, но забросил из-за нехватки времени, так же я decoder переносил под последнюю версию, но там не сложно было. Я сам больше на C# пишу, С++ только изучаю, но могу попробовать помочь, если смогу.

Поделиться сообщением


Ссылка на сообщение

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас популярно

  • Продвигаемые темы

  • Последние сообщения

    • А в теме траблшутера будто бы не ты спустя кучу дней с завершения диалога решил поднять разговор заново. Предлагаешь игнорировать тебя, когда ты несёшь очередную чепуху? Поначалу ещё есть шансы, что диалог завершится за пару-тройку фраз, впрочем, тебя обычно всё-таки перекармливают на сколько-то там страниц. Так что не удивляйся, что когда диалог длится некоторое время, появляются люди, предлагающие тебя более не подкармливать ответами, когда вместо диалога ты уже уходишь в переливание из пустого в порожнее, забывая собственные же более ранние слова.
    • Чел, я с тобой вообще никогда диалоги не начинаю. Ты сам цитируешь мои посты, и сам предлагаешь не “кормить”. Гений, как всегда. Поэтому я с тобой стараюсь даже не заводить диалоги. С тобой уже всё понятно.
    • А давай мы возьмём и... не будем его больше подкармливать какое-то время, а то он как-то вес уже набрал явно, огрызается, когда в зеркало смотрит, видит там себя и думает, что это не он в отражении, а мы по другую сторону зеркала сидим.
    • Причем тут законодательство теперь? У нас законом запрещено пятерочке продавать французу, который, зашел в магазин, хлебушек? Ну да, если вдруг будет такой закон, который запретит продавать всем кроме своих граждан, то перестанет продавать. И если в США будет закон не продавать игры никому кроме граждан США, то стим тоже перестанет продавать всем игры, кроме граждан США. И что? Ну тогда не стим, не пятерочка уже не будут по всему миру работать. Пятерочка подчиняется законам РФ. А стим законам США. стим, просто учитывает пожелания других государств. А может и не учитывать. Он в них даже юридически не представлен. Стиму, просто позволяют работать, смотрят сквозь пальцы, как и многим другим сайтам. А если считают, их нежелательными, то блокируют доступ.  Если вдруг стим РФ посчитает нежелательным, то РФ или другое государство чьи интересы он “учитывает”, заблокирует своим гражданам доступ к нему, но не заблокирует его работу для других. А вот если США, скажет, заблокировать доступ к гражданам любой другой страны, то он заблокирует. Стим(Валве) — это американская компания. Пятерочка(кто там у них юр лицо я хз) — российская. Пока законы их стран, которым они “принадлежат”, не скажут им не продавать другим, то они продают всем. Французу позволили приехать в РФ, зайти в магазин и купить там хлебушек. Вам позволили подключиться к серверам стима,зайти в магазин и купить там игру. Поэтому что там кто что “учитывает”, не имеет вообще никакого значения в контексте вопроса. Как и сам юридический аспект.  Кормлю тут только я скучающих троллей. Хотя скорее не троллей, а лягушек в колодце.
    • Подобные новости раз в пару недель появляются. В заголовке написано так, как будто уже вернулись, а в самом тексте размышления на тему. Примерно так же, как возвращалась ИКЕЯ и ещё кто-то, а новость о том, что они просто в Роспатент подают заявки на регистрацию брендов.
    • Для официального перевода были использованы тексты локализации от Медиахауз.
    • Пошерстил на скорую руку новости. Про “просились” не нашел. Видел только рассуждения о том, может ли Макдональдс вернуться и чего это, по мнению “экспертов” и прочих “знающих”, им будет стоить.
    • Ну, собственно, чтобы купить игры яблоки в “пятёрочке” (стиме) на другой улице (стране) ты точно так же послушно идёшь на эту другую улицу и платишь свои “61 бакса” + цену за транспортировку себя (денег из своего банка) до этой самой другой улицы. Если бы ты жил на другой “улице”, то на “билет” на ту “улицу” не платил бы. Так же, как условному японцу Такеши не пришлось бы платить кучу денег за билет на самолёт (что-то под 20к р. если верить гуглу) ради сомнительного удовольствия купить у тебя яблоки по 5 рублей (где ты такие цены на яблоки видел — вопрос отдельный, даже твои аналогии оторваны от жизни) вместо того, чтобы купить их же у себя за ~100р в эквиваленте в их валюте. Ну не хочет тебе “бабка Зинка” из Канады продавать “яблоки” (недоступную в регионе игру), ну и плюнь ты на них, сдались они тебе. Но нет, тебе обязательно надо ехать на другую “улицу” Канаду и притворяться канадцем из Казахстана.
    • Ты втираешь мне какую-то дичь. (с) Пятерочка в вашем дворе работает по законам одного государства. Стим работает с учетом законодательства множества государств. Но, видимо, эта простая истина вам не доступна. И как он к таким выводам приходит? Сколько раз на подобные “гениальные” трактовки чужих слов (в т.ч. моих) просило его расписать логическую цепочку — ни разу не расписал. Может кто-то сможет хотя бы предположить, как он это делает? @PermResident, говоришь не кормить его? Но ты глянь, какими голодными глазами он на нас смотрит. Не хуже кота из Шрека.  Ну как такого не подкормить, а?
  • Изменения статусов

    • SHAMAH

      Куда вход на сайт убрали и ЗАЧЕМ? Хотел файл скачать, там только медленная загрузка и “зарегистрируйтесь”. Все. Пришлось вручную страницу входа прописывать.
      · 0 ответов
    • Nosferatu  »  behar

      Добрый вечер.
      Подскажите пожалуйста, у вас не осталось случайно исходников для фикса на широкоформатные мониторы для игры Vampire The Masquerade Redemption?
      Если да, то не могли бы вы ими поделиться, а если нет, то прошу прощенья что побеспокоил.
      Заранее спасибо.
      · 0 ответов
    • AlcoKolyic  »  makc_ar

      Здраствуйте! Извините, а можно попросить ссылку на место где можно взять перевод (патч или образ игры с переводом) El Shaddai: Ascension of the Metatron для ps3, пожалуйста? А то в теме к этой игре у меня не получилось найти работающие ссылки… Первая ведет в группу в которой удалены большинство постов, а пост с этой игрой ведет на сайт https://psnext.ru который сейчас не имеет отношения к видеоиграм. 
      · 0 ответов
    • oleg72  »  Boor

      https://www.skidrowcodex.net/fate-reawakened-goldberg/
      · 0 ответов
    • Ob1tel

      Таксист Стример Анимешник https://www.twitch.tv/harddcore_gamer
      https://www.youtube.com/@HarddcorGamer
      · 0 ответов
  • Лучшие авторы


Zone of Games © 2003–2025 | Реклама на сайте.

×