mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Add floating point support for writing and reading wav files
This CL adds support for reading and writing floating point wav files in WebRTC. It also updates the former wav handling code as well as adds some simplifications. Beyond this, the CL also adds support in the APM data_dumper and in the audioproc_f tool for using the floating point wav format. Bug: webrtc:11307 Change-Id: I2ea33fd12f590b6031ac85f75708f6cc88a266b4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/162902 Reviewed-by: Sam Zackrisson <saza@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Commit-Queue: Per Åhgren <peah@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30423}
This commit is contained in:
parent
190539717b
commit
5dca3f1336
11 changed files with 685 additions and 360 deletions
|
@ -13,30 +13,35 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "common_audio/include/audio_util.h"
|
#include "common_audio/include/audio_util.h"
|
||||||
#include "common_audio/wav_header.h"
|
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
#include "rtc_base/logging.h"
|
|
||||||
#include "rtc_base/system/arch.h"
|
#include "rtc_base/system/arch.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// We write 16-bit PCM WAV files.
|
|
||||||
constexpr WavFormat kWavFormat = kWavFormatPcm;
|
|
||||||
static_assert(std::is_trivially_destructible<WavFormat>::value, "");
|
static_assert(std::is_trivially_destructible<WavFormat>::value, "");
|
||||||
constexpr size_t 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.
|
// Doesn't take ownership of the file handle and won't close it.
|
||||||
class ReadableWavFile : public ReadableWav {
|
class WavHeaderFileReader : public WavHeaderReader {
|
||||||
public:
|
public:
|
||||||
explicit ReadableWavFile(FileWrapper* file) : file_(file) {}
|
explicit WavHeaderFileReader(FileWrapper* file) : file_(file) {}
|
||||||
ReadableWavFile(const ReadableWavFile&) = delete;
|
|
||||||
ReadableWavFile& operator=(const ReadableWavFile&) = delete;
|
WavHeaderFileReader(const WavHeaderFileReader&) = delete;
|
||||||
|
WavHeaderFileReader& operator=(const WavHeaderFileReader&) = delete;
|
||||||
|
|
||||||
size_t Read(void* buf, size_t num_bytes) override {
|
size_t Read(void* buf, size_t num_bytes) override {
|
||||||
size_t count = file_->Read(buf, num_bytes);
|
size_t count = file_->Read(buf, num_bytes);
|
||||||
pos_ += count;
|
pos_ += count;
|
||||||
|
@ -49,13 +54,15 @@ class ReadableWavFile : public ReadableWav {
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
int64_t GetPosition() { return pos_; }
|
int64_t GetPosition() override { return pos_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileWrapper* file_;
|
FileWrapper* file_;
|
||||||
int64_t pos_ = 0;
|
int64_t pos_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr size_t kMaxChunksize = 4096;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
WavReader::WavReader(const std::string& filename)
|
WavReader::WavReader(const std::string& filename)
|
||||||
|
@ -65,69 +72,106 @@ WavReader::WavReader(FileWrapper file) : file_(std::move(file)) {
|
||||||
RTC_CHECK(file_.is_open())
|
RTC_CHECK(file_.is_open())
|
||||||
<< "Invalid file. Could not create file handle for wav file.";
|
<< "Invalid file. Could not create file handle for wav file.";
|
||||||
|
|
||||||
ReadableWavFile readable(&file_);
|
WavHeaderFileReader readable(&file_);
|
||||||
WavFormat format;
|
|
||||||
size_t bytes_per_sample;
|
size_t bytes_per_sample;
|
||||||
RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format,
|
RTC_CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format_,
|
||||||
&bytes_per_sample, &num_samples_));
|
&bytes_per_sample, &num_samples_in_file_,
|
||||||
num_samples_remaining_ = num_samples_;
|
&data_start_pos_));
|
||||||
RTC_CHECK_EQ(kWavFormat, format);
|
num_unread_samples_ = num_samples_in_file_;
|
||||||
RTC_CHECK_EQ(kBytesPerSample, bytes_per_sample);
|
RTC_CHECK(FormatSupported(format_)) << "Non-implemented wav-format";
|
||||||
data_start_pos_ = readable.GetPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
WavReader::~WavReader() {
|
|
||||||
Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WavReader::Reset() {
|
void WavReader::Reset() {
|
||||||
RTC_CHECK(file_.SeekTo(data_start_pos_))
|
RTC_CHECK(file_.SeekTo(data_start_pos_))
|
||||||
<< "Failed to set position in the file to WAV data start position";
|
<< "Failed to set position in the file to WAV data start position";
|
||||||
num_samples_remaining_ = num_samples_;
|
num_unread_samples_ = num_samples_in_file_;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WavReader::sample_rate() const {
|
size_t WavReader::ReadSamples(const size_t num_samples,
|
||||||
return sample_rate_;
|
int16_t* const samples) {
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavReader::num_channels() const {
|
|
||||||
return num_channels_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavReader::num_samples() const {
|
|
||||||
return num_samples_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavReader::ReadSamples(size_t num_samples, int16_t* samples) {
|
|
||||||
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
||||||
#error "Need to convert samples to big-endian when reading from WAV file"
|
#error "Need to convert samples to big-endian when reading from WAV file"
|
||||||
#endif
|
#endif
|
||||||
// There could be metadata after the audio; ensure we don't read it.
|
|
||||||
num_samples = std::min(num_samples, num_samples_remaining_);
|
size_t num_samples_left_to_read = num_samples;
|
||||||
const size_t num_bytes = num_samples * sizeof(*samples);
|
size_t next_chunk_start = 0;
|
||||||
const size_t read_bytes = file_.Read(samples, num_bytes);
|
while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) {
|
||||||
// If we didn't read what was requested, ensure we've reached the EOF.
|
const size_t chunk_size = std::min(
|
||||||
RTC_CHECK(read_bytes == num_bytes || file_.ReadEof());
|
std::min(kMaxChunksize, num_samples_left_to_read), num_unread_samples_);
|
||||||
RTC_CHECK_EQ(read_bytes % 2, 0)
|
size_t num_bytes_read;
|
||||||
<< "End of file in the middle of a 16-bit sample";
|
size_t num_samples_read;
|
||||||
const size_t read_samples = read_bytes / 2;
|
if (format_ == WavFormat::kWavFormatIeeeFloat) {
|
||||||
RTC_CHECK_LE(read_samples, num_samples_remaining_);
|
std::array<float, kMaxChunksize> samples_to_convert;
|
||||||
num_samples_remaining_ -= read_samples;
|
num_bytes_read = file_.Read(samples_to_convert.data(),
|
||||||
return read_samples;
|
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) {
|
size_t WavReader::ReadSamples(const size_t num_samples, float* const samples) {
|
||||||
static const size_t kChunksize = 4096 / sizeof(uint16_t);
|
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
||||||
size_t read = 0;
|
#error "Need to convert samples to big-endian when reading from WAV file"
|
||||||
for (size_t i = 0; i < num_samples; i += kChunksize) {
|
#endif
|
||||||
int16_t isamples[kChunksize];
|
|
||||||
size_t chunk = std::min(kChunksize, num_samples - i);
|
size_t num_samples_left_to_read = num_samples;
|
||||||
chunk = ReadSamples(chunk, isamples);
|
size_t next_chunk_start = 0;
|
||||||
for (size_t j = 0; j < chunk; ++j)
|
while (num_samples_left_to_read > 0 && num_unread_samples_ > 0) {
|
||||||
samples[i + j] = isamples[j];
|
const size_t chunk_size = std::min(
|
||||||
read += chunk;
|
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() {
|
void WavReader::Close() {
|
||||||
|
@ -136,71 +180,110 @@ void WavReader::Close() {
|
||||||
|
|
||||||
WavWriter::WavWriter(const std::string& filename,
|
WavWriter::WavWriter(const std::string& filename,
|
||||||
int sample_rate,
|
int sample_rate,
|
||||||
size_t num_channels)
|
size_t num_channels,
|
||||||
|
SampleFormat sample_format)
|
||||||
// Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
|
// Unlike plain fopen, OpenWriteOnly takes care of filename utf8 ->
|
||||||
// wchar conversion on windows.
|
// wchar conversion on windows.
|
||||||
: WavWriter(FileWrapper::OpenWriteOnly(filename),
|
: WavWriter(FileWrapper::OpenWriteOnly(filename),
|
||||||
sample_rate,
|
sample_rate,
|
||||||
num_channels) {}
|
num_channels,
|
||||||
|
sample_format) {}
|
||||||
|
|
||||||
WavWriter::WavWriter(FileWrapper file, int sample_rate, size_t num_channels)
|
WavWriter::WavWriter(FileWrapper file,
|
||||||
|
int sample_rate,
|
||||||
|
size_t num_channels,
|
||||||
|
SampleFormat sample_format)
|
||||||
: sample_rate_(sample_rate),
|
: sample_rate_(sample_rate),
|
||||||
num_channels_(num_channels),
|
num_channels_(num_channels),
|
||||||
num_samples_(0),
|
num_samples_written_(0),
|
||||||
|
format_(sample_format == SampleFormat::kInt16
|
||||||
|
? WavFormat::kWavFormatPcm
|
||||||
|
: WavFormat::kWavFormatIeeeFloat),
|
||||||
file_(std::move(file)) {
|
file_(std::move(file)) {
|
||||||
// Handle errors from the OpenWriteOnly call in above constructor.
|
// Handle errors from the OpenWriteOnly call in above constructor.
|
||||||
RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
|
RTC_CHECK(file_.is_open()) << "Invalid file. Could not create wav file.";
|
||||||
|
|
||||||
RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat,
|
RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, format_,
|
||||||
kBytesPerSample, num_samples_));
|
num_samples_written_));
|
||||||
|
|
||||||
// Write a blank placeholder header, since we need to know the total number
|
// Write a blank placeholder header, since we need to know the total number
|
||||||
// of samples before we can fill in the real data.
|
// of samples before we can fill in the real data.
|
||||||
static const uint8_t blank_header[kWavHeaderSize] = {0};
|
static const uint8_t blank_header[MaxWavHeaderSize()] = {0};
|
||||||
RTC_CHECK(file_.Write(blank_header, kWavHeaderSize));
|
RTC_CHECK(file_.Write(blank_header, WavHeaderSize(format_)));
|
||||||
}
|
|
||||||
|
|
||||||
WavWriter::~WavWriter() {
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int WavWriter::sample_rate() const {
|
|
||||||
return sample_rate_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavWriter::num_channels() const {
|
|
||||||
return num_channels_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavWriter::num_samples() const {
|
|
||||||
return num_samples_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) {
|
void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) {
|
||||||
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
||||||
#error "Need to convert samples to little-endian when writing to WAV file"
|
#error "Need to convert samples to little-endian when writing to WAV file"
|
||||||
#endif
|
#endif
|
||||||
RTC_CHECK(file_.Write(samples, sizeof(*samples) * num_samples));
|
|
||||||
num_samples_ += num_samples;
|
for (size_t i = 0; i < num_samples; i += kMaxChunksize) {
|
||||||
RTC_CHECK(num_samples_ >= num_samples); // detect size_t overflow
|
const size_t num_remaining_samples = num_samples - i;
|
||||||
|
const size_t num_samples_to_write =
|
||||||
|
std::min(kMaxChunksize, num_remaining_samples);
|
||||||
|
|
||||||
|
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])));
|
||||||
|
}
|
||||||
|
|
||||||
|
num_samples_written_ += num_samples_to_write;
|
||||||
|
RTC_CHECK_GE(num_samples_written_,
|
||||||
|
num_samples_to_write); // detect size_t overflow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
|
void WavWriter::WriteSamples(const float* samples, size_t num_samples) {
|
||||||
static const size_t kChunksize = 4096 / sizeof(uint16_t);
|
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
||||||
for (size_t i = 0; i < num_samples; i += kChunksize) {
|
#error "Need to convert samples to little-endian when writing to WAV file"
|
||||||
int16_t isamples[kChunksize];
|
#endif
|
||||||
const size_t chunk = std::min(kChunksize, num_samples - i);
|
|
||||||
FloatS16ToS16(samples + i, chunk, isamples);
|
for (size_t i = 0; i < num_samples; i += kMaxChunksize) {
|
||||||
WriteSamples(isamples, chunk);
|
const size_t num_remaining_samples = num_samples - i;
|
||||||
|
const size_t num_samples_to_write =
|
||||||
|
std::min(kMaxChunksize, num_remaining_samples);
|
||||||
|
|
||||||
|
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])));
|
||||||
|
}
|
||||||
|
|
||||||
|
num_samples_written_ += num_samples_to_write;
|
||||||
|
RTC_CHECK(num_samples_written_ >=
|
||||||
|
num_samples_to_write); // detect size_t overflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WavWriter::Close() {
|
void WavWriter::Close() {
|
||||||
RTC_CHECK(file_.Rewind());
|
RTC_CHECK(file_.Rewind());
|
||||||
uint8_t header[kWavHeaderSize];
|
std::array<uint8_t, MaxWavHeaderSize()> header;
|
||||||
WriteWavHeader(header, num_channels_, sample_rate_, kWavFormat,
|
size_t header_size;
|
||||||
kBytesPerSample, num_samples_);
|
WriteWavHeader(num_channels_, sample_rate_, format_, num_samples_written_,
|
||||||
RTC_CHECK(file_.Write(header, kWavHeaderSize));
|
header.data(), &header_size);
|
||||||
|
RTC_CHECK(file_.Write(header.data(), header_size));
|
||||||
RTC_CHECK(file_.Close());
|
RTC_CHECK(file_.Close());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,16 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "rtc_base/constructor_magic.h"
|
#include "common_audio/wav_header.h"
|
||||||
#include "rtc_base/system/file_wrapper.h"
|
#include "rtc_base/system/file_wrapper.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
// Interface to provide access to WAV file parameters.
|
// Interface to provide access WAV file parameters.
|
||||||
class WavFile {
|
class WavFile {
|
||||||
public:
|
public:
|
||||||
|
enum class SampleFormat { kInt16, kFloat };
|
||||||
|
|
||||||
virtual ~WavFile() {}
|
virtual ~WavFile() {}
|
||||||
|
|
||||||
virtual int sample_rate() const = 0;
|
virtual int sample_rate() const = 0;
|
||||||
|
@ -31,37 +33,44 @@ class WavFile {
|
||||||
virtual size_t num_samples() const = 0;
|
virtual size_t num_samples() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simple C++ class for writing 16-bit PCM WAV files. All error handling is
|
// Simple C++ class for writing 16-bit integer and 32 bit floating point PCM WAV
|
||||||
// by calls to RTC_CHECK(), making it unsuitable for anything but debug code.
|
// files. All error handling is by calls to RTC_CHECK(), making it unsuitable
|
||||||
|
// for anything but debug code.
|
||||||
class WavWriter final : public WavFile {
|
class WavWriter final : public WavFile {
|
||||||
public:
|
public:
|
||||||
// Open a new WAV file for writing.
|
// Opens a new WAV file for writing.
|
||||||
WavWriter(const std::string& filename, int sample_rate, size_t num_channels);
|
WavWriter(const std::string& filename,
|
||||||
|
int sample_rate,
|
||||||
|
size_t num_channels,
|
||||||
|
SampleFormat sample_format = SampleFormat::kInt16);
|
||||||
|
WavWriter(FileWrapper file,
|
||||||
|
int sample_rate,
|
||||||
|
size_t num_channels,
|
||||||
|
SampleFormat sample_format = SampleFormat::kInt16);
|
||||||
|
|
||||||
// Open a new WAV file for writing.
|
// Closes the WAV file, after writing its header.
|
||||||
WavWriter(FileWrapper file, int sample_rate, size_t num_channels);
|
~WavWriter() { Close(); }
|
||||||
|
|
||||||
// Close the WAV file, after writing its header.
|
WavWriter(const WavWriter&) = delete;
|
||||||
~WavWriter() override;
|
WavWriter& operator=(const WavWriter&) = delete;
|
||||||
|
|
||||||
// Write additional samples to the file. Each sample is in the range
|
// Write additional samples to the file. Each sample is in the range
|
||||||
// [-32768,32767], and there must be the previously specified number of
|
// [-32768.0,32767.0], and there must be the previously specified number of
|
||||||
// interleaved channels.
|
// interleaved channels.
|
||||||
void WriteSamples(const float* samples, size_t num_samples);
|
void WriteSamples(const float* samples, size_t num_samples);
|
||||||
void WriteSamples(const int16_t* samples, size_t num_samples);
|
void WriteSamples(const int16_t* samples, size_t num_samples);
|
||||||
|
|
||||||
int sample_rate() const override;
|
int sample_rate() const override { return sample_rate_; }
|
||||||
size_t num_channels() const override;
|
size_t num_channels() const override { return num_channels_; }
|
||||||
size_t num_samples() const override;
|
size_t num_samples() const override { return num_samples_written_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Close();
|
void Close();
|
||||||
const int sample_rate_;
|
const int sample_rate_;
|
||||||
const size_t num_channels_;
|
const size_t num_channels_;
|
||||||
size_t num_samples_; // Total number of samples written to file.
|
size_t num_samples_written_;
|
||||||
FileWrapper file_; // Output file, owned by this class
|
WavFormat format_;
|
||||||
|
FileWrapper file_;
|
||||||
RTC_DISALLOW_COPY_AND_ASSIGN(WavWriter);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Follows the conventions of WavWriter.
|
// Follows the conventions of WavWriter.
|
||||||
|
@ -69,12 +78,13 @@ class WavReader final : public WavFile {
|
||||||
public:
|
public:
|
||||||
// Opens an existing WAV file for reading.
|
// Opens an existing WAV file for reading.
|
||||||
explicit WavReader(const std::string& filename);
|
explicit WavReader(const std::string& filename);
|
||||||
|
|
||||||
// Use an existing WAV file for reading.
|
|
||||||
explicit WavReader(FileWrapper file);
|
explicit WavReader(FileWrapper file);
|
||||||
|
|
||||||
// Close the WAV file.
|
// Close the WAV file.
|
||||||
~WavReader() override;
|
~WavReader() { Close(); }
|
||||||
|
|
||||||
|
WavReader(const WavReader&) = delete;
|
||||||
|
WavReader& operator=(const WavReader&) = delete;
|
||||||
|
|
||||||
// Resets position to the beginning of the file.
|
// Resets position to the beginning of the file.
|
||||||
void Reset();
|
void Reset();
|
||||||
|
@ -84,21 +94,20 @@ class WavReader final : public WavFile {
|
||||||
size_t ReadSamples(size_t num_samples, float* samples);
|
size_t ReadSamples(size_t num_samples, float* samples);
|
||||||
size_t ReadSamples(size_t num_samples, int16_t* samples);
|
size_t ReadSamples(size_t num_samples, int16_t* samples);
|
||||||
|
|
||||||
int sample_rate() const override;
|
int sample_rate() const override { return sample_rate_; }
|
||||||
size_t num_channels() const override;
|
size_t num_channels() const override { return num_channels_; }
|
||||||
size_t num_samples() const override;
|
size_t num_samples() const override { return num_samples_in_file_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Close();
|
void Close();
|
||||||
int sample_rate_;
|
int sample_rate_;
|
||||||
size_t num_channels_;
|
size_t num_channels_;
|
||||||
size_t num_samples_; // Total number of samples in the file.
|
WavFormat format_;
|
||||||
size_t num_samples_remaining_;
|
size_t num_samples_in_file_;
|
||||||
FileWrapper file_; // Input file, owned by this class.
|
size_t num_unread_samples_;
|
||||||
|
FileWrapper file_;
|
||||||
int64_t
|
int64_t
|
||||||
data_start_pos_; // Position in the file immediately after WAV header.
|
data_start_pos_; // Position in the file immediately after WAV header.
|
||||||
|
|
||||||
RTC_DISALLOW_COPY_AND_ASSIGN(WavReader);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -78,7 +78,7 @@ TEST(WavWriterTest, MAYBE_CPP) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
static const size_t kContentSize =
|
static const size_t kContentSize =
|
||||||
kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
|
kPcmWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
|
||||||
static_assert(sizeof(kExpectedContents) == kContentSize, "content size");
|
static_assert(sizeof(kExpectedContents) == kContentSize, "content size");
|
||||||
EXPECT_EQ(kContentSize, test::GetFileSize(outfile));
|
EXPECT_EQ(kContentSize, test::GetFileSize(outfile));
|
||||||
FILE* f = fopen(outfile.c_str(), "rb");
|
FILE* f = fopen(outfile.c_str(), "rb");
|
||||||
|
@ -103,43 +103,75 @@ TEST(WavWriterTest, MAYBE_CPP) {
|
||||||
|
|
||||||
// Write a larger WAV file. You can listen to this file to sanity-check it.
|
// Write a larger WAV file. You can listen to this file to sanity-check it.
|
||||||
TEST(WavWriterTest, LargeFile) {
|
TEST(WavWriterTest, LargeFile) {
|
||||||
std::string outfile = test::OutputPath() + "wavtest3.wav";
|
constexpr int kSampleRate = 8000;
|
||||||
static const int kSampleRate = 8000;
|
constexpr size_t kNumChannels = 2;
|
||||||
static const size_t kNumChannels = 2;
|
constexpr size_t kNumSamples = 3 * kSampleRate * kNumChannels;
|
||||||
static const size_t kNumSamples = 3 * kSampleRate * kNumChannels;
|
for (WavFile::SampleFormat wav_format :
|
||||||
float samples[kNumSamples];
|
{WavFile::SampleFormat::kInt16, WavFile::SampleFormat::kFloat}) {
|
||||||
for (size_t i = 0; i < kNumSamples; i += kNumChannels) {
|
for (WavFile::SampleFormat write_format :
|
||||||
// A nice periodic beeping sound.
|
{WavFile::SampleFormat::kInt16, WavFile::SampleFormat::kFloat}) {
|
||||||
static const double kToneHz = 440;
|
for (WavFile::SampleFormat read_format :
|
||||||
const double t = static_cast<double>(i) / (kNumChannels * kSampleRate);
|
{WavFile::SampleFormat::kInt16, WavFile::SampleFormat::kFloat}) {
|
||||||
const double x =
|
std::string outfile = test::OutputPath() + "wavtest3.wav";
|
||||||
std::numeric_limits<int16_t>::max() * std::sin(t * kToneHz * 2 * M_PI);
|
float samples[kNumSamples];
|
||||||
samples[i] = std::pow(std::sin(t * 2 * 2 * M_PI), 10) * x;
|
for (size_t i = 0; i < kNumSamples; i += kNumChannels) {
|
||||||
samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x;
|
// A nice periodic beeping sound.
|
||||||
}
|
static const double kToneHz = 440;
|
||||||
{
|
const double t =
|
||||||
WavWriter w(outfile, kSampleRate, kNumChannels);
|
static_cast<double>(i) / (kNumChannels * kSampleRate);
|
||||||
EXPECT_EQ(kSampleRate, w.sample_rate());
|
const double x = std::numeric_limits<int16_t>::max() *
|
||||||
EXPECT_EQ(kNumChannels, w.num_channels());
|
std::sin(t * kToneHz * 2 * M_PI);
|
||||||
EXPECT_EQ(0u, w.num_samples());
|
samples[i] = std::pow(std::sin(t * 2 * 2 * M_PI), 10) * x;
|
||||||
w.WriteSamples(samples, kNumSamples);
|
samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x;
|
||||||
EXPECT_EQ(kNumSamples, w.num_samples());
|
}
|
||||||
}
|
{
|
||||||
EXPECT_EQ(sizeof(int16_t) * kNumSamples + kWavHeaderSize,
|
WavWriter w(outfile, kSampleRate, kNumChannels, wav_format);
|
||||||
test::GetFileSize(outfile));
|
EXPECT_EQ(kSampleRate, w.sample_rate());
|
||||||
|
EXPECT_EQ(kNumChannels, w.num_channels());
|
||||||
|
EXPECT_EQ(0u, w.num_samples());
|
||||||
|
if (write_format == WavFile::SampleFormat::kFloat) {
|
||||||
|
float truncated_samples[kNumSamples];
|
||||||
|
for (size_t k = 0; k < kNumSamples; ++k) {
|
||||||
|
truncated_samples[k] = static_cast<int16_t>(samples[k]);
|
||||||
|
}
|
||||||
|
w.WriteSamples(truncated_samples, kNumSamples);
|
||||||
|
} else {
|
||||||
|
w.WriteSamples(samples, kNumSamples);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(kNumSamples, w.num_samples());
|
||||||
|
}
|
||||||
|
if (wav_format == WavFile::SampleFormat::kFloat) {
|
||||||
|
EXPECT_EQ(sizeof(float) * kNumSamples + kIeeeFloatWavHeaderSize,
|
||||||
|
test::GetFileSize(outfile));
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(sizeof(int16_t) * kNumSamples + kPcmWavHeaderSize,
|
||||||
|
test::GetFileSize(outfile));
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
WavReader r(outfile);
|
WavReader r(outfile);
|
||||||
EXPECT_EQ(kSampleRate, r.sample_rate());
|
EXPECT_EQ(kSampleRate, r.sample_rate());
|
||||||
EXPECT_EQ(kNumChannels, r.num_channels());
|
EXPECT_EQ(kNumChannels, r.num_channels());
|
||||||
EXPECT_EQ(kNumSamples, r.num_samples());
|
EXPECT_EQ(kNumSamples, r.num_samples());
|
||||||
|
|
||||||
float read_samples[kNumSamples];
|
if (read_format == WavFile::SampleFormat::kFloat) {
|
||||||
EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, read_samples));
|
float read_samples[kNumSamples];
|
||||||
for (size_t i = 0; i < kNumSamples; ++i)
|
EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, read_samples));
|
||||||
EXPECT_NEAR(samples[i], read_samples[i], 1);
|
for (size_t i = 0; i < kNumSamples; ++i) {
|
||||||
|
EXPECT_NEAR(samples[i], read_samples[i], 1);
|
||||||
EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples));
|
}
|
||||||
|
EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples));
|
||||||
|
} else {
|
||||||
|
int16_t read_samples[kNumSamples];
|
||||||
|
EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, read_samples));
|
||||||
|
for (size_t i = 0; i < kNumSamples; ++i) {
|
||||||
|
EXPECT_NEAR(samples[i], static_cast<float>(read_samples[i]), 1);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +220,7 @@ TEST(WavReaderTest, MAYBE_CPPReset) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
static const size_t kContentSize =
|
static const size_t kContentSize =
|
||||||
kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
|
kPcmWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
|
||||||
static_assert(sizeof(kExpectedContents) == kContentSize, "content size");
|
static_assert(sizeof(kExpectedContents) == kContentSize, "content size");
|
||||||
EXPECT_EQ(kContentSize, test::GetFileSize(outfile));
|
EXPECT_EQ(kContentSize, test::GetFileSize(outfile));
|
||||||
FILE* f = fopen(outfile.c_str(), "rb");
|
FILE* f = fopen(outfile.c_str(), "rb");
|
||||||
|
|
|
@ -26,20 +26,28 @@
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
#ifndef WEBRTC_ARCH_LITTLE_ENDIAN
|
||||||
|
#error "Code not working properly for big endian platforms."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma pack(2)
|
||||||
struct ChunkHeader {
|
struct ChunkHeader {
|
||||||
uint32_t ID;
|
uint32_t ID;
|
||||||
uint32_t Size;
|
uint32_t Size;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(ChunkHeader) == 8, "ChunkHeader size");
|
static_assert(sizeof(ChunkHeader) == 8, "ChunkHeader size");
|
||||||
|
|
||||||
|
#pragma pack(2)
|
||||||
struct RiffHeader {
|
struct RiffHeader {
|
||||||
ChunkHeader header;
|
ChunkHeader header;
|
||||||
uint32_t Format;
|
uint32_t Format;
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(RiffHeader) == sizeof(ChunkHeader) + 4, "RiffHeader size");
|
||||||
|
|
||||||
// We can't nest this definition in WavHeader, because VS2013 gives an error
|
// We can't nest this definition in WavHeader, because VS2013 gives an error
|
||||||
// on sizeof(WavHeader::fmt): "error C2070: 'unknown': illegal sizeof operand".
|
// on sizeof(WavHeader::fmt): "error C2070: 'unknown': illegal sizeof operand".
|
||||||
struct FmtSubchunk {
|
#pragma pack(2)
|
||||||
|
struct FmtPcmSubchunk {
|
||||||
ChunkHeader header;
|
ChunkHeader header;
|
||||||
uint16_t AudioFormat;
|
uint16_t AudioFormat;
|
||||||
uint16_t NumChannels;
|
uint16_t NumChannels;
|
||||||
|
@ -48,60 +56,108 @@ struct FmtSubchunk {
|
||||||
uint16_t BlockAlign;
|
uint16_t BlockAlign;
|
||||||
uint16_t BitsPerSample;
|
uint16_t BitsPerSample;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(FmtSubchunk) == 24, "FmtSubchunk size");
|
static_assert(sizeof(FmtPcmSubchunk) == 24, "FmtPcmSubchunk size");
|
||||||
const uint32_t kFmtSubchunkSize = sizeof(FmtSubchunk) - sizeof(ChunkHeader);
|
const uint32_t kFmtPcmSubchunkSize =
|
||||||
|
sizeof(FmtPcmSubchunk) - sizeof(ChunkHeader);
|
||||||
|
|
||||||
// Simple wav header. It does not include chunks that are not essential to read
|
// Pack struct to avoid additional padding bytes.
|
||||||
// audio samples.
|
#pragma pack(2)
|
||||||
struct WavHeader {
|
struct FmtIeeeFloatSubchunk {
|
||||||
WavHeader(const WavHeader&) = default;
|
ChunkHeader header;
|
||||||
WavHeader& operator=(const WavHeader&) = default;
|
uint16_t AudioFormat;
|
||||||
|
uint16_t NumChannels;
|
||||||
|
uint32_t SampleRate;
|
||||||
|
uint32_t ByteRate;
|
||||||
|
uint16_t BlockAlign;
|
||||||
|
uint16_t BitsPerSample;
|
||||||
|
uint16_t ExtensionSize;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FmtIeeeFloatSubchunk) == 26, "FmtIeeeFloatSubchunk size");
|
||||||
|
const uint32_t kFmtIeeeFloatSubchunkSize =
|
||||||
|
sizeof(FmtIeeeFloatSubchunk) - sizeof(ChunkHeader);
|
||||||
|
|
||||||
|
// Simple PCM wav header. It does not include chunks that are not essential to
|
||||||
|
// read audio samples.
|
||||||
|
#pragma pack(2)
|
||||||
|
struct WavHeaderPcm {
|
||||||
|
WavHeaderPcm(const WavHeaderPcm&) = default;
|
||||||
|
WavHeaderPcm& operator=(const WavHeaderPcm&) = default;
|
||||||
RiffHeader riff;
|
RiffHeader riff;
|
||||||
FmtSubchunk fmt;
|
FmtPcmSubchunk fmt;
|
||||||
struct {
|
struct {
|
||||||
ChunkHeader header;
|
ChunkHeader header;
|
||||||
} data;
|
} data;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(WavHeader) == kWavHeaderSize, "no padding in header");
|
static_assert(sizeof(WavHeaderPcm) == kPcmWavHeaderSize,
|
||||||
|
"no padding in header");
|
||||||
|
|
||||||
#ifdef WEBRTC_ARCH_LITTLE_ENDIAN
|
// IEEE Float Wav header, includes extra chunks necessary for proper non-PCM
|
||||||
static inline void WriteLE16(uint16_t* f, uint16_t x) {
|
// WAV implementation.
|
||||||
*f = x;
|
#pragma pack(2)
|
||||||
}
|
struct WavHeaderIeeeFloat {
|
||||||
static inline void WriteLE32(uint32_t* f, uint32_t x) {
|
WavHeaderIeeeFloat(const WavHeaderIeeeFloat&) = default;
|
||||||
*f = x;
|
WavHeaderIeeeFloat& operator=(const WavHeaderIeeeFloat&) = default;
|
||||||
}
|
RiffHeader riff;
|
||||||
static inline void WriteFourCC(uint32_t* f, char a, char b, char c, char d) {
|
FmtIeeeFloatSubchunk fmt;
|
||||||
*f = static_cast<uint32_t>(a) | static_cast<uint32_t>(b) << 8 |
|
struct {
|
||||||
static_cast<uint32_t>(c) << 16 | static_cast<uint32_t>(d) << 24;
|
ChunkHeader header;
|
||||||
|
uint32_t SampleLength;
|
||||||
|
} fact;
|
||||||
|
struct {
|
||||||
|
ChunkHeader header;
|
||||||
|
} data;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WavHeaderIeeeFloat) == kIeeeFloatWavHeaderSize,
|
||||||
|
"no padding in header");
|
||||||
|
|
||||||
|
uint32_t PackFourCC(char a, char b, char c, char d) {
|
||||||
|
uint32_t packed_value =
|
||||||
|
static_cast<uint32_t>(a) | static_cast<uint32_t>(b) << 8 |
|
||||||
|
static_cast<uint32_t>(c) << 16 | static_cast<uint32_t>(d) << 24;
|
||||||
|
return packed_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint16_t ReadLE16(uint16_t x) {
|
std::string ReadFourCC(uint32_t x) {
|
||||||
return x;
|
|
||||||
}
|
|
||||||
static inline uint32_t ReadLE32(uint32_t x) {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
static inline std::string ReadFourCC(uint32_t x) {
|
|
||||||
return std::string(reinterpret_cast<char*>(&x), 4);
|
return std::string(reinterpret_cast<char*>(&x), 4);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
#error "Write be-to-le conversion functions"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline uint32_t RiffChunkSize(size_t bytes_in_payload) {
|
uint16_t MapWavFormatToHeaderField(WavFormat format) {
|
||||||
return static_cast<uint32_t>(bytes_in_payload + kWavHeaderSize -
|
switch (format) {
|
||||||
|
case WavFormat::kWavFormatPcm:
|
||||||
|
return 1;
|
||||||
|
case WavFormat::kWavFormatIeeeFloat:
|
||||||
|
return 3;
|
||||||
|
case WavFormat::kWavFormatALaw:
|
||||||
|
return 6;
|
||||||
|
case WavFormat::kWavFormatMuLaw:
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
RTC_CHECK(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
WavFormat MapHeaderFieldToWavFormat(uint16_t format_header_value) {
|
||||||
|
if (format_header_value == 1) {
|
||||||
|
return WavFormat::kWavFormatPcm;
|
||||||
|
}
|
||||||
|
if (format_header_value == 3) {
|
||||||
|
return WavFormat::kWavFormatIeeeFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTC_CHECK(false) << "Unsupported WAV format";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RiffChunkSize(size_t bytes_in_payload, size_t header_size) {
|
||||||
|
return static_cast<uint32_t>(bytes_in_payload + header_size -
|
||||||
sizeof(ChunkHeader));
|
sizeof(ChunkHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint32_t ByteRate(size_t num_channels,
|
uint32_t ByteRate(size_t num_channels,
|
||||||
int sample_rate,
|
int sample_rate,
|
||||||
size_t bytes_per_sample) {
|
size_t bytes_per_sample) {
|
||||||
return static_cast<uint32_t>(num_channels * sample_rate * bytes_per_sample);
|
return static_cast<uint32_t>(num_channels * sample_rate * bytes_per_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint16_t BlockAlign(size_t num_channels,
|
uint16_t BlockAlign(size_t num_channels, size_t bytes_per_sample) {
|
||||||
size_t bytes_per_sample) {
|
|
||||||
return static_cast<uint16_t>(num_channels * bytes_per_sample);
|
return static_cast<uint16_t>(num_channels * bytes_per_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +165,7 @@ static inline uint16_t BlockAlign(size_t num_channels,
|
||||||
// first byte of the sought chunk data. If not found, the end of the file is
|
// first byte of the sought chunk data. If not found, the end of the file is
|
||||||
// reached.
|
// reached.
|
||||||
bool FindWaveChunk(ChunkHeader* chunk_header,
|
bool FindWaveChunk(ChunkHeader* chunk_header,
|
||||||
ReadableWav* readable,
|
WavHeaderReader* readable,
|
||||||
const std::string sought_chunk_id) {
|
const std::string sought_chunk_id) {
|
||||||
RTC_DCHECK_EQ(sought_chunk_id.size(), 4);
|
RTC_DCHECK_EQ(sought_chunk_id.size(), 4);
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -124,17 +180,17 @@ bool FindWaveChunk(ChunkHeader* chunk_header,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadFmtChunkData(FmtSubchunk* fmt_subchunk, ReadableWav* readable) {
|
bool ReadFmtChunkData(FmtPcmSubchunk* fmt_subchunk, WavHeaderReader* readable) {
|
||||||
// Reads "fmt " chunk payload.
|
// Reads "fmt " chunk payload.
|
||||||
if (readable->Read(&(fmt_subchunk->AudioFormat), kFmtSubchunkSize) !=
|
if (readable->Read(&(fmt_subchunk->AudioFormat), kFmtPcmSubchunkSize) !=
|
||||||
kFmtSubchunkSize)
|
kFmtPcmSubchunkSize)
|
||||||
return false;
|
return false;
|
||||||
const uint32_t fmt_size = ReadLE32(fmt_subchunk->header.Size);
|
const uint32_t fmt_size = fmt_subchunk->header.Size;
|
||||||
if (fmt_size != kFmtSubchunkSize) {
|
if (fmt_size != kFmtPcmSubchunkSize) {
|
||||||
// There is an optional two-byte extension field permitted to be present
|
// There is an optional two-byte extension field permitted to be present
|
||||||
// with PCM, but which must be zero.
|
// with PCM, but which must be zero.
|
||||||
int16_t ext_size;
|
int16_t ext_size;
|
||||||
if (kFmtSubchunkSize + sizeof(ext_size) != fmt_size)
|
if (kFmtPcmSubchunkSize + sizeof(ext_size) != fmt_size)
|
||||||
return false;
|
return false;
|
||||||
if (readable->Read(&ext_size, sizeof(ext_size)) != sizeof(ext_size))
|
if (readable->Read(&ext_size, sizeof(ext_size)) != sizeof(ext_size))
|
||||||
return false;
|
return false;
|
||||||
|
@ -144,7 +200,89 @@ bool ReadFmtChunkData(FmtSubchunk* fmt_subchunk, ReadableWav* readable) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
void WritePcmWavHeader(size_t num_channels,
|
||||||
|
int sample_rate,
|
||||||
|
size_t bytes_per_sample,
|
||||||
|
size_t num_samples,
|
||||||
|
uint8_t* buf,
|
||||||
|
size_t* header_size) {
|
||||||
|
RTC_CHECK(buf);
|
||||||
|
RTC_CHECK(header_size);
|
||||||
|
*header_size = kPcmWavHeaderSize;
|
||||||
|
auto header = rtc::MsanUninitialized<WavHeaderPcm>({});
|
||||||
|
const size_t bytes_in_payload = bytes_per_sample * num_samples;
|
||||||
|
|
||||||
|
header.riff.header.ID = PackFourCC('R', 'I', 'F', 'F');
|
||||||
|
header.riff.header.Size = RiffChunkSize(bytes_in_payload, *header_size);
|
||||||
|
header.riff.Format = PackFourCC('W', 'A', 'V', 'E');
|
||||||
|
header.fmt.header.ID = PackFourCC('f', 'm', 't', ' ');
|
||||||
|
header.fmt.header.Size = kFmtPcmSubchunkSize;
|
||||||
|
header.fmt.AudioFormat = MapWavFormatToHeaderField(WavFormat::kWavFormatPcm);
|
||||||
|
header.fmt.NumChannels = static_cast<uint16_t>(num_channels);
|
||||||
|
header.fmt.SampleRate = sample_rate;
|
||||||
|
header.fmt.ByteRate = ByteRate(num_channels, sample_rate, bytes_per_sample);
|
||||||
|
header.fmt.BlockAlign = BlockAlign(num_channels, bytes_per_sample);
|
||||||
|
header.fmt.BitsPerSample = static_cast<uint16_t>(8 * bytes_per_sample);
|
||||||
|
header.data.header.ID = PackFourCC('d', 'a', 't', 'a');
|
||||||
|
header.data.header.Size = static_cast<uint32_t>(bytes_in_payload);
|
||||||
|
|
||||||
|
// Do an extra copy rather than writing everything to buf directly, since buf
|
||||||
|
// might not be correctly aligned.
|
||||||
|
memcpy(buf, &header, *header_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteIeeeFloatWavHeader(size_t num_channels,
|
||||||
|
int sample_rate,
|
||||||
|
size_t bytes_per_sample,
|
||||||
|
size_t num_samples,
|
||||||
|
uint8_t* buf,
|
||||||
|
size_t* header_size) {
|
||||||
|
RTC_CHECK(buf);
|
||||||
|
RTC_CHECK(header_size);
|
||||||
|
*header_size = kIeeeFloatWavHeaderSize;
|
||||||
|
auto header = rtc::MsanUninitialized<WavHeaderIeeeFloat>({});
|
||||||
|
const size_t bytes_in_payload = bytes_per_sample * num_samples;
|
||||||
|
|
||||||
|
header.riff.header.ID = PackFourCC('R', 'I', 'F', 'F');
|
||||||
|
header.riff.header.Size = RiffChunkSize(bytes_in_payload, *header_size);
|
||||||
|
header.riff.Format = PackFourCC('W', 'A', 'V', 'E');
|
||||||
|
header.fmt.header.ID = PackFourCC('f', 'm', 't', ' ');
|
||||||
|
header.fmt.header.Size = kFmtIeeeFloatSubchunkSize;
|
||||||
|
header.fmt.AudioFormat =
|
||||||
|
MapWavFormatToHeaderField(WavFormat::kWavFormatIeeeFloat);
|
||||||
|
header.fmt.NumChannels = static_cast<uint16_t>(num_channels);
|
||||||
|
header.fmt.SampleRate = sample_rate;
|
||||||
|
header.fmt.ByteRate = ByteRate(num_channels, sample_rate, bytes_per_sample);
|
||||||
|
header.fmt.BlockAlign = BlockAlign(num_channels, bytes_per_sample);
|
||||||
|
header.fmt.BitsPerSample = static_cast<uint16_t>(8 * bytes_per_sample);
|
||||||
|
header.fmt.ExtensionSize = 0;
|
||||||
|
header.fact.header.ID = PackFourCC('f', 'a', 'c', 't');
|
||||||
|
header.fact.header.Size = 4;
|
||||||
|
header.fact.SampleLength = static_cast<uint32_t>(num_channels * num_samples);
|
||||||
|
header.data.header.ID = PackFourCC('d', 'a', 't', 'a');
|
||||||
|
header.data.header.Size = static_cast<uint32_t>(bytes_in_payload);
|
||||||
|
|
||||||
|
// Do an extra copy rather than writing everything to buf directly, since buf
|
||||||
|
// might not be correctly aligned.
|
||||||
|
memcpy(buf, &header, *header_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of bytes per sample for the format.
|
||||||
|
size_t GetFormatBytesPerSample(WavFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case WavFormat::kWavFormatPcm:
|
||||||
|
// Other values may be OK, but for now we're conservative.
|
||||||
|
return 2;
|
||||||
|
case WavFormat::kWavFormatALaw:
|
||||||
|
case WavFormat::kWavFormatMuLaw:
|
||||||
|
return 1;
|
||||||
|
case WavFormat::kWavFormatIeeeFloat:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
RTC_CHECK(false);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CheckWavParameters(size_t num_channels,
|
bool CheckWavParameters(size_t num_channels,
|
||||||
int sample_rate,
|
int sample_rate,
|
||||||
|
@ -169,23 +307,27 @@ bool CheckWavParameters(size_t num_channels,
|
||||||
|
|
||||||
// format and bytes_per_sample must agree.
|
// format and bytes_per_sample must agree.
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case kWavFormatPcm:
|
case WavFormat::kWavFormatPcm:
|
||||||
// Other values may be OK, but for now we're conservative:
|
// Other values may be OK, but for now we're conservative:
|
||||||
if (bytes_per_sample != 1 && bytes_per_sample != 2)
|
if (bytes_per_sample != 1 && bytes_per_sample != 2)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case kWavFormatALaw:
|
case WavFormat::kWavFormatALaw:
|
||||||
case kWavFormatMuLaw:
|
case WavFormat::kWavFormatMuLaw:
|
||||||
if (bytes_per_sample != 1)
|
if (bytes_per_sample != 1)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
case WavFormat::kWavFormatIeeeFloat:
|
||||||
|
if (bytes_per_sample != 4)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The number of bytes in the file, not counting the first ChunkHeader, must
|
// The number of bytes in the file, not counting the first ChunkHeader, must
|
||||||
// be less than 2^32; otherwise, the ChunkSize field overflows.
|
// be less than 2^32; otherwise, the ChunkSize field overflows.
|
||||||
const size_t header_size = kWavHeaderSize - sizeof(ChunkHeader);
|
const size_t header_size = kPcmWavHeaderSize - sizeof(ChunkHeader);
|
||||||
const size_t max_samples =
|
const size_t max_samples =
|
||||||
(std::numeric_limits<uint32_t>::max() - header_size) / bytes_per_sample;
|
(std::numeric_limits<uint32_t>::max() - header_size) / bytes_per_sample;
|
||||||
if (num_samples > max_samples)
|
if (num_samples > max_samples)
|
||||||
|
@ -198,48 +340,47 @@ bool CheckWavParameters(size_t num_channels,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteWavHeader(uint8_t* buf,
|
} // namespace
|
||||||
size_t num_channels,
|
|
||||||
int sample_rate,
|
|
||||||
WavFormat format,
|
|
||||||
size_t bytes_per_sample,
|
|
||||||
size_t num_samples) {
|
|
||||||
RTC_CHECK(CheckWavParameters(num_channels, sample_rate, format,
|
|
||||||
bytes_per_sample, num_samples));
|
|
||||||
|
|
||||||
auto header = rtc::MsanUninitialized<WavHeader>({});
|
bool CheckWavParameters(size_t num_channels,
|
||||||
const size_t bytes_in_payload = bytes_per_sample * num_samples;
|
int sample_rate,
|
||||||
|
WavFormat format,
|
||||||
WriteFourCC(&header.riff.header.ID, 'R', 'I', 'F', 'F');
|
size_t num_samples) {
|
||||||
WriteLE32(&header.riff.header.Size, RiffChunkSize(bytes_in_payload));
|
return CheckWavParameters(num_channels, sample_rate, format,
|
||||||
WriteFourCC(&header.riff.Format, 'W', 'A', 'V', 'E');
|
GetFormatBytesPerSample(format), num_samples);
|
||||||
|
|
||||||
WriteFourCC(&header.fmt.header.ID, 'f', 'm', 't', ' ');
|
|
||||||
WriteLE32(&header.fmt.header.Size, kFmtSubchunkSize);
|
|
||||||
WriteLE16(&header.fmt.AudioFormat, format);
|
|
||||||
WriteLE16(&header.fmt.NumChannels, static_cast<uint16_t>(num_channels));
|
|
||||||
WriteLE32(&header.fmt.SampleRate, sample_rate);
|
|
||||||
WriteLE32(&header.fmt.ByteRate,
|
|
||||||
ByteRate(num_channels, sample_rate, bytes_per_sample));
|
|
||||||
WriteLE16(&header.fmt.BlockAlign, BlockAlign(num_channels, bytes_per_sample));
|
|
||||||
WriteLE16(&header.fmt.BitsPerSample,
|
|
||||||
static_cast<uint16_t>(8 * bytes_per_sample));
|
|
||||||
|
|
||||||
WriteFourCC(&header.data.header.ID, 'd', 'a', 't', 'a');
|
|
||||||
WriteLE32(&header.data.header.Size, static_cast<uint32_t>(bytes_in_payload));
|
|
||||||
|
|
||||||
// Do an extra copy rather than writing everything to buf directly, since buf
|
|
||||||
// might not be correctly aligned.
|
|
||||||
memcpy(buf, &header, kWavHeaderSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadWavHeader(ReadableWav* readable,
|
void WriteWavHeader(size_t num_channels,
|
||||||
|
int sample_rate,
|
||||||
|
WavFormat format,
|
||||||
|
size_t num_samples,
|
||||||
|
uint8_t* buf,
|
||||||
|
size_t* header_size) {
|
||||||
|
RTC_CHECK(buf);
|
||||||
|
RTC_CHECK(header_size);
|
||||||
|
|
||||||
|
const size_t bytes_per_sample = GetFormatBytesPerSample(format);
|
||||||
|
RTC_CHECK(CheckWavParameters(num_channels, sample_rate, format,
|
||||||
|
bytes_per_sample, num_samples));
|
||||||
|
if (format == WavFormat::kWavFormatPcm) {
|
||||||
|
WritePcmWavHeader(num_channels, sample_rate, bytes_per_sample, num_samples,
|
||||||
|
buf, header_size);
|
||||||
|
} else {
|
||||||
|
RTC_CHECK_EQ(format, WavFormat::kWavFormatIeeeFloat);
|
||||||
|
WriteIeeeFloatWavHeader(num_channels, sample_rate, bytes_per_sample,
|
||||||
|
num_samples, buf, header_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadWavHeader(WavHeaderReader* readable,
|
||||||
size_t* num_channels,
|
size_t* num_channels,
|
||||||
int* sample_rate,
|
int* sample_rate,
|
||||||
WavFormat* format,
|
WavFormat* format,
|
||||||
size_t* bytes_per_sample,
|
size_t* bytes_per_sample,
|
||||||
size_t* num_samples) {
|
size_t* num_samples,
|
||||||
auto header = rtc::MsanUninitialized<WavHeader>({});
|
int64_t* data_start_pos) {
|
||||||
|
// Read using the PCM header, even though it might be float Wav file
|
||||||
|
auto header = rtc::MsanUninitialized<WavHeaderPcm>({});
|
||||||
|
|
||||||
// Read RIFF chunk.
|
// Read RIFF chunk.
|
||||||
if (readable->Read(&header.riff, sizeof(header.riff)) != sizeof(header.riff))
|
if (readable->Read(&header.riff, sizeof(header.riff)) != sizeof(header.riff))
|
||||||
|
@ -267,26 +408,34 @@ bool ReadWavHeader(ReadableWav* readable,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse needed fields.
|
// Parse needed fields.
|
||||||
*format = static_cast<WavFormat>(ReadLE16(header.fmt.AudioFormat));
|
*format = MapHeaderFieldToWavFormat(header.fmt.AudioFormat);
|
||||||
*num_channels = ReadLE16(header.fmt.NumChannels);
|
*num_channels = header.fmt.NumChannels;
|
||||||
*sample_rate = ReadLE32(header.fmt.SampleRate);
|
*sample_rate = header.fmt.SampleRate;
|
||||||
*bytes_per_sample = ReadLE16(header.fmt.BitsPerSample) / 8;
|
*bytes_per_sample = header.fmt.BitsPerSample / 8;
|
||||||
const size_t bytes_in_payload = ReadLE32(header.data.header.Size);
|
const size_t bytes_in_payload = header.data.header.Size;
|
||||||
if (*bytes_per_sample == 0)
|
if (*bytes_per_sample == 0)
|
||||||
return false;
|
return false;
|
||||||
*num_samples = bytes_in_payload / *bytes_per_sample;
|
*num_samples = bytes_in_payload / *bytes_per_sample;
|
||||||
|
|
||||||
if (ReadLE32(header.riff.header.Size) < RiffChunkSize(bytes_in_payload))
|
const size_t header_size = *format == WavFormat::kWavFormatPcm
|
||||||
|
? kPcmWavHeaderSize
|
||||||
|
: kIeeeFloatWavHeaderSize;
|
||||||
|
|
||||||
|
if (header.riff.header.Size < RiffChunkSize(bytes_in_payload, header_size))
|
||||||
return false;
|
return false;
|
||||||
if (ReadLE32(header.fmt.ByteRate) !=
|
if (header.fmt.ByteRate !=
|
||||||
ByteRate(*num_channels, *sample_rate, *bytes_per_sample))
|
ByteRate(*num_channels, *sample_rate, *bytes_per_sample))
|
||||||
return false;
|
return false;
|
||||||
if (ReadLE16(header.fmt.BlockAlign) !=
|
if (header.fmt.BlockAlign != BlockAlign(*num_channels, *bytes_per_sample))
|
||||||
BlockAlign(*num_channels, *bytes_per_sample))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return CheckWavParameters(*num_channels, *sample_rate, *format,
|
if (!CheckWavParameters(*num_channels, *sample_rate, *format,
|
||||||
*bytes_per_sample, *num_samples);
|
*bytes_per_sample, *num_samples)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data_start_pos = readable->GetPosition();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -13,52 +13,77 @@
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "rtc_base/checks.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
static const size_t kWavHeaderSize = 44;
|
// Interface providing header reading functionality.
|
||||||
|
class WavHeaderReader {
|
||||||
class ReadableWav {
|
|
||||||
public:
|
public:
|
||||||
// Returns the number of bytes read.
|
// Returns the number of bytes read.
|
||||||
virtual size_t Read(void* buf, size_t num_bytes) = 0;
|
virtual size_t Read(void* buf, size_t num_bytes) = 0;
|
||||||
virtual bool SeekForward(uint32_t num_bytes) = 0;
|
virtual bool SeekForward(uint32_t num_bytes) = 0;
|
||||||
virtual ~ReadableWav() = default;
|
virtual ~WavHeaderReader() = default;
|
||||||
|
virtual int64_t GetPosition() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WavFormat {
|
// Possible WAV formats.
|
||||||
kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample
|
enum class WavFormat {
|
||||||
kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law
|
kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample.
|
||||||
kWavFormatMuLaw = 7, // 8-bit ITU-T G.711 mu-law
|
kWavFormatIeeeFloat = 3, // IEEE float.
|
||||||
|
kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law.
|
||||||
|
kWavFormatMuLaw = 7, // 8-bit ITU-T G.711 mu-law.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Header sizes for supported WAV formats.
|
||||||
|
constexpr size_t kPcmWavHeaderSize = 44;
|
||||||
|
constexpr size_t kIeeeFloatWavHeaderSize = 58;
|
||||||
|
|
||||||
|
// Returns the size of the WAV header for the specified format.
|
||||||
|
constexpr size_t WavHeaderSize(WavFormat format) {
|
||||||
|
if (format == WavFormat::kWavFormatPcm) {
|
||||||
|
return kPcmWavHeaderSize;
|
||||||
|
}
|
||||||
|
RTC_CHECK_EQ(format, WavFormat::kWavFormatIeeeFloat);
|
||||||
|
return kIeeeFloatWavHeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the maximum size of the supported WAV formats.
|
||||||
|
constexpr size_t MaxWavHeaderSize() {
|
||||||
|
return std::max(WavHeaderSize(WavFormat::kWavFormatPcm),
|
||||||
|
WavHeaderSize(WavFormat::kWavFormatIeeeFloat));
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if the given parameters will make a well-formed WAV header.
|
// Return true if the given parameters will make a well-formed WAV header.
|
||||||
bool CheckWavParameters(size_t num_channels,
|
bool CheckWavParameters(size_t num_channels,
|
||||||
int sample_rate,
|
int sample_rate,
|
||||||
WavFormat format,
|
WavFormat format,
|
||||||
size_t bytes_per_sample,
|
|
||||||
size_t num_samples);
|
size_t num_samples);
|
||||||
|
|
||||||
// Write a kWavHeaderSize bytes long WAV header to buf. The payload that
|
// Write a kWavHeaderSize bytes long WAV header to buf. The payload that
|
||||||
// follows the header is supposed to have the specified number of interleaved
|
// follows the header is supposed to have the specified number of interleaved
|
||||||
// channels and contain the specified total number of samples of the specified
|
// channels and contain the specified total number of samples of the specified
|
||||||
// type. CHECKs the input parameters for validity.
|
// type. The size of the header is returned in header_size. CHECKs the input
|
||||||
void WriteWavHeader(uint8_t* buf,
|
// parameters for validity.
|
||||||
size_t num_channels,
|
void WriteWavHeader(size_t num_channels,
|
||||||
int sample_rate,
|
int sample_rate,
|
||||||
WavFormat format,
|
WavFormat format,
|
||||||
size_t bytes_per_sample,
|
size_t num_samples,
|
||||||
size_t num_samples);
|
uint8_t* buf,
|
||||||
|
size_t* header_size);
|
||||||
|
|
||||||
// Read a WAV header from an implemented ReadableWav and parse the values into
|
// Read a WAV header from an implemented WavHeaderReader and parse the values
|
||||||
// the provided output parameters. ReadableWav is used because the header can
|
// into the provided output parameters. WavHeaderReader is used because the
|
||||||
// be variably sized. Returns false if the header is invalid.
|
// header can be variably sized. Returns false if the header is invalid.
|
||||||
bool ReadWavHeader(ReadableWav* readable,
|
bool ReadWavHeader(WavHeaderReader* readable,
|
||||||
size_t* num_channels,
|
size_t* num_channels,
|
||||||
int* sample_rate,
|
int* sample_rate,
|
||||||
WavFormat* format,
|
WavFormat* format,
|
||||||
size_t* bytes_per_sample,
|
size_t* bytes_per_sample,
|
||||||
size_t* num_samples);
|
size_t* num_samples,
|
||||||
|
int64_t* data_start_pos);
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,16 @@
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
// Doesn't take ownership of the buffer.
|
// Doesn't take ownership of the buffer.
|
||||||
class ReadableWavBuffer : public ReadableWav {
|
class WavHeaderBufferReader : public WavHeaderReader {
|
||||||
public:
|
public:
|
||||||
ReadableWavBuffer(const uint8_t* buf, size_t size, bool check_read_size)
|
WavHeaderBufferReader(const uint8_t* buf, size_t size, bool check_read_size)
|
||||||
: buf_(buf),
|
: buf_(buf),
|
||||||
size_(size),
|
size_(size),
|
||||||
pos_(0),
|
pos_(0),
|
||||||
buf_exhausted_(false),
|
buf_exhausted_(false),
|
||||||
check_read_size_(check_read_size) {}
|
check_read_size_(check_read_size) {}
|
||||||
|
|
||||||
~ReadableWavBuffer() override {
|
~WavHeaderBufferReader() override {
|
||||||
// Verify the entire buffer has been read.
|
// Verify the entire buffer has been read.
|
||||||
if (check_read_size_)
|
if (check_read_size_)
|
||||||
EXPECT_EQ(size_, pos_);
|
EXPECT_EQ(size_, pos_);
|
||||||
|
@ -52,7 +52,7 @@ class ReadableWavBuffer : public ReadableWav {
|
||||||
|
|
||||||
bool SeekForward(uint32_t num_bytes) override {
|
bool SeekForward(uint32_t num_bytes) override {
|
||||||
// Verify we don't try to read outside of a properly sized header.
|
// Verify we don't try to read outside of a properly sized header.
|
||||||
if (size_ >= kWavHeaderSize)
|
if (size_ >= kPcmWavHeaderSize)
|
||||||
EXPECT_GE(size_, pos_ + num_bytes);
|
EXPECT_GE(size_, pos_ + num_bytes);
|
||||||
EXPECT_FALSE(buf_exhausted_);
|
EXPECT_FALSE(buf_exhausted_);
|
||||||
|
|
||||||
|
@ -69,6 +69,8 @@ class ReadableWavBuffer : public ReadableWav {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t GetPosition() override { return pos_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const uint8_t* buf_;
|
const uint8_t* buf_;
|
||||||
const size_t size_;
|
const size_t size_;
|
||||||
|
@ -81,34 +83,28 @@ class ReadableWavBuffer : public ReadableWav {
|
||||||
// ones are accepted and the bad ones rejected.
|
// ones are accepted and the bad ones rejected.
|
||||||
TEST(WavHeaderTest, CheckWavParameters) {
|
TEST(WavHeaderTest, CheckWavParameters) {
|
||||||
// Try some really stupid values for one parameter at a time.
|
// Try some really stupid values for one parameter at a time.
|
||||||
EXPECT_TRUE(CheckWavParameters(1, 8000, kWavFormatPcm, 1, 0));
|
EXPECT_TRUE(CheckWavParameters(1, 8000, WavFormat::kWavFormatPcm, 0));
|
||||||
EXPECT_FALSE(CheckWavParameters(0, 8000, kWavFormatPcm, 1, 0));
|
EXPECT_FALSE(CheckWavParameters(0, 8000, WavFormat::kWavFormatPcm, 0));
|
||||||
EXPECT_FALSE(CheckWavParameters(0x10000, 8000, kWavFormatPcm, 1, 0));
|
EXPECT_FALSE(CheckWavParameters(0x10000, 8000, WavFormat::kWavFormatPcm, 0));
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 0, kWavFormatPcm, 1, 0));
|
EXPECT_FALSE(CheckWavParameters(1, 0, WavFormat::kWavFormatPcm, 0));
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, WavFormat(0), 1, 0));
|
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatPcm, 0, 0));
|
|
||||||
|
|
||||||
// Try invalid format/bytes-per-sample combinations.
|
|
||||||
EXPECT_TRUE(CheckWavParameters(1, 8000, kWavFormatPcm, 2, 0));
|
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatPcm, 4, 0));
|
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatALaw, 2, 0));
|
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatMuLaw, 2, 0));
|
|
||||||
|
|
||||||
// Too large values.
|
// Too large values.
|
||||||
EXPECT_FALSE(CheckWavParameters(1 << 20, 1 << 20, kWavFormatPcm, 1, 0));
|
EXPECT_FALSE(
|
||||||
EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatPcm, 1,
|
CheckWavParameters(1 << 20, 1 << 20, WavFormat::kWavFormatPcm, 0));
|
||||||
|
EXPECT_FALSE(CheckWavParameters(1, 8000, WavFormat::kWavFormatPcm,
|
||||||
std::numeric_limits<uint32_t>::max()));
|
std::numeric_limits<uint32_t>::max()));
|
||||||
|
|
||||||
// Not the same number of samples for each channel.
|
// Not the same number of samples for each channel.
|
||||||
EXPECT_FALSE(CheckWavParameters(3, 8000, kWavFormatPcm, 1, 5));
|
EXPECT_FALSE(CheckWavParameters(3, 8000, WavFormat::kWavFormatPcm, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
size_t num_channels = 0;
|
size_t num_channels = 0;
|
||||||
int sample_rate = 0;
|
int sample_rate = 0;
|
||||||
WavFormat format = kWavFormatPcm;
|
WavFormat format = WavFormat::kWavFormatPcm;
|
||||||
size_t bytes_per_sample = 0;
|
size_t bytes_per_sample = 0;
|
||||||
size_t num_samples = 0;
|
size_t num_samples = 0;
|
||||||
|
int64_t data_start_pos = 0;
|
||||||
|
|
||||||
// Test a few ways the header can be invalid. We start with the valid header
|
// Test a few ways the header can be invalid. We start with the valid header
|
||||||
// used in WriteAndReadWavHeader, and invalidate one field per test. The
|
// used in WriteAndReadWavHeader, and invalidate one field per test. The
|
||||||
|
@ -123,7 +119,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
||||||
|
@ -133,10 +129,11 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kBadRiffID, sizeof(kBadRiffID),
|
WavHeaderBufferReader r(kBadRiffID, sizeof(kBadRiffID),
|
||||||
/*check_read_size=*/false);
|
/*check_read_size=*/false);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kBadBitsPerSample[] = {
|
constexpr uint8_t kBadBitsPerSample[] = {
|
||||||
|
@ -147,7 +144,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
||||||
|
@ -157,10 +154,11 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kBadBitsPerSample, sizeof(kBadBitsPerSample),
|
WavHeaderBufferReader r(kBadBitsPerSample, sizeof(kBadBitsPerSample),
|
||||||
/*check_read_size=*/true);
|
/*check_read_size=*/true);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kBadByteRate[] = {
|
constexpr uint8_t kBadByteRate[] = {
|
||||||
|
@ -171,7 +169,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0x00, 0x33, 0x03, 0, // byte rate: *BAD*
|
0x00, 0x33, 0x03, 0, // byte rate: *BAD*
|
||||||
|
@ -181,10 +179,11 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kBadByteRate, sizeof(kBadByteRate),
|
WavHeaderBufferReader r(kBadByteRate, sizeof(kBadByteRate),
|
||||||
/*check_read_size=*/true);
|
/*check_read_size=*/true);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kBadFmtHeaderSize[] = {
|
constexpr uint8_t kBadFmtHeaderSize[] = {
|
||||||
|
@ -195,7 +194,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
17, 0, 0, 0, // size of fmt block *BAD*. Only 16 and 18 permitted.
|
17, 0, 0, 0, // size of fmt block *BAD*. Only 16 and 18 permitted.
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
||||||
|
@ -206,10 +205,11 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kBadFmtHeaderSize, sizeof(kBadFmtHeaderSize),
|
WavHeaderBufferReader r(kBadFmtHeaderSize, sizeof(kBadFmtHeaderSize),
|
||||||
/*check_read_size=*/false);
|
/*check_read_size=*/false);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kNonZeroExtensionField[] = {
|
constexpr uint8_t kNonZeroExtensionField[] = {
|
||||||
|
@ -220,7 +220,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
18, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
18, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
||||||
|
@ -231,10 +231,12 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kNonZeroExtensionField, sizeof(kNonZeroExtensionField),
|
WavHeaderBufferReader r(kNonZeroExtensionField,
|
||||||
/*check_read_size=*/false);
|
sizeof(kNonZeroExtensionField),
|
||||||
|
/*check_read_size=*/false);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kMissingDataChunk[] = {
|
constexpr uint8_t kMissingDataChunk[] = {
|
||||||
|
@ -245,7 +247,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
||||||
|
@ -253,10 +255,11 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
8, 0, // bits per sample: 1 * 8
|
8, 0, // bits per sample: 1 * 8
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kMissingDataChunk, sizeof(kMissingDataChunk),
|
WavHeaderBufferReader r(kMissingDataChunk, sizeof(kMissingDataChunk),
|
||||||
/*check_read_size=*/true);
|
/*check_read_size=*/true);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
constexpr uint8_t kMissingFmtAndDataChunks[] = {
|
constexpr uint8_t kMissingFmtAndDataChunks[] = {
|
||||||
|
@ -267,37 +270,40 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
ReadableWavBuffer r(kMissingFmtAndDataChunks,
|
WavHeaderBufferReader r(kMissingFmtAndDataChunks,
|
||||||
sizeof(kMissingFmtAndDataChunks),
|
sizeof(kMissingFmtAndDataChunks),
|
||||||
/*check_read_size=*/true);
|
/*check_read_size=*/true);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples,
|
||||||
|
&data_start_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try writing and reading a valid WAV header and make sure it looks OK.
|
// Try writing and reading a valid WAV header and make sure it looks OK.
|
||||||
TEST(WavHeaderTest, WriteAndReadWavHeader) {
|
TEST(WavHeaderTest, WriteAndReadWavHeader) {
|
||||||
constexpr int kSize = 4 + kWavHeaderSize + 4;
|
constexpr int kSize = 4 + kPcmWavHeaderSize + 4;
|
||||||
uint8_t buf[kSize];
|
uint8_t buf[kSize];
|
||||||
|
size_t header_size;
|
||||||
memset(buf, 0xa4, sizeof(buf));
|
memset(buf, 0xa4, sizeof(buf));
|
||||||
WriteWavHeader(buf + 4, 17, 12345, kWavFormatALaw, 1, 123457689);
|
WriteWavHeader(17, 12345, WavFormat::kWavFormatPcm, 123457689, buf + 4,
|
||||||
|
&header_size);
|
||||||
constexpr uint8_t kExpectedBuf[] = {
|
constexpr uint8_t kExpectedBuf[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// clang formatting doesn't respect inline comments.
|
// clang formatting doesn't respect inline comments.
|
||||||
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header
|
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header
|
||||||
'R', 'I', 'F', 'F',
|
'R', 'I', 'F', 'F',
|
||||||
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
|
0x56, 0xa1, 0xb7, 0x0e, // size of whole file - 8: 123457689 + 44 - 8
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
|
||||||
6, 0, // format: A-law (6)
|
1, 0, // format: PCM (1)
|
||||||
17, 0, // channels: 17
|
17, 0, // channels: 17
|
||||||
0x39, 0x30, 0, 0, // sample rate: 12345
|
0x39, 0x30, 0, 0, // sample rate: 12345
|
||||||
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
|
0x92, 0x67, 0x06, 0, // byte rate: 2 * 17 * 12345
|
||||||
17, 0, // block align: NumChannels * BytesPerSample
|
34, 0, // block align: NumChannels * BytesPerSample
|
||||||
8, 0, // bits per sample: 1 * 8
|
16, 0, // bits per sample: 2 * 8
|
||||||
'd', 'a', 't', 'a',
|
'd', 'a', 't', 'a',
|
||||||
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
|
0x32, 0xa1, 0xb7, 0x0e, // size of payload: 2 * 123457689
|
||||||
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header
|
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
@ -306,17 +312,18 @@ TEST(WavHeaderTest, WriteAndReadWavHeader) {
|
||||||
|
|
||||||
size_t num_channels = 0;
|
size_t num_channels = 0;
|
||||||
int sample_rate = 0;
|
int sample_rate = 0;
|
||||||
WavFormat format = kWavFormatPcm;
|
WavFormat format = WavFormat::kWavFormatPcm;
|
||||||
size_t bytes_per_sample = 0;
|
size_t bytes_per_sample = 0;
|
||||||
size_t num_samples = 0;
|
size_t num_samples = 0;
|
||||||
ReadableWavBuffer r(buf + 4, sizeof(buf) - 8,
|
int64_t data_start_pos = 0;
|
||||||
/*check_read_size=*/true);
|
WavHeaderBufferReader r(buf + 4, sizeof(buf) - 8,
|
||||||
|
/*check_read_size=*/true);
|
||||||
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples, &data_start_pos));
|
||||||
EXPECT_EQ(17u, num_channels);
|
EXPECT_EQ(17u, num_channels);
|
||||||
EXPECT_EQ(12345, sample_rate);
|
EXPECT_EQ(12345, sample_rate);
|
||||||
EXPECT_EQ(kWavFormatALaw, format);
|
EXPECT_EQ(WavFormat::kWavFormatPcm, format);
|
||||||
EXPECT_EQ(1u, bytes_per_sample);
|
EXPECT_EQ(2u, bytes_per_sample);
|
||||||
EXPECT_EQ(123457689u, num_samples);
|
EXPECT_EQ(123457689u, num_samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +339,7 @@ TEST(WavHeaderTest, ReadAtypicalWavHeader) {
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
18, 0, 0, 0, // Size of fmt block (with an atypical extension
|
18, 0, 0, 0, // Size of fmt block (with an atypical extension
|
||||||
// size field).
|
// size field).
|
||||||
6, 0, // Format: A-law (6).
|
1, 0, // Format: PCM (1).
|
||||||
17, 0, // Channels: 17.
|
17, 0, // Channels: 17.
|
||||||
0x39, 0x30, 0, 0, // Sample rate: 12345.
|
0x39, 0x30, 0, 0, // Sample rate: 12345.
|
||||||
0xc9, 0x33, 0x03, 0, // Byte rate: 1 * 17 * 12345.
|
0xc9, 0x33, 0x03, 0, // Byte rate: 1 * 17 * 12345.
|
||||||
|
@ -346,15 +353,16 @@ TEST(WavHeaderTest, ReadAtypicalWavHeader) {
|
||||||
|
|
||||||
size_t num_channels = 0;
|
size_t num_channels = 0;
|
||||||
int sample_rate = 0;
|
int sample_rate = 0;
|
||||||
WavFormat format = kWavFormatPcm;
|
WavFormat format = WavFormat::kWavFormatPcm;
|
||||||
size_t bytes_per_sample = 0;
|
size_t bytes_per_sample = 0;
|
||||||
size_t num_samples = 0;
|
size_t num_samples = 0;
|
||||||
ReadableWavBuffer r(kBuf, sizeof(kBuf), /*check_read_size=*/true);
|
int64_t data_start_pos = 0;
|
||||||
|
WavHeaderBufferReader r(kBuf, sizeof(kBuf), /*check_read_size=*/true);
|
||||||
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples, &data_start_pos));
|
||||||
EXPECT_EQ(17u, num_channels);
|
EXPECT_EQ(17u, num_channels);
|
||||||
EXPECT_EQ(12345, sample_rate);
|
EXPECT_EQ(12345, sample_rate);
|
||||||
EXPECT_EQ(kWavFormatALaw, format);
|
EXPECT_EQ(WavFormat::kWavFormatPcm, format);
|
||||||
EXPECT_EQ(1u, bytes_per_sample);
|
EXPECT_EQ(1u, bytes_per_sample);
|
||||||
EXPECT_EQ(123457689u, num_samples);
|
EXPECT_EQ(123457689u, num_samples);
|
||||||
}
|
}
|
||||||
|
@ -372,7 +380,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithOptionalChunk) {
|
||||||
'W', 'A', 'V', 'E',
|
'W', 'A', 'V', 'E',
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // Size of fmt block.
|
16, 0, 0, 0, // Size of fmt block.
|
||||||
6, 0, // Format: A-law (6).
|
1, 0, // Format: PCM (1).
|
||||||
17, 0, // Channels: 17.
|
17, 0, // Channels: 17.
|
||||||
0x39, 0x30, 0, 0, // Sample rate: 12345.
|
0x39, 0x30, 0, 0, // Sample rate: 12345.
|
||||||
0xc9, 0x33, 0x03, 0, // Byte rate: 1 * 17 * 12345.
|
0xc9, 0x33, 0x03, 0, // Byte rate: 1 * 17 * 12345.
|
||||||
|
@ -388,15 +396,16 @@ TEST(WavHeaderTest, ReadWavHeaderWithOptionalChunk) {
|
||||||
|
|
||||||
size_t num_channels = 0;
|
size_t num_channels = 0;
|
||||||
int sample_rate = 0;
|
int sample_rate = 0;
|
||||||
WavFormat format = kWavFormatPcm;
|
WavFormat format = WavFormat::kWavFormatPcm;
|
||||||
size_t bytes_per_sample = 0;
|
size_t bytes_per_sample = 0;
|
||||||
size_t num_samples = 0;
|
size_t num_samples = 0;
|
||||||
ReadableWavBuffer r(kBuf, sizeof(kBuf), /*check_read_size=*/true);
|
int64_t data_start_pos = 0;
|
||||||
|
WavHeaderBufferReader r(kBuf, sizeof(kBuf), /*check_read_size=*/true);
|
||||||
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_TRUE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples, &data_start_pos));
|
||||||
EXPECT_EQ(17u, num_channels);
|
EXPECT_EQ(17u, num_channels);
|
||||||
EXPECT_EQ(12345, sample_rate);
|
EXPECT_EQ(12345, sample_rate);
|
||||||
EXPECT_EQ(kWavFormatALaw, format);
|
EXPECT_EQ(WavFormat::kWavFormatPcm, format);
|
||||||
EXPECT_EQ(1u, bytes_per_sample);
|
EXPECT_EQ(1u, bytes_per_sample);
|
||||||
EXPECT_EQ(123457689u, num_samples);
|
EXPECT_EQ(123457689u, num_samples);
|
||||||
}
|
}
|
||||||
|
@ -415,7 +424,7 @@ TEST(WavHeaderTest, ReadWavHeaderWithDataBeforeFormat) {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Data 16 bytes.
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Data 16 bytes.
|
||||||
'f', 'm', 't', ' ',
|
'f', 'm', 't', ' ',
|
||||||
16, 0, 0, 0, // Size of fmt block.
|
16, 0, 0, 0, // Size of fmt block.
|
||||||
6, 0, // Format: A-law (6).
|
1, 0, // Format: Pcm (1).
|
||||||
1, 0, // Channels: 1.
|
1, 0, // Channels: 1.
|
||||||
60, 0, 0, 0, // Sample rate: 60.
|
60, 0, 0, 0, // Sample rate: 60.
|
||||||
60, 0, 0, 0, // Byte rate: 1 * 1 * 60.
|
60, 0, 0, 0, // Byte rate: 1 * 1 * 60.
|
||||||
|
@ -426,12 +435,13 @@ TEST(WavHeaderTest, ReadWavHeaderWithDataBeforeFormat) {
|
||||||
|
|
||||||
size_t num_channels = 0;
|
size_t num_channels = 0;
|
||||||
int sample_rate = 0;
|
int sample_rate = 0;
|
||||||
WavFormat format = kWavFormatPcm;
|
WavFormat format = WavFormat::kWavFormatPcm;
|
||||||
size_t bytes_per_sample = 0;
|
size_t bytes_per_sample = 0;
|
||||||
size_t num_samples = 0;
|
size_t num_samples = 0;
|
||||||
ReadableWavBuffer r(kBuf, sizeof(kBuf), /*check_read_size=*/false);
|
int64_t data_start_pos = 0;
|
||||||
|
WavHeaderBufferReader r(kBuf, sizeof(kBuf), /*check_read_size=*/false);
|
||||||
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
EXPECT_FALSE(ReadWavHeader(&r, &num_channels, &sample_rate, &format,
|
||||||
&bytes_per_sample, &num_samples));
|
&bytes_per_sample, &num_samples, &data_start_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -76,12 +76,14 @@ FILE* ApmDataDumper::GetRawFile(const char* name) {
|
||||||
|
|
||||||
WavWriter* ApmDataDumper::GetWavFile(const char* name,
|
WavWriter* ApmDataDumper::GetWavFile(const char* name,
|
||||||
int sample_rate_hz,
|
int sample_rate_hz,
|
||||||
int num_channels) {
|
int num_channels,
|
||||||
|
WavFile::SampleFormat format) {
|
||||||
std::string filename = FormFileName(output_dir_, name, instance_index_,
|
std::string filename = FormFileName(output_dir_, name, instance_index_,
|
||||||
recording_set_index_, ".wav");
|
recording_set_index_, ".wav");
|
||||||
auto& f = wav_files_[filename];
|
auto& f = wav_files_[filename];
|
||||||
if (!f) {
|
if (!f) {
|
||||||
f.reset(new WavWriter(filename.c_str(), sample_rate_hz, num_channels));
|
f.reset(
|
||||||
|
new WavWriter(filename.c_str(), sample_rate_hz, num_channels, format));
|
||||||
}
|
}
|
||||||
return f.get();
|
return f.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,8 @@ class ApmDataDumper {
|
||||||
int num_channels) {
|
int num_channels) {
|
||||||
#if WEBRTC_APM_DEBUG_DUMP == 1
|
#if WEBRTC_APM_DEBUG_DUMP == 1
|
||||||
if (recording_activated_) {
|
if (recording_activated_) {
|
||||||
WavWriter* file = GetWavFile(name, sample_rate_hz, num_channels);
|
WavWriter* file = GetWavFile(name, sample_rate_hz, num_channels,
|
||||||
|
WavFile::SampleFormat::kFloat);
|
||||||
file->WriteSamples(v, v_length);
|
file->WriteSamples(v, v_length);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -271,7 +272,10 @@ class ApmDataDumper {
|
||||||
std::unordered_map<std::string, std::unique_ptr<WavWriter>> wav_files_;
|
std::unordered_map<std::string, std::unique_ptr<WavWriter>> wav_files_;
|
||||||
|
|
||||||
FILE* GetRawFile(const char* name);
|
FILE* GetRawFile(const char* name);
|
||||||
WavWriter* GetWavFile(const char* name, int sample_rate_hz, int num_channels);
|
WavWriter* GetWavFile(const char* name,
|
||||||
|
int sample_rate_hz,
|
||||||
|
int num_channels,
|
||||||
|
WavFile::SampleFormat format);
|
||||||
#endif
|
#endif
|
||||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ApmDataDumper);
|
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ApmDataDumper);
|
||||||
};
|
};
|
||||||
|
|
|
@ -348,7 +348,8 @@ void AudioProcessingSimulator::SetupOutput() {
|
||||||
|
|
||||||
std::unique_ptr<WavWriter> out_file(
|
std::unique_ptr<WavWriter> out_file(
|
||||||
new WavWriter(filename, out_config_.sample_rate_hz(),
|
new WavWriter(filename, out_config_.sample_rate_hz(),
|
||||||
static_cast<size_t>(out_config_.num_channels())));
|
static_cast<size_t>(out_config_.num_channels()),
|
||||||
|
settings_.wav_output_format));
|
||||||
buffer_file_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
|
buffer_file_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
|
||||||
} else if (settings_.aec_dump_input_string.has_value()) {
|
} else if (settings_.aec_dump_input_string.has_value()) {
|
||||||
buffer_memory_writer_ = std::make_unique<ChannelBufferVectorWriter>(
|
buffer_memory_writer_ = std::make_unique<ChannelBufferVectorWriter>(
|
||||||
|
@ -365,7 +366,8 @@ void AudioProcessingSimulator::SetupOutput() {
|
||||||
}
|
}
|
||||||
|
|
||||||
linear_aec_output_file_writer_.reset(
|
linear_aec_output_file_writer_.reset(
|
||||||
new WavWriter(filename, 16000, out_config_.num_channels()));
|
new WavWriter(filename, 16000, out_config_.num_channels(),
|
||||||
|
settings_.wav_output_format));
|
||||||
|
|
||||||
linear_aec_output_buf_.resize(out_config_.num_channels());
|
linear_aec_output_buf_.resize(out_config_.num_channels());
|
||||||
}
|
}
|
||||||
|
@ -381,7 +383,8 @@ void AudioProcessingSimulator::SetupOutput() {
|
||||||
|
|
||||||
std::unique_ptr<WavWriter> reverse_out_file(
|
std::unique_ptr<WavWriter> reverse_out_file(
|
||||||
new WavWriter(filename, reverse_out_config_.sample_rate_hz(),
|
new WavWriter(filename, reverse_out_config_.sample_rate_hz(),
|
||||||
static_cast<size_t>(reverse_out_config_.num_channels())));
|
static_cast<size_t>(reverse_out_config_.num_channels()),
|
||||||
|
settings_.wav_output_format));
|
||||||
reverse_buffer_file_writer_.reset(
|
reverse_buffer_file_writer_.reset(
|
||||||
new ChannelBufferWavWriter(std::move(reverse_out_file)));
|
new ChannelBufferWavWriter(std::move(reverse_out_file)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ struct SimulationSettings {
|
||||||
bool store_intermediate_output = false;
|
bool store_intermediate_output = false;
|
||||||
bool print_aec_parameter_values = false;
|
bool print_aec_parameter_values = false;
|
||||||
bool dump_internal_data = false;
|
bool dump_internal_data = false;
|
||||||
|
WavFile::SampleFormat wav_output_format = WavFile::SampleFormat::kInt16;
|
||||||
absl::optional<std::string> dump_internal_data_output_dir;
|
absl::optional<std::string> dump_internal_data_output_dir;
|
||||||
absl::optional<std::string> call_order_input_filename;
|
absl::optional<std::string> call_order_input_filename;
|
||||||
absl::optional<std::string> call_order_output_filename;
|
absl::optional<std::string> call_order_output_filename;
|
||||||
|
|
|
@ -255,6 +255,10 @@ ABSL_FLAG(std::string,
|
||||||
dump_data_output_dir,
|
dump_data_output_dir,
|
||||||
"",
|
"",
|
||||||
"Internal data dump output directory");
|
"Internal data dump output directory");
|
||||||
|
ABSL_FLAG(bool,
|
||||||
|
float_wav_output,
|
||||||
|
false,
|
||||||
|
"Produce floating point wav output files.");
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
@ -437,6 +441,9 @@ SimulationSettings CreateSettings() {
|
||||||
settings.dump_internal_data = absl::GetFlag(FLAGS_dump_data);
|
settings.dump_internal_data = absl::GetFlag(FLAGS_dump_data);
|
||||||
SetSettingIfSpecified(absl::GetFlag(FLAGS_dump_data_output_dir),
|
SetSettingIfSpecified(absl::GetFlag(FLAGS_dump_data_output_dir),
|
||||||
&settings.dump_internal_data_output_dir);
|
&settings.dump_internal_data_output_dir);
|
||||||
|
settings.wav_output_format = absl::GetFlag(FLAGS_float_wav_output)
|
||||||
|
? WavFile::SampleFormat::kFloat
|
||||||
|
: WavFile::SampleFormat::kInt16;
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue