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