Handle all permissible PCM fields with WavReader.

I discovered the hard way that Adobe Audition writes an 18 byte format
header with an extra (zero) extension size field. Although:
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
indicates this field shouldn't exist for PCM, the documentation here:
http://www-mmsp.ece.mcgill.ca/documents/AudioFormats/WAVE/WAVE.html
doesn't list it as strictly forbidden, only that it _must_ exist for
non-PCM formats.

Audition can write metadata to the file after the audio data, which is
also not forbidden. We now ensure to read only up to the audio payload
length to avoid reading the metadata.

R=aluebs@webrtc.org, kwiberg@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/33629004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7915 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
andrew@webrtc.org 2014-12-16 20:17:21 +00:00
parent 451a133f44
commit 048c5029f5
6 changed files with 334 additions and 116 deletions

View File

@ -24,18 +24,28 @@ namespace webrtc {
static const WavFormat kWavFormat = kWavFormatPcm; static const WavFormat kWavFormat = kWavFormatPcm;
static const int kBytesPerSample = 2; static const int kBytesPerSample = 2;
// Doesn't take ownership of the file handle and won't close it.
class ReadableWavFile : public ReadableWav {
public:
explicit ReadableWavFile(FILE* file) : file_(file) {}
virtual size_t Read(void* buf, size_t num_bytes) {
return fread(buf, 1, num_bytes, file_);
}
private:
FILE* file_;
};
WavReader::WavReader(const std::string& filename) WavReader::WavReader(const std::string& filename)
: file_handle_(fopen(filename.c_str(), "rb")) { : file_handle_(fopen(filename.c_str(), "rb")) {
CHECK(file_handle_); CHECK(file_handle_);
uint8_t header[kWavHeaderSize];
const size_t read =
fread(header, sizeof(*header), kWavHeaderSize, file_handle_);
CHECK_EQ(kWavHeaderSize, read);
ReadableWavFile readable(file_handle_);
WavFormat format; WavFormat format;
int bytes_per_sample; int bytes_per_sample;
CHECK(ReadWavHeader(header, &num_channels_, &sample_rate_, &format, CHECK(ReadWavHeader(&readable, &num_channels_, &sample_rate_, &format,
&bytes_per_sample, &num_samples_)); &bytes_per_sample, &num_samples_));
num_samples_remaining_ = num_samples_;
CHECK_EQ(kWavFormat, format); CHECK_EQ(kWavFormat, format);
CHECK_EQ(kBytesPerSample, bytes_per_sample); CHECK_EQ(kBytesPerSample, bytes_per_sample);
} }
@ -48,10 +58,17 @@ 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
// TODO(ajm): Import Chromium's safe_conversions.h for this.
CHECK_LE(num_samples, std::numeric_limits<uint32_t>::max());
// There could be metadata after the audio; ensure we don't read it.
num_samples = std::min(static_cast<uint32_t>(num_samples),
num_samples_remaining_);
const size_t read = const size_t read =
fread(samples, sizeof(*samples), num_samples, file_handle_); fread(samples, sizeof(*samples), num_samples, file_handle_);
// If we didn't read what was requested, ensure we've reached the EOF. // If we didn't read what was requested, ensure we've reached the EOF.
CHECK(read == num_samples || feof(file_handle_)); CHECK(read == num_samples || feof(file_handle_));
CHECK_LE(read, num_samples_remaining_);
num_samples_remaining_ -= read;
return read; return read;
} }

View File

@ -70,6 +70,7 @@ class WavReader {
int sample_rate_; int sample_rate_;
int num_channels_; int num_channels_;
uint32_t num_samples_; // Total number of samples in the file. uint32_t num_samples_; // Total number of samples in the file.
uint32_t num_samples_remaining_;
FILE* file_handle_; // Input file, owned by this class. FILE* file_handle_; // Input file, owned by this class.
}; };

View File

