diff options
author | andrew@webrtc.org <andrew@webrtc.org> | 2014-10-31 21:51:03 +0000 |
---|---|---|
committer | andrew@webrtc.org <andrew@webrtc.org> | 2014-10-31 21:51:03 +0000 |
commit | 46c5634b0a617aa58e5ee12729709776cb73f26b (patch) | |
tree | ccbb26ca0460fe87258de6ddb2e14fd62eebc02f /common_audio | |
parent | 796056b8fff85d6e85e3717d3598684fc9e893f0 (diff) | |
download | webrtc-46c5634b0a617aa58e5ee12729709776cb73f26b.tar.gz |
Add a WavReader counterpart to WavWriter.
Don't bother with a C interface as we currently have no need to call
this from C code. The first use will be in the audioproc tool.
R=kwiberg@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/30829004
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@7585 4adac7df-926f-26a2-2b94-8c16560cd09d
Diffstat (limited to 'common_audio')
-rw-r--r-- | common_audio/BUILD.gn | 4 | ||||
-rw-r--r-- | common_audio/common_audio.gyp | 6 | ||||
-rw-r--r-- | common_audio/wav_file.cc | 166 | ||||
-rw-r--r-- | common_audio/wav_file.h | 98 | ||||
-rw-r--r-- | common_audio/wav_file_unittest.cc (renamed from common_audio/wav_writer_unittest.cc) | 34 | ||||
-rw-r--r-- | common_audio/wav_header.cc | 134 | ||||
-rw-r--r-- | common_audio/wav_header.h | 16 | ||||
-rw-r--r-- | common_audio/wav_header_unittest.cc | 92 | ||||
-rw-r--r-- | common_audio/wav_writer.cc | 115 | ||||
-rw-r--r-- | common_audio/wav_writer.h | 72 |
10 files changed, 506 insertions, 231 deletions
diff --git a/common_audio/BUILD.gn b/common_audio/BUILD.gn index 1338a756..ba1d1795 100644 --- a/common_audio/BUILD.gn +++ b/common_audio/BUILD.gn @@ -85,8 +85,8 @@ source_set("common_audio") { "vad/vad_sp.h", "wav_header.cc", "wav_header.h", - "wav_writer.cc", - "wav_writer.h", + "wav_file.cc", + "wav_file.h", "window_generator.cc", "window_generator.h", ] diff --git a/common_audio/common_audio.gyp b/common_audio/common_audio.gyp index ca77ba8f..8f96674f 100644 --- a/common_audio/common_audio.gyp +++ b/common_audio/common_audio.gyp @@ -99,8 +99,8 @@ 'vad/vad_sp.h', 'wav_header.cc', 'wav_header.h', - 'wav_writer.cc', - 'wav_writer.h', + 'wav_file.cc', + 'wav_file.h', 'window_generator.cc', 'window_generator.h', ], @@ -245,7 +245,7 @@ 'vad/vad_unittest.cc', 'vad/vad_unittest.h', 'wav_header_unittest.cc', - 'wav_writer_unittest.cc', + 'wav_file_unittest.cc', 'window_generator_unittest.cc', ], 'conditions': [ diff --git a/common_audio/wav_file.cc b/common_audio/wav_file.cc new file mode 100644 index 00000000..685f3537 --- /dev/null +++ b/common_audio/wav_file.cc @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/common_audio/wav_file.h" + +#include <algorithm> +#include <cstdio> +#include <limits> + +#include "webrtc/base/checks.h" +#include "webrtc/common_audio/include/audio_util.h" +#include "webrtc/common_audio/wav_header.h" + +namespace webrtc { + +// We write 16-bit PCM WAV files. +static const WavFormat kWavFormat = kWavFormatPcm; +static const int kBytesPerSample = 2; + +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); + + WavFormat format; + int bytes_per_sample; + CHECK(ReadWavHeader(header, &num_channels_, &sample_rate_, &format, + &bytes_per_sample, &num_samples_)); + CHECK_EQ(kWavFormat, format); + CHECK_EQ(kBytesPerSample, bytes_per_sample); +} + +WavReader::~WavReader() { + Close(); +} + +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 + 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_)); + return read; +} + +size_t WavReader::ReadSamples(size_t num_samples, float* samples) { + static const size_t kChunksize = 4096 / sizeof(uint16_t); + size_t read = 0; + for (size_t i = 0; i < num_samples; i += kChunksize) { + int16_t isamples[kChunksize]; + size_t chunk = std::min(kChunksize, num_samples - i); + chunk = ReadSamples(chunk, isamples); + for (size_t j = 0; j < chunk; ++j) + samples[i + j] = isamples[j]; + read += chunk; + } + return read; +} + +void WavReader::Close() { + CHECK_EQ(0, fclose(file_handle_)); + file_handle_ = NULL; +} + +WavWriter::WavWriter(const std::string& filename, int sample_rate, + int num_channels) + : sample_rate_(sample_rate), + num_channels_(num_channels), + num_samples_(0), + file_handle_(fopen(filename.c_str(), "wb")) { + CHECK(file_handle_); + CHECK(CheckWavParameters(num_channels_, + sample_rate_, + kWavFormat, + kBytesPerSample, + num_samples_)); + + // Write a blank placeholder header, since we need to know the total number + // of samples before we can fill in the real data. + static const uint8_t blank_header[kWavHeaderSize] = {0}; + CHECK_EQ(1u, fwrite(blank_header, kWavHeaderSize, 1, file_handle_)); +} + +WavWriter::~WavWriter() { + Close(); +} + +void WavWriter::WriteSamples(const int16_t* samples, size_t num_samples) { +#ifndef WEBRTC_ARCH_LITTLE_ENDIAN +#error "Need to convert samples to little-endian when writing to WAV file" +#endif + const size_t written = + fwrite(samples, sizeof(*samples), num_samples, file_handle_); + CHECK_EQ(num_samples, written); + num_samples_ += static_cast<uint32_t>(written); + CHECK(written <= std::numeric_limits<uint32_t>::max() || + num_samples_ >= written); // detect uint32_t overflow + CHECK(CheckWavParameters(num_channels_, + sample_rate_, + kWavFormat, + kBytesPerSample, + num_samples_)); +} + +void WavWriter::WriteSamples(const float* samples, size_t num_samples) { + static const size_t kChunksize = 4096 / sizeof(uint16_t); + for (size_t i = 0; i < num_samples; i += kChunksize) { + int16_t isamples[kChunksize]; + const size_t chunk = std::min(kChunksize, num_samples - i); + FloatS16ToS16(samples + i, chunk, isamples); + WriteSamples(isamples, chunk); + } +} + +void WavWriter::Close() { + CHECK_EQ(0, fseek(file_handle_, 0, SEEK_SET)); + uint8_t header[kWavHeaderSize]; + CHECK(WriteWavHeader(header, num_channels_, sample_rate_, kWavFormat, + kBytesPerSample, num_samples_)); + CHECK_EQ(1u, fwrite(header, kWavHeaderSize, 1, file_handle_)); + CHECK_EQ(0, fclose(file_handle_)); + file_handle_ = NULL; +} + +} // namespace webrtc + +rtc_WavWriter* rtc_WavOpen(const char* filename, + int sample_rate, + int num_channels) { + return reinterpret_cast<rtc_WavWriter*>( + new webrtc::WavWriter(filename, sample_rate, num_channels)); +} + +void rtc_WavClose(rtc_WavWriter* wf) { + delete reinterpret_cast<webrtc::WavWriter*>(wf); +} + +void rtc_WavWriteSamples(rtc_WavWriter* wf, + const float* samples, + size_t num_samples) { + reinterpret_cast<webrtc::WavWriter*>(wf)->WriteSamples(samples, num_samples); +} + +int rtc_WavSampleRate(const rtc_WavWriter* wf) { + return reinterpret_cast<const webrtc::WavWriter*>(wf)->sample_rate(); +} + +int rtc_WavNumChannels(const rtc_WavWriter* wf) { + return reinterpret_cast<const webrtc::WavWriter*>(wf)->num_channels(); +} + +uint32_t rtc_WavNumSamples(const rtc_WavWriter* wf) { + return reinterpret_cast<const webrtc::WavWriter*>(wf)->num_samples(); +} diff --git a/common_audio/wav_file.h b/common_audio/wav_file.h new file mode 100644 index 00000000..c6c5d6b7 --- /dev/null +++ b/common_audio/wav_file.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_COMMON_AUDIO_WAV_FILE_H_ +#define WEBRTC_COMMON_AUDIO_WAV_FILE_H_ + +#ifdef __cplusplus + +#include <stdint.h> +#include <cstddef> +#include <string> + +namespace webrtc { + +// Simple C++ class for writing 16-bit PCM WAV files. All error handling is +// by calls to CHECK(), making it unsuitable for anything but debug code. +class WavWriter { + public: + // Open a new WAV file for writing. + WavWriter(const std::string& filename, int sample_rate, int num_channels); + + // Close the WAV file, after writing its header. + ~WavWriter(); + + // Write additional samples to the file. Each sample is in the range + // [-32768,32767], and there must be the previously specified number of + // interleaved channels. + void WriteSamples(const float* samples, size_t num_samples); + void WriteSamples(const int16_t* samples, size_t num_samples); + + int sample_rate() const { return sample_rate_; } + int num_channels() const { return num_channels_; } + uint32_t num_samples() const { return num_samples_; } + + private: + void Close(); + const int sample_rate_; + const int num_channels_; + uint32_t num_samples_; // Total number of samples written to file. + FILE* file_handle_; // Output file, owned by this class +}; + +// Follows the conventions of WavWriter. +class WavReader { + public: + // Opens an existing WAV file for reading. + explicit WavReader(const std::string& filename); + + // Close the WAV file. + ~WavReader(); + + // Returns the number of samples read. If this is less than requested, + // verifies that the end of the file was reached. + size_t ReadSamples(size_t num_samples, float* samples); + size_t ReadSamples(size_t num_samples, int16_t* samples); + + int sample_rate() const { return sample_rate_; } + int num_channels() const { return num_channels_; } + uint32_t num_samples() const { return num_samples_; } + + private: + void Close(); + int sample_rate_; + int num_channels_; + uint32_t num_samples_; // Total number of samples in the file. + FILE* file_handle_; // Input file, owned by this class. +}; + +} // namespace webrtc + +extern "C" { +#endif // __cplusplus + +// C wrappers for the WavWriter class. +typedef struct rtc_WavWriter rtc_WavWriter; +rtc_WavWriter* rtc_WavOpen(const char* filename, + int sample_rate, + int num_channels); +void rtc_WavClose(rtc_WavWriter* wf); +void rtc_WavWriteSamples(rtc_WavWriter* wf, + const float* samples, + size_t num_samples); +int rtc_WavSampleRate(const rtc_WavWriter* wf); +int rtc_WavNumChannels(const rtc_WavWriter* wf); +uint32_t rtc_WavNumSamples(const rtc_WavWriter* wf); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBRTC_COMMON_AUDIO_WAV_FILE_H_ diff --git a/common_audio/wav_writer_unittest.cc b/common_audio/wav_file_unittest.cc index 9c593be6..1bdb655d 100644 --- a/common_audio/wav_writer_unittest.cc +++ b/common_audio/wav_file_unittest.cc @@ -17,7 +17,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/base/compile_assert.h" #include "webrtc/common_audio/wav_header.h" -#include "webrtc/common_audio/wav_writer.h" +#include "webrtc/common_audio/wav_file.h" #include "webrtc/test/testsupport/fileutils.h" static const float kSamples[] = {0.0, 10.0, 4e4, -1e9}; @@ -27,7 +27,7 @@ TEST(WavWriterTest, CPP) { const std::string outfile = webrtc::test::OutputPath() + "wavtest1.wav"; static const uint32_t kNumSamples = 3; { - webrtc::WavFile w(outfile, 14099, 1); + webrtc::WavWriter w(outfile, 14099, 1); EXPECT_EQ(14099, w.sample_rate()); EXPECT_EQ(1, w.num_channels()); EXPECT_EQ(0u, w.num_samples()); @@ -62,12 +62,24 @@ TEST(WavWriterTest, CPP) { ASSERT_EQ(1u, fread(contents, kContentSize, 1, f)); EXPECT_EQ(0, fclose(f)); EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); + + { + webrtc::WavReader r(outfile); + EXPECT_EQ(14099, r.sample_rate()); + EXPECT_EQ(1, r.num_channels()); + EXPECT_EQ(kNumSamples, r.num_samples()); + static const float kTruncatedSamples[] = {0.0, 10.0, 32767.0}; + float samples[kNumSamples]; + EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, samples)); + EXPECT_EQ(0, memcmp(kTruncatedSamples, samples, sizeof(samples))); + EXPECT_EQ(0u, r.ReadSamples(kNumSamples, samples)); + } } // 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_WavFile *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(2, rtc_WavNumChannels(w)); EXPECT_EQ(0u, rtc_WavNumSamples(w)); @@ -125,7 +137,7 @@ TEST(WavWriterTest, LargeFile) { samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x; } { - webrtc::WavFile w(outfile, kSampleRate, kNumChannels); + webrtc::WavWriter w(outfile, kSampleRate, kNumChannels); EXPECT_EQ(kSampleRate, w.sample_rate()); EXPECT_EQ(kNumChannels, w.num_channels()); EXPECT_EQ(0u, w.num_samples()); @@ -134,4 +146,18 @@ TEST(WavWriterTest, LargeFile) { } EXPECT_EQ(sizeof(int16_t) * kNumSamples + webrtc::kWavHeaderSize, webrtc::test::GetFileSize(outfile)); + + { + webrtc::WavReader r(outfile); + EXPECT_EQ(kSampleRate, r.sample_rate()); + EXPECT_EQ(kNumChannels, r.num_channels()); + EXPECT_EQ(kNumSamples, r.num_samples()); + + float read_samples[kNumSamples]; + EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, read_samples)); + for (size_t i = 0; i < kNumSamples; ++i) + EXPECT_NEAR(samples[i], read_samples[i], 1); + + EXPECT_EQ(0u, r.ReadSamples(kNumSamples, read_samples)); + } } diff --git a/common_audio/wav_header.cc b/common_audio/wav_header.cc index ce43896f..3182b1ff 100644 --- a/common_audio/wav_header.cc +++ b/common_audio/wav_header.cc @@ -18,9 +18,11 @@ #include <cstring> #include <limits> +#include "webrtc/base/checks.h" #include "webrtc/common_audio/include/audio_util.h" namespace webrtc { +namespace { struct ChunkHeader { uint32_t ID; @@ -28,6 +30,34 @@ struct ChunkHeader { }; COMPILE_ASSERT(sizeof(ChunkHeader) == 8, chunk_header_size); +// We can't nest this definition in WavHeader, because VS2013 gives an error +// on sizeof(WavHeader::fmt): "error C2070: 'unknown': illegal sizeof operand". +struct FmtSubchunk { + ChunkHeader header; + uint16_t AudioFormat; + uint16_t NumChannels; + uint32_t SampleRate; + uint32_t ByteRate; + uint16_t BlockAlign; + uint16_t BitsPerSample; +}; +COMPILE_ASSERT(sizeof(FmtSubchunk) == 24, fmt_subchunk_size); +const uint32_t kFmtSubchunkSize = sizeof(FmtSubchunk) - sizeof(ChunkHeader); + +struct WavHeader { + struct { + ChunkHeader header; + uint32_t Format; + } riff; + FmtSubchunk fmt; + struct { + ChunkHeader header; + } data; +}; +COMPILE_ASSERT(sizeof(WavHeader) == kWavHeaderSize, no_padding_in_header); + +} // namespace + bool CheckWavParameters(int num_channels, int sample_rate, WavFormat format, @@ -91,54 +121,54 @@ static inline void WriteFourCC(uint32_t* f, char a, char b, char c, char d) { | static_cast<uint32_t>(c) << 16 | static_cast<uint32_t>(d) << 24; } + +static inline uint16_t ReadLE16(uint16_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); +} #else #error "Write be-to-le conversion functions" #endif -void WriteWavHeader(uint8_t* buf, +static inline uint32_t RiffChunkSize(uint32_t bytes_in_payload) { + return bytes_in_payload + kWavHeaderSize - sizeof(ChunkHeader); +} + +static inline uint32_t ByteRate(int num_channels, int sample_rate, + int bytes_per_sample) { + return static_cast<uint32_t>(num_channels) * sample_rate * bytes_per_sample; +} + +static inline uint16_t BlockAlign(int num_channels, int bytes_per_sample) { + return num_channels * bytes_per_sample; +} + +bool WriteWavHeader(uint8_t* buf, int num_channels, int sample_rate, WavFormat format, int bytes_per_sample, uint32_t num_samples) { - assert(CheckWavParameters(num_channels, sample_rate, format, - bytes_per_sample, num_samples)); - - struct { - struct { - ChunkHeader header; - uint32_t Format; - } riff; - struct { - ChunkHeader header; - uint16_t AudioFormat; - uint16_t NumChannels; - uint32_t SampleRate; - uint32_t ByteRate; - uint16_t BlockAlign; - uint16_t BitsPerSample; - } fmt; - struct { - ChunkHeader header; - } data; - } header; - COMPILE_ASSERT(sizeof(header) == kWavHeaderSize, no_padding_in_header); + if (!CheckWavParameters(num_channels, sample_rate, format, + bytes_per_sample, num_samples)) + return false; + WavHeader header; const uint32_t bytes_in_payload = bytes_per_sample * num_samples; WriteFourCC(&header.riff.header.ID, 'R', 'I', 'F', 'F'); - WriteLE32(&header.riff.header.Size, - bytes_in_payload + kWavHeaderSize - sizeof(ChunkHeader)); + WriteLE32(&header.riff.header.Size, RiffChunkSize(bytes_in_payload)); WriteFourCC(&header.riff.Format, 'W', 'A', 'V', 'E'); WriteFourCC(&header.fmt.header.ID, 'f', 'm', 't', ' '); - WriteLE32(&header.fmt.header.Size, sizeof(header.fmt) - sizeof(ChunkHeader)); + WriteLE32(&header.fmt.header.Size, kFmtSubchunkSize); WriteLE16(&header.fmt.AudioFormat, format); WriteLE16(&header.fmt.NumChannels, num_channels); WriteLE32(&header.fmt.SampleRate, sample_rate); - WriteLE32(&header.fmt.ByteRate, (static_cast<uint32_t>(num_channels) - * sample_rate * bytes_per_sample)); - WriteLE16(&header.fmt.BlockAlign, num_channels * bytes_per_sample); + 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, 8 * bytes_per_sample); WriteFourCC(&header.data.header.ID, 'd', 'a', 't', 'a'); @@ -147,6 +177,52 @@ void WriteWavHeader(uint8_t* buf, // Do an extra copy rather than writing everything to buf directly, since buf // might not be correctly aligned. memcpy(buf, &header, kWavHeaderSize); + return true; } +bool ReadWavHeader(const uint8_t* buf, + int* num_channels, + int* sample_rate, + WavFormat* format, + int* bytes_per_sample, + uint32_t* num_samples) { + WavHeader header; + memcpy(&header, buf, kWavHeaderSize); + + // Parse needed fields. + *format = static_cast<WavFormat>(ReadLE16(header.fmt.AudioFormat)); + *num_channels = ReadLE16(header.fmt.NumChannels); + *sample_rate = ReadLE32(header.fmt.SampleRate); + *bytes_per_sample = ReadLE16(header.fmt.BitsPerSample) / 8; + const uint32_t bytes_in_payload = ReadLE32(header.data.header.Size); + if (*bytes_per_sample <= 0) + return false; + *num_samples = bytes_in_payload / *bytes_per_sample; + + // Sanity check remaining fields. + if (ReadFourCC(header.riff.header.ID) != "RIFF") + return false; + if (ReadFourCC(header.riff.Format) != "WAVE") + return false; + if (ReadFourCC(header.fmt.header.ID) != "fmt ") + return false; + 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) + return false; + if (ReadLE32(header.fmt.ByteRate) != + ByteRate(*num_channels, *sample_rate, *bytes_per_sample)) + return false; + if (ReadLE16(header.fmt.BlockAlign) != + BlockAlign(*num_channels, *bytes_per_sample)) + return false; + + return CheckWavParameters(*num_channels, *sample_rate, *format, + *bytes_per_sample, *num_samples); +} + + } // namespace webrtc diff --git a/common_audio/wav_header.h b/common_audio/wav_header.h index f9ed8a57..0901f970 100644 --- a/common_audio/wav_header.h +++ b/common_audio/wav_header.h @@ -11,11 +11,12 @@ #ifndef WEBRTC_COMMON_AUDIO_WAV_HEADER_H_ #define WEBRTC_COMMON_AUDIO_WAV_HEADER_H_ +#include <stddef.h> #include <stdint.h> namespace webrtc { -static const int kWavHeaderSize = 44; +static const size_t kWavHeaderSize = 44; enum WavFormat { kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample @@ -33,14 +34,23 @@ bool CheckWavParameters(int num_channels, // Write a kWavHeaderSize bytes long WAV header to buf. The payload that // follows the header is supposed to have the specified number of interleaved // channels and contain the specified total number of samples of the specified -// type. -void WriteWavHeader(uint8_t* buf, +// type. Returns false if any of the input parameters are invalid. +bool WriteWavHeader(uint8_t* buf, int num_channels, int sample_rate, WavFormat format, 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, + int* num_channels, + int* sample_rate, + WavFormat* format, + int* bytes_per_sample, + uint32_t* num_samples); + } // namespace webrtc #endif // WEBRTC_COMMON_AUDIO_WAV_HEADER_H_ diff --git a/common_audio/wav_header_unittest.cc b/common_audio/wav_header_unittest.cc index f05160ea..ac79cc16 100644 --- a/common_audio/wav_header_unittest.cc +++ b/common_audio/wav_header_unittest.cc @@ -48,13 +48,85 @@ TEST(WavHeaderTest, CheckWavParameters) { webrtc::CheckWavParameters(3, 8000, webrtc::kWavFormatPcm, 1, 5)); } +TEST(WavHeaderTest, ReadWavHeader) { + int num_channels = 0; + int sample_rate = 0; + webrtc::WavFormat format = webrtc::kWavFormatPcm; + int bytes_per_sample = 0; + uint32_t num_samples = 0; + + // 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 + // 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)); +} + // Try writing a WAV header and make sure it looks OK. -TEST(WavHeaderTest, WriteWavHeader) { +TEST(WavHeaderTest, WriteAndReadWavHeader) { static const int kSize = 4 + webrtc::kWavHeaderSize + 4; uint8_t buf[kSize]; memset(buf, 0xa4, sizeof(buf)); - webrtc::WriteWavHeader( - buf + 4, 17, 12345, webrtc::kWavFormatALaw, 1, 123457689); + EXPECT_TRUE(webrtc::WriteWavHeader( + buf + 4, 17, 12345, webrtc::kWavFormatALaw, 1, 123457689)); static const uint8_t kExpectedBuf[] = { 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header 'R', 'I', 'F', 'F', @@ -74,4 +146,18 @@ TEST(WavHeaderTest, WriteWavHeader) { }; COMPILE_ASSERT(sizeof(kExpectedBuf) == kSize, buf_size); EXPECT_EQ(0, memcmp(kExpectedBuf, buf, kSize)); + + int num_channels = 0; + int sample_rate = 0; + webrtc::WavFormat format = webrtc::kWavFormatPcm; + int bytes_per_sample = 0; + uint32_t num_samples = 0; + EXPECT_TRUE( + webrtc::ReadWavHeader(buf + 4, &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(1, bytes_per_sample); + EXPECT_EQ(123457689u, num_samples); } diff --git a/common_audio/wav_writer.cc b/common_audio/wav_writer.cc deleted file mode 100644 index 52449789..00000000 --- a/common_audio/wav_writer.cc +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "webrtc/common_audio/wav_writer.h" - -#include <algorithm> -#include <cstdio> -#include <limits> - -#include "webrtc/base/checks.h" -#include "webrtc/common_audio/include/audio_util.h" -#include "webrtc/common_audio/wav_header.h" - -namespace webrtc { - -// We write 16-bit PCM WAV files. -static const WavFormat kWavFormat = kWavFormatPcm; -static const int kBytesPerSample = 2; - -WavFile::WavFile(const std::string& filename, int sample_rate, int num_channels) - : sample_rate_(sample_rate), - num_channels_(num_channels), - num_samples_(0), - file_handle_(fopen(filename.c_str(), "wb")) { - CHECK(file_handle_); - CHECK(CheckWavParameters(num_channels_, - sample_rate_, - kWavFormat, - kBytesPerSample, - num_samples_)); - - // Write a blank placeholder header, since we need to know the total number - // of samples before we can fill in the real data. - static const uint8_t blank_header[kWavHeaderSize] = {0}; - CHECK_EQ(1u, fwrite(blank_header, kWavHeaderSize, 1, file_handle_)); -} - -WavFile::~WavFile() { - Close(); -} - -void WavFile::WriteSamples(const int16_t* samples, size_t num_samples) { -#ifndef WEBRTC_ARCH_LITTLE_ENDIAN -#error "Need to convert samples to little-endian when writing to WAV file" -#endif - const size_t written = - fwrite(samples, sizeof(*samples), num_samples, file_handle_); - CHECK_EQ(num_samples, written); - num_samples_ += static_cast<uint32_t>(written); - CHECK(written <= std::numeric_limits<uint32_t>::max() || - num_samples_ >= written); // detect uint32_t overflow - CHECK(CheckWavParameters(num_channels_, - sample_rate_, - kWavFormat, - kBytesPerSample, - num_samples_)); -} - -void WavFile::WriteSamples(const float* samples, size_t num_samples) { - static const size_t kChunksize = 4096 / sizeof(uint16_t); - for (size_t i = 0; i < num_samples; i += kChunksize) { - int16_t isamples[kChunksize]; - const size_t chunk = std::min(kChunksize, num_samples - i); - FloatS16ToS16(samples + i, chunk, isamples); - WriteSamples(isamples, chunk); - } -} - -void WavFile::Close() { - CHECK_EQ(0, fseek(file_handle_, 0, SEEK_SET)); - uint8_t header[kWavHeaderSize]; - WriteWavHeader(header, num_channels_, sample_rate_, kWavFormat, - kBytesPerSample, num_samples_); - CHECK_EQ(1u, fwrite(header, kWavHeaderSize, 1, file_handle_)); - CHECK_EQ(0, fclose(file_handle_)); - file_handle_ = NULL; -} - -} // namespace webrtc - -rtc_WavFile* rtc_WavOpen(const char* filename, - int sample_rate, - int num_channels) { - return reinterpret_cast<rtc_WavFile*>( - new webrtc::WavFile(filename, sample_rate, num_channels)); -} - -void rtc_WavClose(rtc_WavFile* wf) { - delete reinterpret_cast<webrtc::WavFile*>(wf); -} - -void rtc_WavWriteSamples(rtc_WavFile* wf, - const float* samples, - size_t num_samples) { - reinterpret_cast<webrtc::WavFile*>(wf)->WriteSamples(samples, num_samples); -} - -int rtc_WavSampleRate(const rtc_WavFile* wf) { - return reinterpret_cast<const webrtc::WavFile*>(wf)->sample_rate(); -} - -int rtc_WavNumChannels(const rtc_WavFile* wf) { - return reinterpret_cast<const webrtc::WavFile*>(wf)->num_channels(); -} - -uint32_t rtc_WavNumSamples(const rtc_WavFile* wf) { - return reinterpret_cast<const webrtc::WavFile*>(wf)->num_samples(); -} diff --git a/common_audio/wav_writer.h b/common_audio/wav_writer.h deleted file mode 100644 index 09667279..00000000 --- a/common_audio/wav_writer.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef WEBRTC_COMMON_AUDIO_WAV_WRITER_H_ -#define WEBRTC_COMMON_AUDIO_WAV_WRITER_H_ - -#ifdef __cplusplus - -#include <stdint.h> -#include <cstddef> -#include <string> - -namespace webrtc { - -// Simple C++ class for writing 16-bit PCM WAV files. All error handling is -// by calls to CHECK(), making it unsuitable for anything but debug code. -class WavFile { - public: - // Open a new WAV file for writing. - WavFile(const std::string& filename, int sample_rate, int num_channels); - - // Close the WAV file, after writing its header. - ~WavFile(); - - // Write additional samples to the file. Each sample is in the range - // [-32768,32767], and there must be the previously specified number of - // interleaved channels. - void WriteSamples(const float* samples, size_t num_samples); - void WriteSamples(const int16_t* samples, size_t num_samples); - - int sample_rate() const { return sample_rate_; } - int num_channels() const { return num_channels_; } - uint32_t num_samples() const { return num_samples_; } - - private: - void Close(); - const int sample_rate_; - const int num_channels_; - uint32_t num_samples_; // total number of samples written to file - FILE* file_handle_; // output file, owned by this class -}; - -} // namespace webrtc - -extern "C" { -#endif // __cplusplus - -// C wrappers for the WavFile class. -typedef struct rtc_WavFile rtc_WavFile; -rtc_WavFile* rtc_WavOpen(const char* filename, - int sample_rate, - int num_channels); -void rtc_WavClose(rtc_WavFile* wf); -void rtc_WavWriteSamples(rtc_WavFile* wf, - const float* samples, - size_t num_samples); -int rtc_WavSampleRate(const rtc_WavFile* wf); -int rtc_WavNumChannels(const rtc_WavFile* wf); -uint32_t rtc_WavNumSamples(const rtc_WavFile* wf); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // WEBRTC_COMMON_AUDIO_WAV_WRITER_H_ |