blob: 127c9c07579b04bee773ee7fc7f326ed4f65bff7 [file] [log] [blame]
andrew@webrtc.orga3ed7132014-10-31 21:51:031/*
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 Bonadei92ea95e2017-09-15 04:47:3111#include "common_audio/wav_file.h"
andrew@webrtc.orga3ed7132014-10-31 21:51:0312
Yves Gerey988cc082018-10-23 10:03:0113#include <errno.h>
Jonas Olssona4d87372019-07-05 17:08:3314
andrew@webrtc.orga3ed7132014-10-31 21:51:0315#include <algorithm>
Per Åhgren5dca3f12020-01-28 08:08:1116#include <array>
andrew@webrtc.orga3ed7132014-10-31 21:51:0317#include <cstdio>
Alessio Bazzicaa33c7af2018-11-08 11:16:1118#include <type_traits>
Niels Möller19a1d502019-06-13 12:08:2419#include <utility>
andrew@webrtc.orga3ed7132014-10-31 21:51:0320
Mirko Bonadei92ea95e2017-09-15 04:47:3121#include "common_audio/include/audio_util.h"
Mirko Bonadei92ea95e2017-09-15 04:47:3122#include "rtc_base/checks.h"
Niels Möllera12c42a2018-07-25 14:05:4823#include "rtc_base/system/arch.h"
andrew@webrtc.orga3ed7132014-10-31 21:51:0324
25namespace webrtc {
Alessio Bazzicaa33c7af2018-11-08 11:16:1126namespace {
andrew@webrtc.orga3ed7132014-10-31 21:51:0327
Alessio Bazzicaa33c7af2018-11-08 11:16:1128static_assert(std::is_trivially_destructible<WavFormat>::value, "");
Per Åhgren5dca3f12020-01-28 08:08:1129
30// Checks whether the format is supported or not.
31bool FormatSupported(WavFormat format) {
32 // Only PCM and IEEE Float formats are supported.
33 return format == WavFormat::kWavFormatPcm ||
34 format == WavFormat::kWavFormatIeeeFloat;
35}
andrew@webrtc.orga3ed7132014-10-31 21:51:0336
andrew@webrtc.org048c5022014-12-16 20:17:2137// Doesn't take ownership of the file handle and won't close it.
Per Åhgren5dca3f12020-01-28 08:08:1138class WavHeaderFileReader : public WavHeaderReader {
andrew@webrtc.org048c5022014-12-16 20:17:2139 public:
Per Åhgren5dca3f12020-01-28 08:08:1140 explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {}
41
42 WavHeaderFileReader(const WavHeaderFileReader&) = delete;
43 WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete;
44
Mirko Bonadei91df0912018-07-17 09:08:1545 size_t Read(void* buf, size_t num_bytes) override {
Niels Möller7ba3b812019-08-06 07:58:5646 size_t count = file_->Read(buf, num_bytes);
47 pos_ += count;
48 return count;
andrew@webrtc.org048c5022014-12-16 20:17:2149 }
Alessio Bazzicaa33c7af2018-11-08 11:16:1150 bool SeekForward(uint32_t num_bytes) override {
Niels Möller7ba3b812019-08-06 07:58:5651 bool success = file_->SeekRelative(num_bytes);
52 if (success) {
53 pos_ += num_bytes;
54 }
55 return success;
Alessio Bazzicaa33c7af2018-11-08 11:16:1156 }
Per Åhgren5dca3f12020-01-28 08:08:1157 int64_t GetPosition() override { return pos_; }
andrew@webrtc.org048c5022014-12-16 20:17:2158
59 private:
Niels Möller7ba3b812019-08-06 07:58:5660 FileWrapper* file_;
61 int64_t pos_ = 0;
andrew@webrtc.org048c5022014-12-16 20:17:2162};
63
Per Åhgren5dca3f12020-01-28 08:08:1164constexpr size_t kMaxChunksize = 4096;
65
Alessio Bazzicaa33c7af2018-11-08 11:16:1166} // namespace
67
Ali Tofigh76d84f12022-05-12 21:24:2768WavReader::WavReader(absl::string_view filename)
Niels Möller7ba3b812019-08-06 07:58:5669 : WavReader(FileWrapper::OpenReadOnly(filename)) {}
Artem Titove62f6002018-03-19 14:40:0070
Niels Möller7ba3b812019-08-06 07:58:5671WavReader::WavReader(FileWrapper file) : file_(std::move(file)) {
72 RTC_CHECK(file_.is_open())
Artem Titove62f6002018-03-19 14:40:0073 << "Invalid file. Could not create file handle for wav file.";
andrew@webrtc.orga3ed7132014-10-31 21:51:0374
Per Åhgren5dca3f12020-01-28 08:08:1175 WavHeaderFileReader readable(&file_);
pkasting25702cb2016-01-08 21:50:2776 size_t bytes_per_sample;
Per Åhgren5dca3f12020-01-28 08:08:1177 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.orga3ed7132014-10-31 21:51:0382}
83
Artem Titov153056b2019-04-16 14:49:3284void WavReader::Reset() {
Niels Möller7ba3b812019-08-06 07:58:5685 RTC_CHECK(file_.SeekTo(data_start_pos_))
Artem Titov153056b2019-04-16 14:49:3286 << "Failed to set position in the file to WAV data start position";
Per Åhgren5dca3f12020-01-28 08:08:1187 num_unread_samples_ = num_samples_in_file_;
Artem Titov153056b2019-04-16 14:49:3288}
89
Per Åhgren5dca3f12020-01-28 08:08:1190size_t WavReader::ReadSamples(const size_t num_samples,
91 int16_t* const samples) {
andrew@webrtc.orga3ed7132014-10-31 21:51:0392#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
93#error "Need to convert samples to big-endian when reading from WAV file"
94#endif
Per Åhgren5dca3f12020-01-28 08:08:1195
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.orga3ed7132014-10-31 21:51:03129}
130
Per Åhgren5dca3f12020-01-28 08:08:11131size_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.orga3ed7132014-10-31 21:51:03172 }
Per Åhgren5dca3f12020-01-28 08:08:11173
174 return num_samples - num_samples_left_to_read;
andrew@webrtc.orga3ed7132014-10-31 21:51:03175}
176
177void WavReader::Close() {
Niels Möller7ba3b812019-08-06 07:58:56178 file_.Close();
andrew@webrtc.orga3ed7132014-10-31 21:51:03179}
180
Ali Tofigh76d84f12022-05-12 21:24:27181WavWriter::WavWriter(absl::string_view filename,
Artem Titove62f6002018-03-19 14:40:00182 int sample_rate,
Per Åhgren5dca3f12020-01-28 08:08:11183 size_t num_channels,
184 SampleFormat sample_format)
Niels Möller7ba3b812019-08-06 07:58:56185 // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
Artem Titove62f6002018-03-19 14:40:00186 // wchar conversion on windows.
Niels Möller19a1d502019-06-13 12:08:24187 : WavWriter(FileWrapper::OpenWriteOnly(filename),
188 sample_rate,
Per Åhgren5dca3f12020-01-28 08:08:11189 num_channels,
190 sample_format) {}
Artem Titove62f6002018-03-19 14:40:00191
Per Åhgren5dca3f12020-01-28 08:08:11192WavWriter::WavWriter(FileWrapper file,
193 int sample_rate,
194 size_t num_channels,
195 SampleFormat sample_format)
Niels Möller19a1d502019-06-13 12:08:24196 : sample_rate_(sample_rate),
197 num_channels_(num_channels),
Per Åhgren5dca3f12020-01-28 08:08:11198 num_samples_written_(0),
199 format_(sample_format == SampleFormat::kInt16
200 ? WavFormat::kWavFormatPcm
201 : WavFormat::kWavFormatIeeeFloat),
Niels Möller19a1d502019-06-13 12:08:24202 file_(std::move(file)) {
Niels Möller7ba3b812019-08-06 07:58:56203 // Handle errors from the OpenWriteOnly call in above constructor.
Niels Möller19a1d502019-06-13 12:08:24204 RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
Artem Titove62f6002018-03-19 14:40:00205
Per Åhgren5dca3f12020-01-28 08:08:11206 RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_,
207 num_samples_written_));
andrew@webrtc.orga3ed7132014-10-31 21:51:03208
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 Åhgren5dca3f12020-01-28 08:08:11211 static const uint8_t blank_header[MaxWavHeaderSize()] = {0};
212 RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_)));
peahd1f718b2016-02-22 10:13:28213}
214
andrew@webrtc.orga3ed7132014-10-31 21:51:03215void 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 Åhgren5dca3f12020-01-28 08:08:11219
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.orga3ed7132014-10-31 21:51:03243}
244
245void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
Per Åhgren5dca3f12020-01-28 08:08:11246#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.orga3ed7132014-10-31 21:51:03277 }
278}
279
280void WavWriter::Close() {
Niels Möller19a1d502019-06-13 12:08:24281 RTC_CHECK(file_.Rewind());
Per Åhgren5dca3f12020-01-28 08:08:11282 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öller19a1d502019-06-13 12:08:24287 RTC_CHECK(file_.Close());
andrew@webrtc.orga3ed7132014-10-31 21:51:03288}
289
290} // namespace webrtc