@ -20,20 +20,32 @@
#include "webrtc/common_audio/wav_file.h" #include "webrtc/common_audio/wav_file.h"
#include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
static const float kSamples[] = {0.0, 10.0, 4e4, -1e9}; static const float kSamples[] = {0.0, 10.0, 4e4, -1e9};
// Write a tiny WAV file with the C++ interface and verify the result. // Write a tiny WAV file with the C++ interface and verify the result.
TEST(WavWriterTest, CPP) { TEST(WavWriterTest, CPP) {
const std::string outfile = webrtc::test::OutputPath() + "wavtest1.wav"; const std::string outfile = test::OutputPath() + "wavtest1.wav";
static const uint32_t kNumSamples = 3; static const uint32_t kNumSamples = 3;
{ {
webrtc::WavWriter w(outfile, 14099, 1); WavWriter w(outfile, 14099, 1);
EXPECT_EQ(14099, w.sample_rate()); EXPECT_EQ(14099, w.sample_rate());
EXPECT_EQ(1, w.num_channels()); EXPECT_EQ(1, w.num_channels());
EXPECT_EQ(0u, w.num_samples()); EXPECT_EQ(0u, w.num_samples());
w.WriteSamples(kSamples, kNumSamples); w.WriteSamples(kSamples, kNumSamples);
EXPECT_EQ(kNumSamples, w.num_samples()); EXPECT_EQ(kNumSamples, w.num_samples());
} }
// Write some extra "metadata" to the file that should be silently ignored
// by WavReader. We don't use WavWriter directly for this because it doesn't
// support metadata.
static const uint8_t kMetadata[] = {101, 202};
{
FILE* f = fopen(outfile.c_str(), "ab");
ASSERT_TRUE(f);
ASSERT_EQ(1u, fwrite(kMetadata, sizeof(kMetadata), 1, f));
fclose(f);
}
static const uint8_t kExpectedContents[] = { static const uint8_t kExpectedContents[] = {
'R', 'I', 'F', 'F', 'R', 'I', 'F', 'F',
42, 0, 0, 0, // size of whole file - 8: 6 + 44 - 8 42, 0, 0, 0, // size of whole file - 8: 6 + 44 - 8
@ -51,11 +63,12 @@ TEST(WavWriterTest, CPP) {
0, 0, // first sample: 0.0 0, 0, // first sample: 0.0
10, 0, // second sample: 10.0 10, 0, // second sample: 10.0
0xff, 0x7f, // third sample: 4e4 (saturated) 0xff, 0x7f, // third sample: 4e4 (saturated)
kMetadata[0], kMetadata[1],
}; };
static const int kContentSize = static const int kContentSize =
webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata);
COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size); COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size);
EXPECT_EQ(size_t(kContentSize), webrtc::test::GetFileSize(outfile)); EXPECT_EQ(size_t(kContentSize), test::GetFileSize(outfile));
FILE* f = fopen(outfile.c_str(), "rb"); FILE* f = fopen(outfile.c_str(), "rb");
ASSERT_TRUE(f); ASSERT_TRUE(f);
uint8_t contents[kContentSize]; uint8_t contents[kContentSize];
@ -64,7 +77,7 @@ TEST(WavWriterTest, CPP) {
EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize));
{ {
webrtc::WavReader r(outfile); WavReader r(outfile);
EXPECT_EQ(14099, r.sample_rate()); EXPECT_EQ(14099, r.sample_rate());
EXPECT_EQ(1, r.num_channels()); EXPECT_EQ(1, r.num_channels());
EXPECT_EQ(kNumSamples, r.num_samples()); EXPECT_EQ(kNumSamples, r.num_samples());
@ -78,8 +91,8 @@ TEST(WavWriterTest, CPP) {
// Write a tiny WAV file with the C interface and verify the result. // Write a tiny WAV file with the C interface and verify the result.
TEST(WavWriterTest, C) { TEST(WavWriterTest, C) {
const std::string outfile = webrtc::test::OutputPath() + "wavtest2.wav"; const std::string outfile = test::OutputPath() + "wavtest2.wav";
rtc_WavWriter *w = rtc_WavOpen(outfile.c_str(), 11904, 2); rtc_WavWriter* w = rtc_WavOpen(outfile.c_str(), 11904, 2);
EXPECT_EQ(11904, rtc_WavSampleRate(w)); EXPECT_EQ(11904, rtc_WavSampleRate(w));
EXPECT_EQ(2, rtc_WavNumChannels(w)); EXPECT_EQ(2, rtc_WavNumChannels(w));
EXPECT_EQ(0u, rtc_WavNumSamples(w)); EXPECT_EQ(0u, rtc_WavNumSamples(w));
@ -109,9 +122,9 @@ TEST(WavWriterTest, C) {
0, 0x80, // fourth sample: -1e9 (saturated) 0, 0x80, // fourth sample: -1e9 (saturated)
}; };
static const int kContentSize = static const int kContentSize =
webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); kWavHeaderSize + kNumSamples * sizeof(int16_t);
COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size); COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size);
EXPECT_EQ(size_t(kContentSize), webrtc::test::GetFileSize(outfile)); EXPECT_EQ(size_t(kContentSize), test::GetFileSize(outfile));
FILE* f = fopen(outfile.c_str(), "rb"); FILE* f = fopen(outfile.c_str(), "rb");
ASSERT_TRUE(f); ASSERT_TRUE(f);
uint8_t contents[kContentSize]; uint8_t contents[kContentSize];
@ -122,7 +135,7 @@ TEST(WavWriterTest, C) {
// 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 = webrtc::test::OutputPath() + "wavtest3.wav"; std::string outfile = test::OutputPath() + "wavtest3.wav";
static const int kSampleRate = 8000; static const int kSampleRate = 8000;
static const int kNumChannels = 2; static const int kNumChannels = 2;
static const uint32_t kNumSamples = 3 * kSampleRate * kNumChannels; static const uint32_t kNumSamples = 3 * kSampleRate * kNumChannels;
@ -137,18 +150,18 @@ TEST(WavWriterTest, LargeFile) {
samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x; samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x;
} }
{ {
webrtc::WavWriter w(outfile, kSampleRate, kNumChannels); WavWriter w(outfile, kSampleRate, kNumChannels);
EXPECT_EQ(kSampleRate, w.sample_rate()); EXPECT_EQ(kSampleRate, w.sample_rate());
EXPECT_EQ(kNumChannels, w.num_channels()); EXPECT_EQ(kNumChannels, w.num_channels());
EXPECT_EQ(0u, w.num_samples()); EXPECT_EQ(0u, w.num_samples());
w.WriteSamples(samples, kNumSamples); w.WriteSamples(samples, kNumSamples);
EXPECT_EQ(kNumSamples, w.num_samples()); EXPECT_EQ(kNumSamples, w.num_samples());
} }
EXPECT_EQ(sizeof(int16_t) * kNumSamples + webrtc::kWavHeaderSize, EXPECT_EQ(sizeof(int16_t) * kNumSamples + kWavHeaderSize,
webrtc::test::GetFileSize(outfile)); test::GetFileSize(outfile));
{ {
webrtc::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());
@ -161,3 +174,5 @@ TEST(WavWriterTest, LargeFile) {
EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples)); EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples));
} }
} }
} // namespace webrtc

