blob: e49126f1392150e808455cea96e4682a0d37362e [file] [log] [blame]
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +00001/*
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 06:47:31 +020011#include "common_audio/wav_file.h"
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000012
Yves Gerey988cc082018-10-23 12:03:01 +020013#include <errno.h>
Jonas Olssona4d87372019-07-05 19:08:33 +020014
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000015#include <algorithm>
Per Åhgren5dca3f12020-01-28 09:08:11 +010016#include <array>
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000017#include <cstdio>
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010018#include <type_traits>
Niels Möller19a1d502019-06-13 14:08:24 +020019#include <utility>
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000020
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "common_audio/include/audio_util.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020022#include "rtc_base/checks.h"
Niels Möllera12c42a2018-07-25 16:05:48 +020023#include "rtc_base/system/arch.h"
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000024
25namespace webrtc {
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010026namespace {
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000027
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010028static_assert(std::is_trivially_destructible<WavFormat>::value, "");
Per Åhgren5dca3f12020-01-28 09:08:11 +010029
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:03 +000036
andrew@webrtc.org048c5022014-12-16 20:17:21 +000037// Doesn't take ownership of the file handle and won't close it.
Per Åhgren5dca3f12020-01-28 09:08:11 +010038class WavHeaderFileReader : public WavHeaderReader {
andrew@webrtc.org048c5022014-12-16 20:17:21 +000039 public:
Per Åhgren5dca3f12020-01-28 09:08:11 +010040 explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {}
41
42 WavHeaderFileReader(const WavHeaderFileReader&) = delete;
43 WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete;
44
Mirko Bonadei91df0912018-07-17 11:08:15 +020045 size_t Read(void* buf, size_t num_bytes) override {
Niels Möller7ba3b812019-08-06 09:58:56 +020046 size_t count = file_->Read(buf, num_bytes);
47 pos_ += count;
48 return count;
andrew@webrtc.org048c5022014-12-16 20:17:21 +000049 }
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010050 bool SeekForward(uint32_t num_bytes) override {
Niels Möller7ba3b812019-08-06 09:58:56 +020051 bool success = file_->SeekRelative(num_bytes);
52 if (success) {
53 pos_ += num_bytes;
54 }
55 return success;
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010056 }
Per Åhgren5dca3f12020-01-28 09:08:11 +010057 int64_t GetPosition() override { return pos_; }
andrew@webrtc.org048c5022014-12-16 20:17:21 +000058
59 private:
Niels Möller7ba3b812019-08-06 09:58:56 +020060 FileWrapper* file_;
61 int64_t pos_ = 0;
andrew@webrtc.org048c5022014-12-16 20:17:21 +000062};
63
Per Åhgren5dca3f12020-01-28 09:08:11 +010064constexpr size_t kMaxChunksize = 4096;
65
Alessio Bazzicaa33c7af2018-11-08 12:16:11 +010066} // namespace
67
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000068WavReader::WavReader(const std::string& filename)
Niels Möller7ba3b812019-08-06 09:58:56 +020069 : WavReader(FileWrapper::OpenReadOnly(filename)) {}
Artem Titove62f6002018-03-19 15:40:00 +010070
Niels Möller7ba3b812019-08-06 09:58:56 +020071WavReader::WavReader(FileWrapper file) : file_(std::move(file)) {
72 RTC_CHECK(file_.is_open())
Artem Titove62f6002018-03-19 15:40:00 +010073 << "Invalid file. Could not create file handle for wav file.";
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000074
Per Åhgren5dca3f12020-01-28 09:08:11 +010075 WavHeaderFileReader readable(&file_);
pkasting25702cb2016-01-08 13:50:27 -080076 size_t bytes_per_sample;
Per Åhgren5dca3f12020-01-28 09:08:11 +010077 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:03 +000082}
83
Artem Titov153056b2019-04-16 16:49:32 +020084void WavReader::Reset() {
Niels Möller7ba3b812019-08-06 09:58:56 +020085 RTC_CHECK(file_.SeekTo(data_start_pos_))
Artem Titov153056b2019-04-16 16:49:32 +020086 << "Failed to set position in the file to WAV data start position";
Per Åhgren5dca3f12020-01-28 09:08:11 +010087 num_unread_samples_ = num_samples_in_file_;
Artem Titov153056b2019-04-16 16:49:32 +020088}
89
Per Åhgren5dca3f12020-01-28 09:08:11 +010090size_t WavReader::ReadSamples(const size_t num_samples,
91 int16_t* const samples) {
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +000092#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 09:08:11 +010095
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:03 +0000129}
130
Per Åhgren5dca3f12020-01-28 09:08:11 +0100131size_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:03 +0000172 }
Per Åhgren5dca3f12020-01-28 09:08:11 +0100173
174 return num_samples - num_samples_left_to_read;
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +0000175}
176
177void WavReader::Close() {
Niels Möller7ba3b812019-08-06 09:58:56 +0200178 file_.Close();
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +0000179}
180
Artem Titove62f6002018-03-19 15:40:00 +0100181WavWriter::WavWriter(const std::string& filename,
182 int sample_rate,
Per Åhgren5dca3f12020-01-28 09:08:11 +0100183 size_t num_channels,
184 SampleFormat sample_format)
Niels Möller7ba3b812019-08-06 09:58:56 +0200185 // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
Artem Titove62f6002018-03-19 15:40:00 +0100186 // wchar conversion on windows.
Niels Möller19a1d502019-06-13 14:08:24 +0200187 : WavWriter(FileWrapper::OpenWriteOnly(filename),
188 sample_rate,
Per Åhgren5dca3f12020-01-28 09:08:11 +0100189 num_channels,
190 sample_format) {}
Artem Titove62f6002018-03-19 15:40:00 +0100191
Per Åhgren5dca3f12020-01-28 09:08:11 +0100192WavWriter::WavWriter(FileWrapper file,
193 int sample_rate,
194 size_t num_channels,
195 SampleFormat sample_format)
Niels Möller19a1d502019-06-13 14:08:24 +0200196 : sample_rate_(sample_rate),
197 num_channels_(num_channels),
Per Åhgren5dca3f12020-01-28 09:08:11 +0100198 num_samples_written_(0),
199 format_(sample_format == SampleFormat::kInt16
200 ? WavFormat::kWavFormatPcm
201 : WavFormat::kWavFormatIeeeFloat),
Niels Möller19a1d502019-06-13 14:08:24 +0200202 file_(std::move(file)) {
Niels Möller7ba3b812019-08-06 09:58:56 +0200203 // Handle errors from the OpenWriteOnly call in above constructor.
Niels Möller19a1d502019-06-13 14:08:24 +0200204 RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
Artem Titove62f6002018-03-19 15:40:00 +0100205
Per Åhgren5dca3f12020-01-28 09:08:11 +0100206 RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_,
207 num_samples_written_));
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +0000208
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 09:08:11 +0100211 static const uint8_t blank_header[MaxWavHeaderSize()] = {0};
212 RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_)));
peahd1f718b2016-02-22 02:13:28 -0800213}
214
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +0000215void 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 09:08:11 +0100219
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:03 +0000243}
244
245void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
Per Åhgren5dca3f12020-01-28 09:08:11 +0100246#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:03 +0000277 }
278}
279
280void WavWriter::Close() {
Niels Möller19a1d502019-06-13 14:08:24 +0200281 RTC_CHECK(file_.Rewind());
Per Åhgren5dca3f12020-01-28 09:08:11 +0100282 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 14:08:24 +0200287 RTC_CHECK(file_.Close());
andrew@webrtc.orga3ed7132014-10-31 21:51:03 +0000288}
289
290} // namespace webrtc