diff --git a/webrtc/common_audio/wav_file.cc b/webrtc/common_audio/wav_file.cc index 880e1ec428..21e7ff1f6e 100644 --- a/webrtc/common_audio/wav_file.cc +++ b/webrtc/common_audio/wav_file.cc @@ -24,18 +24,28 @@ namespace webrtc { static const WavFormat kWavFormat = kWavFormatPcm; 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) : file_handle_(fopen(filename.c_str(), "rb")) { 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; 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_)); + num_samples_remaining_ = num_samples_; CHECK_EQ(kWavFormat, format); 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 #error "Need to convert samples to big-endian when reading from WAV file" #endif + // TODO(ajm): Import Chromium's safe_conversions.h for this. + CHECK_LE(num_samples, std::numeric_limits::max()); + // There could be metadata after the audio; ensure we don't read it. + num_samples = std::min(static_cast(num_samples), + num_samples_remaining_); const size_t read = fread(samples, sizeof(*samples), num_samples, file_handle_); // If we didn't read what was requested, ensure we've reached the EOF. CHECK(read == num_samples || feof(file_handle_)); + CHECK_LE(read, num_samples_remaining_); + num_samples_remaining_ -= read; return read; } diff --git a/webrtc/common_audio/wav_file.h b/webrtc/common_audio/wav_file.h index c6c5d6b7ed..1fbf9541fa 100644 --- a/webrtc/common_audio/wav_file.h +++ b/webrtc/common_audio/wav_file.h @@ -70,6 +70,7 @@ class WavReader { int sample_rate_; int num_channels_; 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. }; diff --git a/webrtc/common_audio/wav_file_unittest.cc b/webrtc/common_audio/wav_file_unittest.cc index 1bdb655d62..1991c3b59b 100644 --- a/webrtc/common_audio/wav_file_unittest.cc +++ b/webrtc/common_audio/wav_file_unittest.cc @@ -20,20 +20,32 @@ #include "webrtc/common_audio/wav_file.h" #include "webrtc/test/testsupport/fileutils.h" +namespace webrtc { + static const float kSamples[] = {0.0, 10.0, 4e4, -1e9}; // Write a tiny WAV file with the C++ interface and verify the result. 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; { - webrtc::WavWriter w(outfile, 14099, 1); + WavWriter w(outfile, 14099, 1); EXPECT_EQ(14099, w.sample_rate()); EXPECT_EQ(1, w.num_channels()); EXPECT_EQ(0u, w.num_samples()); w.WriteSamples(kSamples, kNumSamples); 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[] = { 'R', 'I', 'F', 'F', 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 10, 0, // second sample: 10.0 0xff, 0x7f, // third sample: 4e4 (saturated) + kMetadata[0], kMetadata[1], }; static const int kContentSize = - webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); + kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata); 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"); ASSERT_TRUE(f); uint8_t contents[kContentSize]; @@ -64,7 +77,7 @@ TEST(WavWriterTest, CPP) { EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); { - webrtc::WavReader r(outfile); + WavReader r(outfile); EXPECT_EQ(14099, r.sample_rate()); EXPECT_EQ(1, r.num_channels()); 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. TEST(WavWriterTest, C) { - const std::string outfile = webrtc::test::OutputPath() + "wavtest2.wav"; - rtc_WavWriter *w = rtc_WavOpen(outfile.c_str(), 11904, 2); + const std::string outfile = test::OutputPath() + "wavtest2.wav"; + rtc_WavWriter* w = rtc_WavOpen(outfile.c_str(), 11904, 2); EXPECT_EQ(11904, rtc_WavSampleRate(w)); EXPECT_EQ(2, rtc_WavNumChannels(w)); EXPECT_EQ(0u, rtc_WavNumSamples(w)); @@ -109,9 +122,9 @@ TEST(WavWriterTest, C) { 0, 0x80, // fourth sample: -1e9 (saturated) }; static const int kContentSize = - webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); + kWavHeaderSize + kNumSamples * sizeof(int16_t); 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"); ASSERT_TRUE(f); 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. 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 kNumChannels = 2; 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; } { - webrtc::WavWriter w(outfile, kSampleRate, kNumChannels); + WavWriter w(outfile, kSampleRate, kNumChannels); EXPECT_EQ(kSampleRate, w.sample_rate()); EXPECT_EQ(kNumChannels, w.num_channels()); EXPECT_EQ(0u, w.num_samples()); w.WriteSamples(samples, kNumSamples); EXPECT_EQ(kNumSamples, w.num_samples()); } - EXPECT_EQ(sizeof(int16_t) * kNumSamples + webrtc::kWavHeaderSize, - webrtc::test::GetFileSize(outfile)); + EXPECT_EQ(sizeof(int16_t) * kNumSamples + kWavHeaderSize, + test::GetFileSize(outfile)); { - webrtc::WavReader r(outfile); + WavReader r(outfile); EXPECT_EQ(kSampleRate, r.sample_rate()); EXPECT_EQ(kNumChannels, r.num_channels()); EXPECT_EQ(kNumSamples, r.num_samples()); @@ -161,3 +174,5 @@ TEST(WavWriterTest, LargeFile) { EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples)); } } + +} // namespace webrtc diff --git a/webrtc/common_audio/wav_header.cc b/webrtc/common_audio/wav_header.cc index 8c781fb4cf..9776bc4d1d 100644 --- a/webrtc/common_audio/wav_header.cc +++ b/webrtc/common_audio/wav_header.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include "webrtc/base/checks.h" #include "webrtc/common_audio/include/audio_util.h" @@ -178,14 +179,31 @@ void WriteWavHeader(uint8_t* buf, memcpy(buf, &header, kWavHeaderSize); } -bool ReadWavHeader(const uint8_t* buf, +bool ReadWavHeader(ReadableWav* readable, int* num_channels, int* sample_rate, WavFormat* format, int* bytes_per_sample, uint32_t* num_samples) { 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. *format = static_cast(ReadLE16(header.fmt.AudioFormat)); @@ -207,9 +225,7 @@ bool ReadWavHeader(const uint8_t* buf, if (ReadFourCC(header.data.header.ID) != "data") return false; - if (ReadLE32(header.riff.header.Size) != RiffChunkSize(bytes_in_payload)) - return false; - if (ReadLE32(header.fmt.header.Size) != kFmtSubchunkSize) + if (ReadLE32(header.riff.header.Size) < RiffChunkSize(bytes_in_payload)) return false; if (ReadLE32(header.fmt.ByteRate) != ByteRate(*num_channels, *sample_rate, *bytes_per_sample)) diff --git a/webrtc/common_audio/wav_header.h b/webrtc/common_audio/wav_header.h index 37f78a6fe9..1a0fd7c81d 100644 --- a/webrtc/common_audio/wav_header.h +++ b/webrtc/common_audio/wav_header.h @@ -18,6 +18,13 @@ namespace webrtc { 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 { kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law @@ -42,9 +49,10 @@ void WriteWavHeader(uint8_t* buf, int bytes_per_sample, uint32_t num_samples); -// Read a kWavHeaderSize bytes long WAV header from buf and parse the values -// into the provided output parameters. Returns false if the header is invalid. -bool ReadWavHeader(const uint8_t* buf, +// Read a WAV header from an implemented ReadableWav and parse the values into +// the provided output parameters. ReadableWav is used because the header can +// be variably sized. Returns false if the header is invalid. +bool ReadWavHeader(ReadableWav* readable, int* num_channels, int* sample_rate, WavFormat* format, diff --git a/webrtc/common_audio/wav_header_unittest.cc b/webrtc/common_audio/wav_header_unittest.cc index 677affa528..63ede83834 100644 --- a/webrtc/common_audio/wav_header_unittest.cc +++ b/webrtc/common_audio/wav_header_unittest.cc @@ -14,44 +14,87 @@ #include "webrtc/common_audio/wav_header.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 // ones are accepted and the bad ones rejected. TEST(WavHeaderTest, CheckWavParameters) { // Try some really stupid values for one parameter at a time. - EXPECT_TRUE(webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 1, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(0, 8000, webrtc::kWavFormatPcm, 1, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(-1, 8000, webrtc::kWavFormatPcm, 1, 0)); - EXPECT_FALSE(webrtc::CheckWavParameters(1, 0, webrtc::kWavFormatPcm, 1, 0)); - EXPECT_FALSE(webrtc::CheckWavParameters(1, 8000, webrtc::WavFormat(0), 1, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 0, 0)); + EXPECT_TRUE(CheckWavParameters(1, 8000, kWavFormatPcm, 1, 0)); + EXPECT_FALSE(CheckWavParameters(0, 8000, kWavFormatPcm, 1, 0)); + EXPECT_FALSE(CheckWavParameters(-1, 8000, kWavFormatPcm, 1, 0)); + EXPECT_FALSE(CheckWavParameters(1, 0, kWavFormatPcm, 1, 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(webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 2, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatPcm, 4, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatALaw, 2, 0)); - EXPECT_FALSE( - webrtc::CheckWavParameters(1, 8000, webrtc::kWavFormatMuLaw, 2, 0)); + 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. - EXPECT_FALSE(webrtc::CheckWavParameters( - 1 << 20, 1 << 20, webrtc::kWavFormatPcm, 1, 0)); - EXPECT_FALSE(webrtc::CheckWavParameters( - 1, 8000, webrtc::kWavFormatPcm, 1, std::numeric_limits::max())); + EXPECT_FALSE(CheckWavParameters(1 << 20, 1 << 20, kWavFormatPcm, 1, 0)); + EXPECT_FALSE(CheckWavParameters( + 1, 8000, kWavFormatPcm, 1, std::numeric_limits::max())); // Not the same number of samples for each channel. - EXPECT_FALSE( - webrtc::CheckWavParameters(3, 8000, webrtc::kWavFormatPcm, 1, 5)); + EXPECT_FALSE(CheckWavParameters(3, 8000, kWavFormatPcm, 1, 5)); } TEST(WavHeaderTest, ReadWavHeaderWithErrors) { int num_channels = 0; int sample_rate = 0; - webrtc::WavFormat format = webrtc::kWavFormatPcm; + WavFormat format = kWavFormatPcm; int bytes_per_sample = 0; uint32_t num_samples = 0; @@ -59,74 +102,153 @@ TEST(WavHeaderTest, ReadWavHeaderWithErrors) { // used in WriteAndReadWavHeader, and invalidate one field per test. The // invalid field is indicated in the array name, and in the comments with // *BAD*. - static const uint8_t kBadRiffID[] = { - 'R', 'i', 'f', 'f', // *BAD* - 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 - 'd', 'a', 't', 'a', - 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 - 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header - }; - EXPECT_FALSE( - webrtc::ReadWavHeader(kBadRiffID, &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 - '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 - 1, 0, // bits per sample: *BAD* - 'd', 'a', 't', 'a', - 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 - 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header - }; - EXPECT_FALSE( - webrtc::ReadWavHeader(kBadBitsPerSample, &num_channels, &sample_rate, - &format, &bytes_per_sample, &num_samples)); - - static const uint8_t kBadByteRate[] = { - '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 - 0x00, 0x33, 0x03, 0, // byte rate: *BAD* - 17, 0, // block align: NumChannels * BytesPerSample - 8, 0, // bits per sample: 1 * 8 - 'd', 'a', 't', 'a', - 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 - 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header - }; - EXPECT_FALSE( - webrtc::ReadWavHeader(kBadByteRate, &num_channels, &sample_rate, - &format, &bytes_per_sample, &num_samples)); + { + static const uint8_t kBadRiffID[] = { + 'R', 'i', 'f', 'f', // *BAD* + 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 + 'd', 'a', 't', 'a', + 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 + }; + ReadableWavBuffer r(kBadRiffID, sizeof(kBadRiffID)); + EXPECT_FALSE( + 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 + '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 + 1, 0, // bits per sample: *BAD* + 'd', 'a', 't', 'a', + 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 + }; + ReadableWavBuffer r(kBadBitsPerSample, sizeof(kBadBitsPerSample)); + EXPECT_FALSE( + ReadWavHeader(&r, &num_channels, &sample_rate, &format, + &bytes_per_sample, &num_samples)); + } + { + static const uint8_t kBadByteRate[] = { + '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 + 0x00, 0x33, 0x03, 0, // byte rate: *BAD* + 17, 0, // block align: NumChannels * BytesPerSample + 8, 0, // bits per sample: 1 * 8 + 'd', 'a', 't', 'a', + 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 + }; + 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. TEST(WavHeaderTest, WriteAndReadWavHeader) { - static const int kSize = 4 + webrtc::kWavHeaderSize + 4; + static const int kSize = 4 + kWavHeaderSize + 4; uint8_t buf[kSize]; memset(buf, 0xa4, sizeof(buf)); - webrtc::WriteWavHeader( - buf + 4, 17, 12345, webrtc::kWavFormatALaw, 1, 123457689); + WriteWavHeader(buf + 4, 17, 12345, kWavFormatALaw, 1, 123457689); static const uint8_t kExpectedBuf[] = { 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header 'R', 'I', 'F', 'F', @@ -149,15 +271,54 @@ TEST(WavHeaderTest, WriteAndReadWavHeader) { int num_channels = 0; int sample_rate = 0; - webrtc::WavFormat format = webrtc::kWavFormatPcm; + WavFormat format = kWavFormatPcm; int bytes_per_sample = 0; uint32_t num_samples = 0; + ReadableWavBuffer r(buf + 4, sizeof(buf) - 8); EXPECT_TRUE( - webrtc::ReadWavHeader(buf + 4, &num_channels, &sample_rate, &format, - &bytes_per_sample, &num_samples)); + ReadWavHeader(&r, &num_channels, &sample_rate, &format, + &bytes_per_sample, &num_samples)); EXPECT_EQ(17, num_channels); EXPECT_EQ(12345, sample_rate); - EXPECT_EQ(webrtc::kWavFormatALaw, format); + EXPECT_EQ(kWavFormatALaw, format); EXPECT_EQ(1, bytes_per_sample); 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