// // Created by WolverinDEV on 18/03/2020. // #include "./wav.h" #include "../../logger.h" #include using namespace tc::audio::file; struct WAVFileHeader { /* RIFF Chunk Descriptor */ uint8_t RIFF[4]; // RIFF Header Magic header uint32_t ChunkSize; // RIFF Chunk Size uint8_t WAVE[4]; // WAVE Header /* "fmt" sub-chunk */ uint8_t fmt[4]; // FMT header uint32_t Subchunk1Size; // Size of the fmt chunk uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio uint32_t SamplesPerSec; // Sampling Frequency in Hz uint32_t bytesPerSec; // bytes per second uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo uint16_t bitsPerSample; // Number of bits per sample }; static_assert(sizeof(WAVFileHeader) == 0x24); struct WAFFileChunk { uint8_t id[4]; uint32_t size; }; static_assert(sizeof(WAFFileChunk) == 0x08); WAVReader::WAVReader(std::string file) : file_path_{std::move(file)} {} WAVReader::~WAVReader() { this->close_file(); } FileOpenResult WAVReader::open_file(std::string& error) { //C:\Users\%D0%9E%D0%BB%D0%B5%D0%B3\AppData\Roaming\TeaClient\tmp\ui\release_1584625323\index.html this->is_.open(std::filesystem::u8path(this->file_path_), std::ifstream::in | std::ifstream::binary); if(!this->is_) { error = tr("failed to open file"); return FileOpenResult::OPEN_RESULT_ERROR; } WAVFileHeader header{}; if(!this->is_.read((char*) &header, sizeof(header))) { error = tr("failed to read wav header"); return FileOpenResult::OPEN_RESULT_ERROR; } if(memcmp(header.RIFF, "RIFF", 4) != 0) { error = tr("invalid RIFF header"); return FileOpenResult::OPEN_RESULT_ERROR; } if(memcmp(header.WAVE, "WAVE", 4) != 0) { error = tr("invalid WAVE header"); return FileOpenResult::OPEN_RESULT_ERROR; } if(memcmp(header.fmt, "fmt ", 4) != 0) { error = tr("invalid WAVE header"); return FileOpenResult::OPEN_RESULT_ERROR; } if(header.AudioFormat != 1) { error = tr("Only PCM has been supported. WAV file does not contains PCM data."); return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED; } if(header.bytesPerSec != (header.NumOfChan * header.SamplesPerSec * header.bitsPerSample) / 8) { error = tr("inconsistent WAV header"); return FileOpenResult::OPEN_RESULT_INVALID_FORMAT; } if(header.bitsPerSample != 8 && header.bitsPerSample != 16 && header.bitsPerSample != 24) { error = tr("unsupported bitrate"); return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED; } if(header.NumOfChan != 2 && header.NumOfChan != 1) { error = tr("unsupported channel count"); return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED; } WAFFileChunk chunk{}; while(true) { if(!this->is_.read((char*) &chunk, sizeof(chunk))) { error = tr("failed to read chunks until data chunk"); return FileOpenResult::OPEN_RESULT_ERROR; } if(memcmp(chunk.id, "data ", 4) == 0) break; this->is_.seekg(chunk.size, std::ifstream::cur); } this->current_sample_offset_ = 0; this->bytes_per_sample = header.bitsPerSample / 8; this->total_samples_ = chunk.size / this->bytes_per_sample / header.NumOfChan; this->sample_rate_ = header.SamplesPerSec; this->channels_ = header.NumOfChan; return FileOpenResult::OPEN_RESULT_SUCCESS; } void WAVReader::close_file() { this->total_samples_ = 0; this->bytes_per_sample = 0; this->sample_rate_ = 0; this->channels_ = 0; this->is_.close(); } float _8bit_float_convert(const uint8_t* buffer) { int16_t value = buffer[0] & 0xFFU; return (float) (value - 127) * (1.0f / 127.0f); } float _16bit_float_convert(const uint8_t* buffer) { int16_t value = *reinterpret_cast(buffer); return (float) value / 32767.f; } float _24bit_float_convert(const uint8_t* buffer) { #if 0 int32_t value = (*reinterpret_cast(buffer) & 0xFFFFFFU) << 8; return (float) (value - 1073741824) / 1073741824.f; //2147483648 / 2 #endif #if 1 int32_t value = ((uint32_t) buffer[2] << 16U) | ((uint32_t) buffer[1] << 8U) | ((uint32_t) buffer[0] << 0U); if (value & 0x800000) // if the 24th bit is set, this is a negative number in 24-bit world value = value | ~0xFFFFFF; // so make sure sign is extended to the 32 bit float auto result = (float) value / (float) 8388608.f; //8388608 return result; #endif } static std::array pcm_to_float_converters{ _8bit_float_convert, _16bit_float_convert, _24bit_float_convert }; ReadResult WAVReader::read(void *buffer, size_t* samples) { auto fbuffer = (float*) buffer; const auto max_sample_count = this->total_samples_ - this->current_sample_offset_; const auto max_samples = std::min(*samples, max_sample_count); if(max_samples == 0) { if(max_sample_count == 0) return ReadResult::READ_RESULT_EOF; return ReadResult::READ_RESULT_SUCCESS; } constexpr size_t sbuffer_size{1536}; /* must be dividable by 24, 165 and 8 bit! As well by two channels so 6, 4 and 2 byte to avoid to mess up one frame */ uint8_t sbuffer[sbuffer_size]; size_t samples_read{0}; auto fconverter = pcm_to_float_converters[(this->bytes_per_sample - 1) & 0x3U]; while(samples_read < max_samples) { const auto block_byte_length{std::min(sbuffer_size, (max_samples - samples_read) * this->bytes_per_sample * this->channels_)}; if(!this->is_.read((char*) sbuffer, block_byte_length)) return ReadResult::READ_RESULT_UNRECOVERABLE_ERROR; uint8_t* sbufferptr = sbuffer; uint8_t* sbuferendptr = sbuffer + block_byte_length; while(sbufferptr != sbuferendptr) { *fbuffer++ = fconverter(sbufferptr); sbufferptr += this->bytes_per_sample; } samples_read += block_byte_length / (this->bytes_per_sample * this->channels_); } *samples = samples_read; this->current_sample_offset_ += samples_read; return ReadResult::READ_RESULT_SUCCESS; }