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