wav_file.cc (10741B)
1 /* 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "common_audio/wav_file.h" 12 13 #include <algorithm> 14 #include <array> 15 #include <cstdint> 16 #include <cstdio> 17 #include <type_traits> 18 #include <utility> 19 20 #include "absl/strings/string_view.h" 21 #include "common_audio/include/audio_util.h" 22 #include "common_audio/wav_header.h" 23 #include "rtc_base/checks.h" 24 #include "rtc_base/system/arch.h" 25 #include "rtc_base/system/file_wrapper.h" 26 27 namespace webrtc { 28 namespace { 29 30 static_assert(std::is_trivially_destructible<WavFormat>::value, ""); 31 32 // Checks whether the format is supported or not. 33 bool FormatSupported(WavFormat format) { 34 // Only PCM and IEEE Float formats are supported. 35 return format == WavFormat::kWavFormatPcm || 36 format == WavFormat::kWavFormatIeeeFloat; 37 } 38 39 // Doesn't take ownership of the file handle and won't close it. 40 class WavHeaderFileReader : public WavHeaderReader { 41 public: 42 explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {} 43 44 WavHeaderFileReader(const WavHeaderFileReader&) = delete; 45 WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete; 46 47 size_t Read(void* buf, size_t num_bytes) override { 48 size_t count = file_->Read(buf, num_bytes); 49 pos_ += count; 50 return count; 51 } 52 bool SeekForward(uint32_t num_bytes) override { 53 bool success = file_->SeekRelative(num_bytes); 54 if (success) { 55 pos_ += num_bytes; 56 } 57 return success; 58 } 59 int64_t GetPosition() override { return pos_; } 60 61 private: 62 FileWrapper* file_; 63 int64_t pos_ = 0; 64 }; 65 66 constexpr size_t kMaxChunksize = 4096; 67 68 } // namespace 69 70 WavReader::WavReader(absl::string_view filename) 71 : WavReader(FileWrapper::OpenReadOnly(filename)) {} 72 73 WavReader::WavReader(FileWrapper file) : file_(std::move(file)) { 74 RTC_CHECK(file_.is_open()) 75 << "Invalid file. Could not create file handle for wav file."; 76 77 WavHeaderFileReader readable(&file_); 78 size_t bytes_per_sample; 79 RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format_, 80 &bytes_per_sample, &num_samples_in_file_, 81 &data_start_pos_)); 82 num_unread_samples_ = num_samples_in_file_; 83 RTC_CHECK(FormatSupported(format_)) << "Non-implemented wav-format"; 84 } 85 86 void WavReader::Reset() { 87 RTC_CHECK(file_.SeekTo(data_start_pos_)) 88 << "Failed to set position in the file to WAV data start position"; 89 num_unread_samples_ = num_samples_in_file_; 90 } 91 92 size_t WavReader::ReadSamples(const size_t num_samples, 93 int16_t* const samples) { 94 #ifndef WEBRTC_ARCH_LITTLE_ENDIAN 95 #error "Need to convert samples to big-endian when reading from WAV file" 96 #endif 97 98 size_t num_samples_left_to_read = num_samples; 99 size_t next_chunk_start = 0; 100 while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) { 101 const size_t chunk_size = std::min( 102 std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_); 103 size_t num_bytes_read; 104 size_t num_samples_read; 105 if (format_ == WavFormat::kWavFormatIeeeFloat) { 106 std::array<float, kMaxChunksize> samples_to_convert; 107 num_bytes_read = file_.Read(samples_to_convert.data(), 108 chunk_size * sizeof(samples_to_convert[0])); 109 num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]); 110 111 for (size_t j = 0; j < num_samples_read; ++j) { 112 samples[next_chunk_start + j] = FloatToS16(samples_to_convert[j]); 113 } 114 } else { 115 RTC_CHECK_EQ(format_, WavFormat::kWavFormatPcm); 116 num_bytes_read = file_.Read(&samples[next_chunk_start], 117 chunk_size * sizeof(samples[0])); 118 num_samples_read = num_bytes_read / sizeof(samples[0]); 119 } 120 RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0) 121 << "Corrupt file: file ended in the middle of a sample."; 122 RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof()) 123 << "Corrupt file: payload size does not match header."; 124 125 next_chunk_start += num_samples_read; 126 num_unread_samples_ -= num_samples_read; 127 num_samples_left_to_read -= num_samples_read; 128 } 129 130 return num_samples - num_samples_left_to_read; 131 } 132 133 size_t WavReader::ReadSamples(const size_t num_samples, float* const samples) { 134 #ifndef WEBRTC_ARCH_LITTLE_ENDIAN 135 #error "Need to convert samples to big-endian when reading from WAV file" 136 #endif 137 138 size_t num_samples_left_to_read = num_samples; 139 size_t next_chunk_start = 0; 140 while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) { 141 const size_t chunk_size = std::min( 142 std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_); 143 size_t num_bytes_read; 144 size_t num_samples_read; 145 if (format_ == WavFormat::kWavFormatPcm) { 146 std::array<int16_t, kMaxChunksize> samples_to_convert; 147 num_bytes_read = file_.Read(samples_to_convert.data(), 148 chunk_size * sizeof(samples_to_convert[0])); 149 num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]); 150 151 for (size_t j = 0; j < num_samples_read; ++j) { 152 samples[next_chunk_start + j] = 153 static_cast<float>(samples_to_convert[j]); 154 } 155 } else { 156 RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); 157 num_bytes_read = file_.Read(&samples[next_chunk_start], 158 chunk_size * sizeof(samples[0])); 159 num_samples_read = num_bytes_read / sizeof(samples[0]); 160 161 for (size_t j = 0; j < num_samples_read; ++j) { 162 samples[next_chunk_start + j] = 163 FloatToFloatS16(samples[next_chunk_start + j]); 164 } 165 } 166 RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0) 167 << "Corrupt file: file ended in the middle of a sample."; 168 RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof()) 169 << "Corrupt file: payload size does not match header."; 170 171 next_chunk_start += num_samples_read; 172 num_unread_samples_ -= num_samples_read; 173 num_samples_left_to_read -= num_samples_read; 174 } 175 176 return num_samples - num_samples_left_to_read; 177 } 178 179 void WavReader::Close() { 180 file_.Close(); 181 } 182 183 WavWriter::WavWriter(absl::string_view filename, 184 int sample_rate, 185 size_t num_channels, 186 SampleFormat sample_format) 187 // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 -> 188 // wchar conversion on windows. 189 : WavWriter(FileWrapper::OpenWriteOnly(filename), 190 sample_rate, 191 num_channels, 192 sample_format) {} 193 194 WavWriter::WavWriter(FileWrapper file, 195 int sample_rate, 196 size_t num_channels, 197 SampleFormat sample_format) 198 : sample_rate_(sample_rate), 199 num_channels_(num_channels), 200 num_samples_written_(0), 201 format_(sample_format == SampleFormat::kInt16 202 ? WavFormat::kWavFormatPcm 203 : WavFormat::kWavFormatIeeeFloat), 204 file_(std::move(file)) { 205 // Handle errors from the OpenWriteOnly call in above constructor. 206 RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file."; 207 208 RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_, 209 num_samples_written_)); 210 211 // Write a blank placeholder header, since we need to know the total number 212 // of samples before we can fill in the real data. 213 static const uint8_t blank_header[MaxWavHeaderSize()] = {0}; 214 RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_))); 215 } 216 217 void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) { 218 #ifndef WEBRTC_ARCH_LITTLE_ENDIAN 219 #error "Need to convert samples to little-endian when writing to WAV file" 220 #endif 221 222 for (size_t i = 0; i < num_samples; i += kMaxChunksize) { 223 const size_t num_remaining_samples = num_samples - i; 224 const size_t num_samples_to_write = 225 std::min(kMaxChunksize, num_remaining_samples); 226 227 if (format_ == WavFormat::kWavFormatPcm) { 228 RTC_CHECK( 229 file_.Write(&samples[i], num_samples_to_write * sizeof(samples[0]))); 230 } else { 231 RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); 232 std::array<float, kMaxChunksize> converted_samples; 233 for (size_t j = 0; j < num_samples_to_write; ++j) { 234 converted_samples[j] = S16ToFloat(samples[i + j]); 235 } 236 RTC_CHECK( 237 file_.Write(converted_samples.data(), 238 num_samples_to_write * sizeof(converted_samples[0]))); 239 } 240 241 num_samples_written_ += num_samples_to_write; 242 RTC_CHECK_GE(num_samples_written_, 243 num_samples_to_write); // detect size_t overflow 244 } 245 } 246 247 void WavWriter::WriteSamples(const float* samples, size_t num_samples) { 248 #ifndef WEBRTC_ARCH_LITTLE_ENDIAN 249 #error "Need to convert samples to little-endian when writing to WAV file" 250 #endif 251 252 for (size_t i = 0; i < num_samples; i += kMaxChunksize) { 253 const size_t num_remaining_samples = num_samples - i; 254 const size_t num_samples_to_write = 255 std::min(kMaxChunksize, num_remaining_samples); 256 257 if (format_ == WavFormat::kWavFormatPcm) { 258 std::array<int16_t, kMaxChunksize> converted_samples; 259 for (size_t j = 0; j < num_samples_to_write; ++j) { 260 converted_samples[j] = FloatS16ToS16(samples[i + j]); 261 } 262 RTC_CHECK( 263 file_.Write(converted_samples.data(), 264 num_samples_to_write * sizeof(converted_samples[0]))); 265 } else { 266 RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat); 267 std::array<float, kMaxChunksize> converted_samples; 268 for (size_t j = 0; j < num_samples_to_write; ++j) { 269 converted_samples[j] = FloatS16ToFloat(samples[i + j]); 270 } 271 RTC_CHECK( 272 file_.Write(converted_samples.data(), 273 num_samples_to_write * sizeof(converted_samples[0]))); 274 } 275 276 num_samples_written_ += num_samples_to_write; 277 RTC_CHECK(num_samples_written_ >= 278 num_samples_to_write); // detect size_t overflow 279 } 280 } 281 282 void WavWriter::Close() { 283 RTC_CHECK(file_.Rewind()); 284 std::array<uint8_t, MaxWavHeaderSize()> header; 285 size_t header_size; 286 WriteWavHeader(num_channels_, sample_rate_, format_, num_samples_written_, 287 header.data(), &header_size); 288 RTC_CHECK(file_.Write(header.data(), header_size)); 289 RTC_CHECK(file_.Close()); 290 } 291 292 } // namespace webrtc