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 <errno.h>
14
15 #include <algorithm>
16 #include <array>
17 #include <cstdio>
18 #include <type_traits>
19 #include <utility>
20
21 #include "common_audio/include/audio_util.h"
22 #include "rtc_base/checks.h"
23 #include "rtc_base/system/arch.h"
24
25 namespace webrtc {
26 namespace {
27
28 static_assert(std::is_trivially_destructible<WavFormat>::value, "");
29
30 // Checks whether the format is supported or not.
FormatSupported(WavFormat format)31 bool FormatSupported(WavFormat format) {
32 // Only PCM and IEEE Float formats are supported.
33 return format == WavFormat::kWavFormatPcm ||
34 format == WavFormat::kWavFormatIeeeFloat;
35 }
36
37 // Doesn't take ownership of the file handle and won't close it.
38 class WavHeaderFileReader : public WavHeaderReader {
39 public:
WavHeaderFileReader(FileWrapper * file)40 explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {}
41
42 WavHeaderFileReader(const WavHeaderFileReader&) = delete;
43 WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete;
44
Read(void * buf,size_t num_bytes)45 size_t Read(void* buf, size_t num_bytes) override {
46 size_t count = file_->Read(buf, num_bytes);
47 pos_ += count;
48 return count;
49 }
SeekForward(uint32_t num_bytes)50 bool SeekForward(uint32_t num_bytes) override {
51 bool success = file_->SeekRelative(num_bytes);
52 if (success) {
53 pos_ += num_bytes;
54 }
55 return success;
56 }
GetPosition()57 int64_t GetPosition() override { return pos_; }
58
59 private:
60 FileWrapper* file_;
61 int64_t pos_ = 0;
62 };
63
64 constexpr size_t kMaxChunksize = 4096;
65
66 } // namespace
67
WavReader(const std::string & filename)68 WavReader::WavReader(const std::string& filename)
69 : WavReader(FileWrapper::OpenReadOnly(filename)) {}
70
WavReader(FileWrapper file)71 WavReader::WavReader(FileWrapper file) : file_(std::move(file)) {
72 RTC_CHECK(file_.is_open())
73 << "Invalid file. Could not create file handle for wav file.";
74
75 WavHeaderFileReader readable(&file_);
76 size_t bytes_per_sample;
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";
82 }
83
Reset()84 void WavReader::Reset() {
85 RTC_CHECK(file_.SeekTo(data_start_pos_))
86 << "Failed to set position in the file to WAV data start position";
87 num_unread_samples_ = num_samples_in_file_;
88 }
89
ReadSamples(const size_t num_samples,int16_t * const samples)90 size_t WavReader::ReadSamples(const size_t num_samples,
91 int16_t* const samples) {
92 #ifndef WEBRTC_ARCH_LITTLE_ENDIAN
93 #error "Need to convert samples to big-endian when reading from WAV file"
94 #endif
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;
129 }
130
ReadSamples(const size_t num_samples,float * const samples)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;
172 }
173
174 return num_samples - num_samples_left_to_read;
175 }
176
Close()177 void WavReader::Close() {
178 file_.Close();
179 }
180
WavWriter(const std::string & filename,int sample_rate,size_t num_channels,SampleFormat sample_format)181 WavWriter::WavWriter(const std::string& filename,
182 int sample_rate,
183 size_t num_channels,
184 SampleFormat sample_format)
185 // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
186 // wchar conversion on windows.
187 : WavWriter(FileWrapper::OpenWriteOnly(filename),
188 sample_rate,
189 num_channels,
190 sample_format) {}
191
WavWriter(FileWrapper file,int sample_rate,size_t num_channels,SampleFormat sample_format)192 WavWriter::WavWriter(FileWrapper file,
193 int sample_rate,
194 size_t num_channels,
195 SampleFormat sample_format)
196 : sample_rate_(sample_rate),
197 num_channels_(num_channels),
198 num_samples_written_(0),
199 format_(sample_format == SampleFormat::kInt16
200 ? WavFormat::kWavFormatPcm
201 : WavFormat::kWavFormatIeeeFloat),
202 file_(std::move(file)) {
203 // Handle errors from the OpenWriteOnly call in above constructor.
204 RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
205
206 RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_,
207 num_samples_written_));
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.
211 static const uint8_t blank_header[MaxWavHeaderSize()] = {0};
212 RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_)));
213 }
214
WriteSamples(const int16_t * samples,size_t num_samples)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
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 }
243 }
244
WriteSamples(const float * samples,size_t num_samples)245 void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
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
277 }
278 }
279
Close()280 void WavWriter::Close() {
281 RTC_CHECK(file_.Rewind());
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));
287 RTC_CHECK(file_.Close());
288 }
289
290 } // namespace webrtc
291