summaryrefslogtreecommitdiff
path: root/webrtc/common_audio/wav_file.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/common_audio/wav_file.cc')
-rw-r--r--webrtc/common_audio/wav_file.cc344
1 files changed, 226 insertions, 118 deletions
diff --git a/webrtc/common_audio/wav_file.cc b/webrtc/common_audio/wav_file.cc
index b14b620..e49126f 100644
--- a/webrtc/common_audio/wav_file.cc
+++ b/webrtc/common_audio/wav_file.cc
@@ -8,175 +8,283 @@
* be found in the AUTHORS file in the root of the source tree.
*/
-#include "webrtc/common_audio/wav_file.h"
+#include "common_audio/wav_file.h"
+
+#include <errno.h>
#include <algorithm>
+#include <array>
#include <cstdio>
-#include <limits>
+#include <type_traits>
+#include <utility>
-#include "webrtc/base/checks.h"
-#include "webrtc/base/safe_conversions.h"
-#include "webrtc/common_audio/include/audio_util.h"
-#include "webrtc/common_audio/wav_header.h"
+#include "common_audio/include/audio_util.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/system/arch.h"
namespace webrtc {
+namespace {
+
+static_assert(std::is_trivially_destructible<WavFormat>::value, "");
-// We write 16-bit PCM WAV files.
-static const WavFormat kWavFormat = kWavFormatPcm;
-static const int kBytesPerSample = 2;
+// Checks whether the format is supported or not.
+bool FormatSupported(WavFormat format) {
+ // Only PCM and IEEE Float formats are supported.
+ return format == WavFormat::kWavFormatPcm ||
+ format == WavFormat::kWavFormatIeeeFloat;
+}
// Doesn't take ownership of the file handle and won't close it.
-class ReadableWavFile : public ReadableWav {
+class WavHeaderFileReader : public WavHeaderReader {
public:
- explicit ReadableWavFile(FILE* file) : file_(file) {}
- virtual size_t Read(void* buf, size_t num_bytes) {
- return fread(buf, 1, num_bytes, file_);
+ explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {}
+
+ WavHeaderFileReader(const WavHeaderFileReader&) = delete;
+ WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete;
+
+ size_t Read(void* buf, size_t num_bytes) override {
+ size_t count = file_->Read(buf, num_bytes);
+ pos_ += count;
+ return count;
+ }
+ bool SeekForward(uint32_t num_bytes) override {
+ bool success = file_->SeekRelative(num_bytes);
+ if (success) {
+ pos_ += num_bytes;
+ }
+ return success;
}
+ int64_t GetPosition() override { return pos_; }
private:
- FILE* file_;
+ FileWrapper* file_;
+ int64_t pos_ = 0;
};
-std::string WavFile::FormatAsString() const {
- std::ostringstream s;
- s << "Sample rate: " << sample_rate() << " Hz, Channels: " << num_channels()
- << ", Duration: "
- << (1.f * num_samples()) / (num_channels() * sample_rate()) << " s";
- return s.str();
-}
+constexpr size_t kMaxChunksize = 4096;
+
+} // namespace
WavReader::WavReader(const std::string& filename)
- : file_handle_(fopen(filename.c_str(), "rb")) {
- RTC_CHECK(file_handle_) << "Could not open wav file for reading.";
-
- ReadableWavFile readable(file_handle_);
- WavFormat format;
- int bytes_per_sample;
- RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format,
- &bytes_per_sample, &num_samples_));
- num_samples_remaining_ = num_samples_;
- RTC_CHECK_EQ(kWavFormat, format);
- RTC_CHECK_EQ(kBytesPerSample, bytes_per_sample);
+ : WavReader(FileWrapper::OpenReadOnly(filename)) {}
+
+WavReader::WavReader(FileWrapper file) : file_(std::move(file)) {
+ RTC_CHECK(file_.is_open())
+ << "Invalid file. Could not create file handle for wav file.";
+
+ WavHeaderFileReader readable(&file_);
+ size_t bytes_per_sample;
+ RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format_,
+ &bytes_per_sample, &num_samples_in_file_,
+ &data_start_pos_));
+ num_unread_samples_ = num_samples_in_file_;
+ RTC_CHECK(FormatSupported(format_)) << "Non-implemented wav-format";
}
-WavReader::~WavReader() {
- Close();
+void WavReader::Reset() {
+ RTC_CHECK(file_.SeekTo(data_start_pos_))
+ << "Failed to set position in the file to WAV data start position";
+ num_unread_samples_ = num_samples_in_file_;
}
-size_t WavReader::ReadSamples(size_t num_samples, int16_t* samples) {
+size_t WavReader::ReadSamples(const size_t num_samples,
+ int16_t* const samples) {
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
#error "Need to convert samples to big-endian when reading from WAV file"
#endif
- // There could be metadata after the audio; ensure we don't read it.
- num_samples = std::min(rtc::checked_cast<uint32_t>(num_samples),
- num_samples_remaining_);
- const size_t read =
- fread(samples, sizeof(*samples), num_samples, file_handle_);
- // If we didn't read what was requested, ensure we've reached the EOF.
- RTC_CHECK(read == num_samples || feof(file_handle_));
- RTC_CHECK_LE(read, num_samples_remaining_);
- num_samples_remaining_ -= rtc::checked_cast<uint32_t>(read);
- return read;
+
+ size_t num_samples_left_to_read = num_samples;
+ size_t next_chunk_start = 0;
+ while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) {
+ const size_t chunk_size = std::min(
+ std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_);
+ size_t num_bytes_read;
+ size_t num_samples_read;
+ if (format_ == WavFormat::kWavFormatIeeeFloat) {
+ std::array<float, kMaxChunksize> samples_to_convert;
+ num_bytes_read = file_.Read(samples_to_convert.data(),
+ chunk_size * sizeof(samples_to_convert[0]));
+ num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]);
+
+ for (size_t j = 0; j < num_samples_read; ++j) {
+ samples[next_chunk_start + j] = FloatToS16(samples_to_convert[j]);
+ }
+ } else {
+ RTC_CHECK_EQ(format_, WavFormat::kWavFormatPcm);
+ num_bytes_read = file_.Read(&samples[next_chunk_start],
+ chunk_size * sizeof(samples[0]));
+ num_samples_read = num_bytes_read / sizeof(samples[0]);
+ }
+ RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0)
+ << "Corrupt file: file ended in the middle of a sample.";
+ RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof())
+ << "Corrupt file: payload size does not match header.";
+
+ next_chunk_start += num_samples_read;
+ num_unread_samples_ -= num_samples_read;
+ num_samples_left_to_read -= num_samples_read;
+ }
+
+ return num_samples - num_samples_left_to_read;
}
-size_t WavReader::ReadSamples(size_t num_samples, float* samples) {
- static const size_t kChunksize = 4096 / sizeof(uint16_t);
- size_t read = 0;
- for (size_t i = 0; i < num_samples; i += kChunksize) {
- int16_t isamples[kChunksize];
- size_t chunk = std::min(kChunksize, num_samples - i);
- chunk = ReadSamples(chunk, isamples);
- for (size_t j = 0; j < chunk; ++j)
- samples[i + j] = isamples[j];
- read += chunk;
+size_t WavReader::ReadSamples(const size_t num_samples, float* const samples) {
+#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
+#error "Need to convert samples to big-endian when reading from WAV file"
+#endif
+
+ size_t num_samples_left_to_read = num_samples;
+ size_t next_chunk_start = 0;
+ while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) {
+ const size_t chunk_size = std::min(
+ std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_);
+ size_t num_bytes_read;
+ size_t num_samples_read;
+ if (format_ == WavFormat::kWavFormatPcm) {
+ std::array<int16_t, kMaxChunksize> samples_to_convert;
+ num_bytes_read = file_.Read(samples_to_convert.data(),
+ chunk_size * sizeof(samples_to_convert[0]));
+ num_samples_read = num_bytes_read / sizeof(samples_to_convert[0]);
+
+ for (size_t j = 0; j < num_samples_read; ++j) {
+ samples[next_chunk_start + j] =
+ static_cast<float>(samples_to_convert[j]);
+ }
+ } else {
+ RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat);
+ num_bytes_read = file_.Read(&samples[next_chunk_start],
+ chunk_size * sizeof(samples[0]));
+ num_samples_read = num_bytes_read / sizeof(samples[0]);
+
+ for (size_t j = 0; j < num_samples_read; ++j) {
+ samples[next_chunk_start + j] =
+ FloatToFloatS16(samples[next_chunk_start + j]);
+ }
+ }
+ RTC_CHECK(num_samples_read == 0 || (num_bytes_read % num_samples_read) == 0)
+ << "Corrupt file: file ended in the middle of a sample.";
+ RTC_CHECK(num_samples_read == chunk_size || file_.ReadEof())
+ << "Corrupt file: payload size does not match header.";
+
+ next_chunk_start += num_samples_read;
+ num_unread_samples_ -= num_samples_read;
+ num_samples_left_to_read -= num_samples_read;
}
- return read;
+
+ return num_samples - num_samples_left_to_read;
}
void WavReader::Close() {
- RTC_CHECK_EQ(0, fclose(file_handle_));
- file_handle_ = NULL;
+ file_.Close();
}
-WavWriter::WavWriter(const std::string& filename, int sample_rate,
- int num_channels)
+WavWriter::WavWriter(const std::string& filename,
+ int sample_rate,
+ size_t num_channels,
+ SampleFormat sample_format)
+ // Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
+ // wchar conversion on windows.
+ : WavWriter(FileWrapper::OpenWriteOnly(filename),
+ sample_rate,
+ num_channels,
+ sample_format) {}
+
+WavWriter::WavWriter(FileWrapper file,
+ int sample_rate,
+ size_t num_channels,
+ SampleFormat sample_format)
: sample_rate_(sample_rate),
num_channels_(num_channels),
- num_samples_(0),
- file_handle_(fopen(filename.c_str(), "wb")) {
- RTC_CHECK(file_handle_) << "Could not open wav file for writing.";
- RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat,
- kBytesPerSample, num_samples_));
+ num_samples_written_(0),
+ format_(sample_format == SampleFormat::kInt16
+ ? WavFormat::kWavFormatPcm
+ : WavFormat::kWavFormatIeeeFloat),
+ file_(std::move(file)) {
+ // Handle errors from the OpenWriteOnly call in above constructor.
+ RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
+
+ RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_,
+ num_samples_written_));
// Write a blank placeholder header, since we need to know the total number
// of samples before we can fill in the real data.
- static const uint8_t blank_header[kWavHeaderSize] = {0};
- RTC_CHECK_EQ(1u, fwrite(blank_header, kWavHeaderSize, 1, file_handle_));
-}
-
-WavWriter::~WavWriter() {
- Close();
+ static const uint8_t blank_header[MaxWavHeaderSize()] = {0};
+ RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_)));
}
void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) {
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
#error "Need to convert samples to little-endian when writing to WAV file"
#endif
- const size_t written =
- fwrite(samples, sizeof(*samples), num_samples, file_handle_);
- RTC_CHECK_EQ(num_samples, written);
- num_samples_ += static_cast<uint32_t>(written);
- RTC_CHECK(written <= std::numeric_limits<uint32_t>::max() ||
- num_samples_ >= written); // detect uint32_t overflow
-}
-
-void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
- static const size_t kChunksize = 4096 / sizeof(uint16_t);
- for (size_t i = 0; i < num_samples; i += kChunksize) {
- int16_t isamples[kChunksize];
- const size_t chunk = std::min(kChunksize, num_samples - i);
- FloatS16ToS16(samples + i, chunk, isamples);
- WriteSamples(isamples, chunk);
- }
-}
-void WavWriter::Close() {
- RTC_CHECK_EQ(0, fseek(file_handle_, 0, SEEK_SET));
- uint8_t header[kWavHeaderSize];
- WriteWavHeader(header, num_channels_, sample_rate_, kWavFormat,
- kBytesPerSample, num_samples_);
- RTC_CHECK_EQ(1u, fwrite(header, kWavHeaderSize, 1, file_handle_));
- RTC_CHECK_EQ(0, fclose(file_handle_));
- file_handle_ = NULL;
-}
+ for (size_t i = 0; i < num_samples; i += kMaxChunksize) {
+ const size_t num_remaining_samples = num_samples - i;
+ const size_t num_samples_to_write =
+ std::min(kMaxChunksize, num_remaining_samples);
-} // namespace webrtc
+ if (format_ == WavFormat::kWavFormatPcm) {
+ RTC_CHECK(
+ file_.Write(&samples[i], num_samples_to_write * sizeof(samples[0])));
+ } else {
+ RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat);
+ std::array<float, kMaxChunksize> converted_samples;
+ for (size_t j = 0; j < num_samples_to_write; ++j) {
+ converted_samples[j] = S16ToFloat(samples[i + j]);
+ }
+ RTC_CHECK(
+ file_.Write(converted_samples.data(),
+ num_samples_to_write * sizeof(converted_samples[0])));
+ }
-rtc_WavWriter* rtc_WavOpen(const char* filename,
- int sample_rate,
- int num_channels) {
- return reinterpret_cast<rtc_WavWriter*>(
- new webrtc::WavWriter(filename, sample_rate, num_channels));
+ num_samples_written_ += num_samples_to_write;
+ RTC_CHECK_GE(num_samples_written_,
+ num_samples_to_write); // detect size_t overflow
+ }
}
-void rtc_WavClose(rtc_WavWriter* wf) {
- delete reinterpret_cast<webrtc::WavWriter*>(wf);
-}
+void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
+#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
+#error "Need to convert samples to little-endian when writing to WAV file"
+#endif
-void rtc_WavWriteSamples(rtc_WavWriter* wf,
- const float* samples,
- size_t num_samples) {
- reinterpret_cast<webrtc::WavWriter*>(wf)->WriteSamples(samples, num_samples);
-}
+ for (size_t i = 0; i < num_samples; i += kMaxChunksize) {
+ const size_t num_remaining_samples = num_samples - i;
+ const size_t num_samples_to_write =
+ std::min(kMaxChunksize, num_remaining_samples);
-int rtc_WavSampleRate(const rtc_WavWriter* wf) {
- return reinterpret_cast<const webrtc::WavWriter*>(wf)->sample_rate();
-}
+ if (format_ == WavFormat::kWavFormatPcm) {
+ std::array<int16_t, kMaxChunksize> converted_samples;
+ for (size_t j = 0; j < num_samples_to_write; ++j) {
+ converted_samples[j] = FloatS16ToS16(samples[i + j]);
+ }
+ RTC_CHECK(
+ file_.Write(converted_samples.data(),
+ num_samples_to_write * sizeof(converted_samples[0])));
+ } else {
+ RTC_CHECK_EQ(format_, WavFormat::kWavFormatIeeeFloat);
+ std::array<float, kMaxChunksize> converted_samples;
+ for (size_t j = 0; j < num_samples_to_write; ++j) {
+ converted_samples[j] = FloatS16ToFloat(samples[i + j]);
+ }
+ RTC_CHECK(
+ file_.Write(converted_samples.data(),
+ num_samples_to_write * sizeof(converted_samples[0])));
+ }
-int rtc_WavNumChannels(const rtc_WavWriter* wf) {
- return reinterpret_cast<const webrtc::WavWriter*>(wf)->num_channels();
+ num_samples_written_ += num_samples_to_write;
+ RTC_CHECK(num_samples_written_ >=
+ num_samples_to_write); // detect size_t overflow
+ }
}
-uint32_t rtc_WavNumSamples(const rtc_WavWriter* wf) {
- return reinterpret_cast<const webrtc::WavWriter*>(wf)->num_samples();
+void WavWriter::Close() {
+ RTC_CHECK(file_.Rewind());
+ std::array<uint8_t, MaxWavHeaderSize()> header;
+ size_t header_size;
+ WriteWavHeader(num_channels_, sample_rate_, format_, num_samples_written_,
+ header.data(), &header_size);
+ RTC_CHECK(file_.Write(header.data(), header_size));
+ RTC_CHECK(file_.Close());
}
+
+} // namespace webrtc