/* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "common_audio/wav_file.h" #include #include #include #include #include #include #include "common_audio/include/audio_util.h" #include "rtc_base/checks.h" #include "rtc_base/system/arch.h" namespace webrtc { namespace { static_assert(std::is_trivially_destructible::value, ""); // Checks whether the format is supported or not. bool FormatSupported(WavFormat format) { // Only PCM and IEEE Float formats are supported. return format == WavFormat::kWavFormatPcm || format == WavFormat::kWavFormatIeeeFloat; } // Doesn't take ownership of the file handle and won't close it. class WavHeaderFileReader : public WavHeaderReader { public: explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {} WavHeaderFileReader(const WavHeaderFileReader&) = delete; WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete; size_t Read(void* buf, size_t num_bytes) override { size_t count = file_->Read(buf, num_bytes); pos_ += count; return count; } bool SeekForward(uint32_t num_bytes) override { bool success = file_->SeekRelative(num_bytes); if (success) { pos_ += num_bytes; } return success; } int64_t GetPosition() override { return pos_; } private: FileWrapper* file_; int64_t pos_ = 0; }; constexpr size_t kMaxChunksize = 4096; } // namespace WavReader::WavReader(const std::string& filename) : WavReader(FileWrapper::OpenReadOnly(filename)) {} WavReader::WavReader(FileWrapper file) : file_(std::move(file)) { RTC_CHECK(file_.is_open()) << "Invalid file. Could not create file handle for wav file."; WavHeaderFileReader readable(&file_); size_t bytes_per_sample; RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format_, &bytes_per_sample, &num_samples_in_file_, &data_start_pos_)); num_unread_samples_ = num_samples_in_file_; RTC_CHECK(FormatSupported(format_)) << "Non-implemented wav-format"; } void WavReader::Reset() { RTC_CHECK(file_.SeekTo(data_start_pos_)) << "Failed to set position in the file to WAV data start position"; num_unread_samples_ = num_samples_in_file_; } size_t WavReader::ReadSamples(const size_t num_samples, int16_t* const samples) { #ifndef WEBRTC_ARCH_LITTLE_ENDIAN #error "Need to convert samples to big-endian when reading from WAV file" #endif size_t num_samples_left_to_read = num_samples; size_t next_chunk_start = 0; while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) { const size_t chunk_size = std::min( std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_); size_t num_bytes_read; size_t num_samples_read; if (format_ == WavFormat::kWavFormatIeeeFloat) { std::array samples_to_convert; num_bytes_read = file_.Read(samples_to_convert.data(), chunk_size * sizeof(samples_to_convert[0])); num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]); for (size_t j = 0; j < num_samples_read; ++j) { samples[next_chunk_start + j] = FloatToS16(samples_to_convert[j]); } } else { RTC_CHECK_EQ(format_, WavFormat::kWavFormatPcm); num_bytes_read = file_.Read(&samples[next_chunk_start], chunk_size * sizeof(samples[0])); num_samples_read = num_bytes_read / sizeof(samples[0]); } RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0) << "Corrupt file: file ended in the middle of a sample."; RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof()) << "Corrupt file: payload size does not match header."; next_chunk_start += num_samples_read; num_unread_samples_ -= num_samples_read; num_samples_left_to_read -= num_samples_read; } return num_samples - num_samples_left_to_read; } size_t WavReader::ReadSamples(const size_t num_samples, float* const samples) { #ifndef WEBRTC_ARCH_LITTLE_ENDIAN #error "Need to convert samples to big-endian when reading from WAV file" #endif size_t num_samples_left_to_read = num_samples; size_t next_chunk_start = 0; while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) { const size_t chunk_size = std::min( std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_); size_t num_bytes_read; size_t num_samples_read; if (format_ == WavFormat::kWavFormatPcm) { std::array samples_to_convert; num_bytes_read = file_.Read(samples_to_convert.data(), chunk_size * sizeof(samples_to_convert[0])); num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]); for (size_t j = 0; j < num_samples_read; ++j) { samples[next_chunk_start + j] = static_cast(samples_to_convert[j]); } } else { RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); num_bytes_read = file_.Read(&samples[next_chunk_start], chunk_size * sizeof(samples[0])); num_samples_read = num_bytes_read / sizeof(samples[0]); for (size_t j = 0; j < num_samples_read; ++j) { samples[next_chunk_start + j] = FloatToFloatS16(samples[next_chunk_start + j]); } } RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0) << "Corrupt file: file ended in the middle of a sample."; RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof()) << "Corrupt file: payload size does not match header."; next_chunk_start += num_samples_read; num_unread_samples_ -= num_samples_read; num_samples_left_to_read -= num_samples_read; } return num_samples - num_samples_left_to_read; } void WavReader::Close() { file_.Close(); } WavWriter::WavWriter(const std::string& filename, int sample_rate, size_t num_channels, SampleFormat sample_format) // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 -> // wchar conversion on windows. : WavWriter(FileWrapper::OpenWriteOnly(filename), sample_rate, num_channels, sample_format) {} WavWriter::WavWriter(FileWrapper file, int sample_rate, size_t num_channels, SampleFormat sample_format) : sample_rate_(sample_rate), num_channels_(num_channels), num_samples_written_(0), format_(sample_format == SampleFormat::kInt16 ? WavFormat::kWavFormatPcm : WavFormat::kWavFormatIeeeFloat), file_(std::move(file)) { // Handle errors from the OpenWriteOnly call in above constructor. RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file."; RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_, num_samples_written_)); // Write a blank placeholder header, since we need to know the total number // of samples before we can fill in the real data. static const uint8_t blank_header[MaxWavHeaderSize()] = {0}; RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_))); } void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) { #ifndef WEBRTC_ARCH_LITTLE_ENDIAN #error "Need to convert samples to little-endian when writing to WAV file" #endif for (size_t i = 0; i < num_samples; i += kMaxChunksize) { const size_t num_remaining_samples = num_samples - i; const size_t num_samples_to_write = std::min(kMaxChunksize, num_remaining_samples); if (format_ == WavFormat::kWavFormatPcm) { RTC_CHECK( file_.Write(&samples[i], num_samples_to_write * sizeof(samples[0]))); } else { RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); std::array converted_samples; for (size_t j = 0; j < num_samples_to_write; ++j) { converted_samples[j] = S16ToFloat(samples[i + j]); } RTC_CHECK( file_.Write(converted_samples.data(), num_samples_to_write * sizeof(converted_samples[0]))); } num_samples_written_ += num_samples_to_write; RTC_CHECK_GE(num_samples_written_, num_samples_to_write); // detect size_t overflow } } void WavWriter::WriteSamples(const float* samples, size_t num_samples) { #ifndef WEBRTC_ARCH_LITTLE_ENDIAN #error "Need to convert samples to little-endian when writing to WAV file" #endif for (size_t i = 0; i < num_samples; i += kMaxChunksize) { const size_t num_remaining_samples = num_samples - i; const size_t num_samples_to_write = std::min(kMaxChunksize, num_remaining_samples); if (format_ == WavFormat::kWavFormatPcm) { std::array converted_samples; for (size_t j = 0; j < num_samples_to_write; ++j) { converted_samples[j] = FloatS16ToS16(samples[i + j]); } RTC_CHECK( file_.Write(converted_samples.data(), num_samples_to_write * sizeof(converted_samples[0]))); } else { RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); std::array converted_samples; for (size_t j = 0; j < num_samples_to_write; ++j) { converted_samples[j] = FloatS16ToFloat(samples[i + j]); } RTC_CHECK( file_.Write(converted_samples.data(), num_samples_to_write * sizeof(converted_samples[0]))); } num_samples_written_ += num_samples_to_write; RTC_CHECK(num_samples_written_ >= num_samples_to_write); // detect size_t overflow } } void WavWriter::Close() { RTC_CHECK(file_.Rewind()); std::array header; size_t header_size; WriteWavHeader(num_channels_, sample_rate_, format_, num_samples_written_, header.data(), &header_size); RTC_CHECK(file_.Write(header.data(), header_size)); RTC_CHECK(file_.Close()); } } // namespace webrtc