View File

@ -17,6 +17,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <limits> #include <limits>
#include <string>
#include "webrtc/base/checks.h" #include "webrtc/base/checks.h"
#include "webrtc/common_audio/include/audio_util.h" #include "webrtc/common_audio/include/audio_util.h"
@ -178,14 +179,31 @@ void WriteWavHeader(uint8_t* buf,
memcpy(buf, &header, kWavHeaderSize); memcpy(buf, &header, kWavHeaderSize);
} }
bool ReadWavHeader(const uint8_t* buf, bool ReadWavHeader(ReadableWav* readable,
int* num_channels, int* num_channels,
int* sample_rate, int* sample_rate,
WavFormat* format, WavFormat* format,
int* bytes_per_sample, int* bytes_per_sample,
uint32_t* num_samples) { uint32_t* num_samples) {
WavHeader header; WavHeader header;
memcpy(&header, buf, kWavHeaderSize); if (readable->Read(&header, kWavHeaderSize - sizeof(header.data)) !=
kWavHeaderSize - sizeof(header.data))
return false;
const uint32_t fmt_size = ReadLE32(header.fmt.header.Size);
if (fmt_size != kFmtSubchunkSize) {
// There is an optional two-byte extension field permitted to be present
// with PCM, but which must be zero.
int16_t ext_size;
if (kFmtSubchunkSize + sizeof(ext_size) != fmt_size)
return false;
if (readable->Read(&ext_size, sizeof(ext_size)) != sizeof(ext_size))
return false;
if (ext_size != 0)
return false;
}
if (readable->Read(&header.data, sizeof(header.data)) != sizeof(header.data))
return false;
// Parse needed fields. // Parse needed fields.
*format = static_cast<WavFormat>(ReadLE16(header.fmt.AudioFormat)); *format = static_cast<WavFormat>(ReadLE16(header.fmt.AudioFormat));
@ -207,9 +225,7 @@ bool ReadWavHeader(const uint8_t* buf,
if (ReadFourCC(header.data.header.ID) != "data") if (ReadFourCC(header.data.header.ID) != "data")
return false; return false;
if (ReadLE32(header.riff.header.Size) != RiffChunkSize(bytes_in_payload)) if (ReadLE32(header.riff.header.Size) < RiffChunkSize(bytes_in_payload))
return false;
if (ReadLE32(header.fmt.header.Size) != kFmtSubchunkSize)
return false; return false;
if (ReadLE32(header.fmt.ByteRate) != if (ReadLE32(header.fmt.ByteRate) !=
ByteRate(*num_channels, *sample_rate, *bytes_per_sample)) ByteRate(*num_channels, *sample_rate, *bytes_per_sample))

View File

@ -18,6 +18,13 @@ namespace webrtc {
static const size_t kWavHeaderSize = 44; static const size_t kWavHeaderSize = 44;
class ReadableWav {
public:
// Returns the number of bytes read.
size_t virtual Read(void* buf, size_t num_bytes) = 0;
virtual ~ReadableWav() {}
};
enum WavFormat { enum WavFormat {
kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample
kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law
@ -42,9 +49,10 @@ void WriteWavHeader(uint8_t* buf,
int bytes_per_sample, int bytes_per_sample,
uint32_t num_samples); uint32_t num_samples);
// Read a kWavHeaderSize bytes long WAV header from buf and parse the values // Read a WAV header from an implemented ReadableWav and parse the values into
// into the provided output parameters. Returns false if the header is invalid. // the provided output parameters. ReadableWav is used because the header can
bool ReadWavHeader(const uint8_t* buf, // be variably sized. Returns false if the header is invalid.
bool ReadWavHeader(ReadableWav* readable,
int* num_channels, int* num_channels,
int* sample_rate, int* sample_rate,
WavFormat* format, WavFormat* format,

View File

@ -14,44 +14,87 @@
#include "webrtc/common_audio/wav_header.h" #include "webrtc/common_audio/wav_header.h"
#include "webrtc/system_wrappers/interface/compile_assert.h" #include "webrtc/system_wrappers/interface/compile_assert.h"
namespace webrtc {
// Doesn't take ownership of the buffer.
class ReadableWavBuffer : public ReadableWav {
public:
ReadableWavBuffer(const uint8_t* buf, size_t size)
: buf_(buf),
size_(size),
pos_(0),
buf_exhausted_(false),
check_read_size_(true) {}
ReadableWavBuffer(const uint8_t* buf, size_t size, bool check_read_size)
: buf_(buf),
size_(size),
pos_(0),
buf_exhausted_(false),
check_read_size_(check_read_size) {}
virtual ~ReadableWavBuffer() {
// Verify the entire buffer has been read.
if (check_read_size_)
EXPECT_EQ(size_, pos_);
}
virtual size_t Read(void* buf, size_t num_bytes) {
// Verify we don't try to read outside of a properly sized header.
if (size_ >= kWavHeaderSize)
EXPECT_GE(size_, pos_ + num_bytes);
EXPECT_FALSE(buf_exhausted_);
const size_t bytes_remaining = size_ - pos_;
if (num_bytes > bytes_remaining) {
// The caller is signalled about an exhausted buffer when we return fewer
// bytes than requested. There should not be another read attempt after
// this point.
buf_exhausted_ = true;
num_bytes = bytes_remaining;
}
memcpy(buf, &buf_[pos_], num_bytes);
pos_ += num_bytes;
return num_bytes;
}
private:
const uint8_t* buf_;
const size_t size_;
size_t pos_;
bool buf_exhausted_;
const bool check_read_size_;
};
// Try various choices of WAV header parameters, and make sure that the good // Try various choices of WAV header parameters, and make sure that the good
// 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(webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 1, 0)); EXPECT_TRUE(CheckWavParameters(1, 8000, kWavFormatPcm, 1, 0));
EXPECT_FALSE( EXPECT_FALSE(CheckWavParameters(0, 8000, kWavFormatPcm, 1, 0));
webrtc::CheckWavParameters(0, 8000, webrtc::kWavFormatPcm, 1, 0)); EXPECT_FALSE(CheckWavParameters(-1, 8000, kWavFormatPcm, 1, 0));
EXPECT_FALSE( EXPECT_FALSE(CheckWavParameters(1, 0, kWavFormatPcm, 1, 0));
webrtc::CheckWavParameters(-1, 8000, webrtc::kWavFormatPcm, 1, 0)); EXPECT_FALSE(CheckWavParameters(1, 8000, WavFormat(0), 1, 0));
EXPECT_FALSE(webrtc::CheckWavParameters(1, 0, webrtc::kWavFormatPcm, 1, 0)); EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatPcm, 0, 0));
EXPECT_FALSE(webrtc::CheckWavParameters(1, 8000, webrtc::WavFormat(0), 1, 0));
EXPECT_FALSE(
webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 0, 0));
// Try invalid format/bytes-per-sample combinations. // Try invalid format/bytes-per-sample combinations.
EXPECT_TRUE(webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 2, 0)); EXPECT_TRUE(CheckWavParameters(1, 8000, kWavFormatPcm, 2, 0));
EXPECT_FALSE( EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatPcm, 4, 0));
webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 4, 0)); EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatALaw, 2, 0));
EXPECT_FALSE( EXPECT_FALSE(CheckWavParameters(1, 8000, kWavFormatMuLaw, 2, 0));
webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatALaw, 2, 0));
EXPECT_FALSE(
webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatMuLaw, 2, 0));
// Too large values. // Too large values.
EXPECT_FALSE(webrtc::CheckWavParameters( EXPECT_FALSE(CheckWavParameters(1 << 20, 1 << 20, kWavFormatPcm, 1, 0));
1 << 20, 1 << 20, webrtc::kWavFormatPcm, 1, 0)); EXPECT_FALSE(CheckWavParameters(
EXPECT_FALSE(webrtc::CheckWavParameters( 1, 8000, kWavFormatPcm, 1, std::numeric_limits<uint32_t>::max()));
1, 8000, webrtc::kWavFormatPcm, 1, 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( EXPECT_FALSE(CheckWavParameters(3, 8000, kWavFormatPcm, 1, 5));
webrtc::CheckWavParameters(3, 8000, webrtc::kWavFormatPcm, 1, 5));
} }
TEST(WavHeaderTest, ReadWavHeaderWithErrors) { TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
int num_channels = 0; int num_channels = 0;
int sample_rate = 0; int sample_rate = 0;
webrtc::WavFormat format = webrtc::kWavFormatPcm; WavFormat format = kWavFormatPcm;
int bytes_per_sample = 0; int bytes_per_sample = 0;
uint32_t num_samples = 0; uint32_t num_samples = 0;
@ -59,74 +102,153 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) {
// used in WriteAndReadWavHeader, and invalidate one field per test. The // used in WriteAndReadWavHeader, and invalidate one field per test. The
// invalid field is indicated in the array name, and in the comments with // invalid field is indicated in the array name, and in the comments with
// *BAD*. // *BAD*.
static const uint8_t kBadRiffID[] = { {
'R', 'i', 'f', 'f', // *BAD* static const uint8_t kBadRiffID[] = {
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8 'R', 'i', 'f', 'f', // *BAD*
'W', 'A', 'V', 'E', 0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
'f', 'm', 't', ' ', 'W', 'A', 'V', 'E',
16, 0, 0, 0, // size of fmt block - 8: 24 - 8 'f', 'm', 't', ' ',
6, 0, // format: A-law (6) 16, 0, 0, 0, // size of fmt block - 8: 24 - 8
17, 0, // channels: 17 6, 0, // format: A-law (6)
0x39, 0x30, 0, 0, // sample rate: 12345 17, 0, // channels: 17
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345 0x39, 0x30, 0, 0, // sample rate: 12345
17, 0, // block align: NumChannels * BytesPerSample 0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
8, 0, // bits per sample: 1 * 8 17, 0, // block align: NumChannels * BytesPerSample
'd', 'a', 't', 'a', 8, 0, // bits per sample: 1 * 8
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 'd', 'a', 't', 'a',
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
}; };
EXPECT_FALSE( ReadableWavBuffer r(kBadRiffID, sizeof(kBadRiffID));
webrtc::ReadWavHeader(kBadRiffID, &num_channels, &sample_rate, EXPECT_FALSE(
&format, &bytes_per_sample, &num_samples)); ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
static const uint8_t kBadBitsPerSample[] = { }
'R', 'I', 'F', 'F', {
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8 static const uint8_t kBadBitsPerSample[] = {
'W', 'A', 'V', 'E', 'R', 'I', 'F', 'F',
'f', 'm', 't', ' ', 0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
16, 0, 0, 0, // size of fmt block - 8: 24 - 8 'W', 'A', 'V', 'E',
6, 0, // format: A-law (6) 'f', 'm', 't', ' ',
17, 0, // channels: 17 16, 0, 0, 0, // size of fmt block - 8: 24 - 8
0x39, 0x30, 0, 0, // sample rate: 12345 6, 0, // format: A-law (6)
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345 17, 0, // channels: 17
17, 0, // block align: NumChannels * BytesPerSample 0x39, 0x30, 0, 0, // sample rate: 12345
1, 0, // bits per sample: *BAD* 0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
'd', 'a', 't', 'a', 17, 0, // block align: NumChannels * BytesPerSample
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 1, 0, // bits per sample: *BAD*
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header 'd', 'a', 't', 'a',
}; 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
EXPECT_FALSE( };
webrtc::ReadWavHeader(kBadBitsPerSample, &num_channels, &sample_rate, ReadableWavBuffer r(kBadBitsPerSample, sizeof(kBadBitsPerSample));
&format, &bytes_per_sample, &num_samples)); EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
static const uint8_t kBadByteRate[] = { &bytes_per_sample, &num_samples));
'R', 'I', 'F', 'F', }
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8 {
'W', 'A', 'V', 'E', static const uint8_t kBadByteRate[] = {
'f', 'm', 't', ' ', 'R', 'I', 'F', 'F',
16, 0, 0, 0, // size of fmt block - 8: 24 - 8 0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
6, 0, // format: A-law (6) 'W', 'A', 'V', 'E',
17, 0, // channels: 17 'f', 'm', 't', ' ',
0x39, 0x30, 0, 0, // sample rate: 12345 16, 0, 0, 0, // size of fmt block - 8: 24 - 8
0x00, 0x33, 0x03, 0, // byte rate: *BAD* 6, 0, // format: A-law (6)
17, 0, // block align: NumChannels * BytesPerSample 17, 0, // channels: 17
8, 0, // bits per sample: 1 * 8 0x39, 0x30, 0, 0, // sample rate: 12345
'd', 'a', 't', 'a', 0x00, 0x33, 0x03, 0, // byte rate: *BAD*
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 17, 0, // block align: NumChannels * BytesPerSample
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header 8, 0, // bits per sample: 1 * 8
}; 'd', 'a', 't', 'a',
EXPECT_FALSE( 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
webrtc::ReadWavHeader(kBadByteRate, &num_channels, &sample_rate, };
&format, &bytes_per_sample, &num_samples)); ReadableWavBuffer r(kBadByteRate, sizeof(kBadByteRate));
EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
}
{
static const uint8_t kBadFmtHeaderSize[] = {
'R', 'I', 'F', 'F',
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
17, 0, 0, 0, // size of fmt block *BAD*. Only 16 and 18 permitted.
6, 0, // format: A-law (6)
17, 0, // channels: 17
0x39, 0x30, 0, 0, // sample rate: 12345
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
17, 0, // block align: NumChannels * BytesPerSample
8, 0, // bits per sample: 1 * 8
0, // extra (though invalid) header byte
'd', 'a', 't', 'a',
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
};
ReadableWavBuffer r(kBadFmtHeaderSize, sizeof(kBadFmtHeaderSize), false);
EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
}
{
static const uint8_t kNonZeroExtensionField[] = {
'R', 'I', 'F', 'F',
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
18, 0, 0, 0, // size of fmt block - 8: 24 - 8
6, 0, // format: A-law (6)
17, 0, // channels: 17
0x39, 0x30, 0, 0, // sample rate: 12345
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
17, 0, // block align: NumChannels * BytesPerSample
8, 0, // bits per sample: 1 * 8
1, 0, // non-zero extension field *BAD*
'd', 'a', 't', 'a',
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
};
ReadableWavBuffer r(kNonZeroExtensionField, sizeof(kNonZeroExtensionField),
false);
EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
}
{
static const uint8_t kMissingDataChunk[] = {
'R', 'I', 'F', 'F',
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
16, 0, 0, 0, // size of fmt block - 8: 24 - 8
6, 0, // format: A-law (6)
17, 0, // channels: 17
0x39, 0x30, 0, 0, // sample rate: 12345
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
17, 0, // block align: NumChannels * BytesPerSample
8, 0, // bits per sample: 1 * 8
};
ReadableWavBuffer r(kMissingDataChunk, sizeof(kMissingDataChunk));
EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
}
{
static const uint8_t kMissingFmtAndDataChunks[] = {
'R', 'I', 'F', 'F',
0xbd, 0xd0, 0x5b, 0x07, // size of whole file - 8: 123457689 + 44 - 8
'W', 'A', 'V', 'E',
};
ReadableWavBuffer r(kMissingFmtAndDataChunks,
sizeof(kMissingFmtAndDataChunks));
EXPECT_FALSE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
}
} }
// 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) {
static const int kSize = 4 + webrtc::kWavHeaderSize + 4; static const int kSize = 4 + kWavHeaderSize + 4;
uint8_t buf[kSize]; uint8_t buf[kSize];
memset(buf, 0xa4, sizeof(buf)); memset(buf, 0xa4, sizeof(buf));
webrtc::WriteWavHeader( WriteWavHeader(buf + 4, 17, 12345, kWavFormatALaw, 1, 123457689);
buf + 4, 17, 12345, webrtc::kWavFormatALaw, 1, 123457689);
static const uint8_t kExpectedBuf[] = { static const uint8_t kExpectedBuf[] = {
0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header
'R', 'I', 'F', 'F', 'R', 'I', 'F', 'F',
@ -149,15 +271,54 @@ TEST(WavHeaderTest, WriteAndReadWavHeader) {
int num_channels = 0; int num_channels = 0;
int sample_rate = 0; int sample_rate = 0;
webrtc::WavFormat format = webrtc::kWavFormatPcm; WavFormat format = kWavFormatPcm;
int bytes_per_sample = 0; int bytes_per_sample = 0;
uint32_t num_samples = 0; uint32_t num_samples = 0;
ReadableWavBuffer r(buf + 4, sizeof(buf) - 8);
EXPECT_TRUE( EXPECT_TRUE(
webrtc::ReadWavHeader(buf + 4, &num_channels, &sample_rate, &format, ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples)); &bytes_per_sample, &num_samples));
EXPECT_EQ(17, num_channels); EXPECT_EQ(17, num_channels);
EXPECT_EQ(12345, sample_rate); EXPECT_EQ(12345, sample_rate);
EXPECT_EQ(webrtc::kWavFormatALaw, format); EXPECT_EQ(kWavFormatALaw, format);
EXPECT_EQ(1, bytes_per_sample); EXPECT_EQ(1, bytes_per_sample);
EXPECT_EQ(123457689u, num_samples); EXPECT_EQ(123457689u, num_samples);
} }
// Try reading an atypical but valid WAV header and make sure it's parsed OK.
TEST(WavHeaderTest, ReadAtypicalWavHeader) {
static const uint8_t kBuf[] = {
'R', 'I', 'F', 'F',
0x3d, 0xd1, 0x5b, 0x07, // size of whole file - 8 + an extra 128 bytes of
// "metadata": 123457689 + 44 - 8 + 128. (atypical)
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ',
18, 0, 0, 0, // size of fmt block (with an atypical extension size field)
6, 0, // format: A-law (6)
17, 0, // channels: 17
0x39, 0x30, 0, 0, // sample rate: 12345
0xc9, 0x33, 0x03, 0, // byte rate: 1 * 17 * 12345
17, 0, // block align: NumChannels * BytesPerSample
8, 0, // bits per sample: 1 * 8
0, 0, // zero extension size field (atypical)
'd', 'a', 't', 'a',
0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689
};
int num_channels = 0;
int sample_rate = 0;
WavFormat format = kWavFormatPcm;
int bytes_per_sample = 0;
uint32_t num_samples = 0;
ReadableWavBuffer r(kBuf, sizeof(kBuf));
EXPECT_TRUE(
ReadWavHeader(&r, &num_channels, &sample_rate, &format,
&bytes_per_sample, &num_samples));
EXPECT_EQ(17, num_channels);
EXPECT_EQ(12345, sample_rate);
EXPECT_EQ(kWavFormatALaw, format);
EXPECT_EQ(1, bytes_per_sample);
EXPECT_EQ(123457689u, num_samples);
}
} // namespace webrtc