diff --git a/webrtc/common_audio/common_audio.gyp b/webrtc/common_audio/common_audio.gyp index 3bed4e4d8..58b9bb983 100644 --- a/webrtc/common_audio/common_audio.gyp +++ b/webrtc/common_audio/common_audio.gyp @@ -92,6 +92,10 @@ 'vad/vad_gmm.h', 'vad/vad_sp.c', 'vad/vad_sp.h', + 'wav_header.cc', + 'wav_header.h', + 'wav_writer.cc', + 'wav_writer.h', ], 'conditions': [ ['target_arch=="ia32" or target_arch=="x64"', { @@ -211,6 +215,8 @@ 'vad/vad_sp_unittest.cc', 'vad/vad_unittest.cc', 'vad/vad_unittest.h', + 'wav_header_unittest.cc', + 'wav_writer_unittest.cc', ], 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are diff --git a/webrtc/common_audio/wav_header.cc b/webrtc/common_audio/wav_header.cc new file mode 100644 index 000000000..ce43896ff --- /dev/null +++ b/webrtc/common_audio/wav_header.cc @@ -0,0 +1,152 @@ +/* + * 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. + */ + +// Based on the WAV file format documentation at +// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ and +// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + +#include "webrtc/common_audio/wav_header.h" + +#include +#include +#include + +#include "webrtc/common_audio/include/audio_util.h" + +namespace webrtc { + +struct ChunkHeader { + uint32_t ID; + uint32_t Size; +}; +COMPILE_ASSERT(sizeof(ChunkHeader) == 8, chunk_header_size); + +bool CheckWavParameters(int num_channels, + int sample_rate, + WavFormat format, + int bytes_per_sample, + uint32_t num_samples) { + // num_channels, sample_rate, and bytes_per_sample must be positive, must fit + // in their respective fields, and their product must fit in the 32-bit + // ByteRate field. + if (num_channels <= 0 || sample_rate <= 0 || bytes_per_sample <= 0) + return false; + if (static_cast(sample_rate) > std::numeric_limits::max()) + return false; + if (static_cast(num_channels) > + std::numeric_limits::max()) + return false; + if (static_cast(bytes_per_sample) * 8 > + std::numeric_limits::max()) + return false; + if (static_cast(sample_rate) * num_channels * bytes_per_sample > + std::numeric_limits::max()) + return false; + + // format and bytes_per_sample must agree. + switch (format) { + case kWavFormatPcm: + // Other values may be OK, but for now we're conservative: + if (bytes_per_sample != 1 && bytes_per_sample != 2) + return false; + break; + case kWavFormatALaw: + case kWavFormatMuLaw: + if (bytes_per_sample != 1) + return false; + break; + default: + return false; + } + + // The number of bytes in the file, not counting the first ChunkHeader, must + // be less than 2^32; otherwise, the ChunkSize field overflows. + const uint32_t max_samples = + (std::numeric_limits::max() + - (kWavHeaderSize - sizeof(ChunkHeader))) / + bytes_per_sample; + if (num_samples > max_samples) + return false; + + // Each channel must have the same number of samples. + if (num_samples % num_channels != 0) + return false; + + return true; +} + +#ifdef WEBRTC_ARCH_LITTLE_ENDIAN +static inline void WriteLE16(uint16_t* f, uint16_t x) { *f = x; } +static inline void WriteLE32(uint32_t* f, uint32_t x) { *f = x; } +static inline void WriteFourCC(uint32_t* f, char a, char b, char c, char d) { + *f = static_cast(a) + | static_cast(b) << 8 + | static_cast(c) << 16 + | static_cast(d) << 24; +} +#else +#error "Write be-to-le conversion functions" +#endif + +void 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); + + 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)); + 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)); + WriteLE16(&header.fmt.AudioFormat, format); + WriteLE16(&header.fmt.NumChannels, num_channels); + WriteLE32(&header.fmt.SampleRate, sample_rate); + WriteLE32(&header.fmt.ByteRate, (static_cast(num_channels) + * sample_rate * bytes_per_sample)); + WriteLE16(&header.fmt.BlockAlign, num_channels * bytes_per_sample); + WriteLE16(&header.fmt.BitsPerSample, 8 * bytes_per_sample); + + WriteFourCC(&header.data.header.ID, 'd', 'a', 't', 'a'); + WriteLE32(&header.data.header.Size, 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); +} + +} // namespace webrtc diff --git a/webrtc/common_audio/wav_header.h b/webrtc/common_audio/wav_header.h new file mode 100644 index 000000000..f9ed8a576 --- /dev/null +++ b/webrtc/common_audio/wav_header.h @@ -0,0 +1,46 @@ +/* + * 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_HEADER_H_ +#define WEBRTC_COMMON_AUDIO_WAV_HEADER_H_ + +#include + +namespace webrtc { + +static const int kWavHeaderSize = 44; + +enum WavFormat { + kWavFormatPcm = 1, // PCM, each sample of size bytes_per_sample + kWavFormatALaw = 6, // 8-bit ITU-T G.711 A-law + kWavFormatMuLaw = 7, // 8-bit ITU-T G.711 mu-law +}; + +// Return true if the given parameters will make a well-formed WAV header. +bool CheckWavParameters(int num_channels, + int sample_rate, + WavFormat format, + int bytes_per_sample, + uint32_t num_samples); + +// 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, + 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/webrtc/common_audio/wav_header_unittest.cc b/webrtc/common_audio/wav_header_unittest.cc new file mode 100644 index 000000000..f05160eaf --- /dev/null +++ b/webrtc/common_audio/wav_header_unittest.cc @@ -0,0 +1,77 @@ +/* + * 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 + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/common_audio/wav_header.h" +#include "webrtc/system_wrappers/interface/compile_assert.h" + +// 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)); + + // 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)); + + // 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())); + + // Not the same number of samples for each channel. + EXPECT_FALSE( + webrtc::CheckWavParameters(3, 8000, webrtc::kWavFormatPcm, 1, 5)); +} + +// Try writing a WAV header and make sure it looks OK. +TEST(WavHeaderTest, WriteWavHeader) { + 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); + static const uint8_t kExpectedBuf[] = { + 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes before header + '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 + 'd', 'a', 't', 'a', + 0x99, 0xd0, 0x5b, 0x07, // size of payload: 123457689 + 0xa4, 0xa4, 0xa4, 0xa4, // untouched bytes after header + }; + COMPILE_ASSERT(sizeof(kExpectedBuf) == kSize, buf_size); + EXPECT_EQ(0, memcmp(kExpectedBuf, buf, kSize)); +} diff --git a/webrtc/common_audio/wav_writer.cc b/webrtc/common_audio/wav_writer.cc new file mode 100644 index 000000000..e5714735b --- /dev/null +++ b/webrtc/common_audio/wav_writer.cc @@ -0,0 +1,103 @@ +/* + * 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 +#include +#include + +#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")) { + FATAL_ERROR_IF(!CheckWavParameters(num_channels_, + sample_rate_, + kWavFormat, + kBytesPerSample, + num_samples_)); + FATAL_ERROR_IF(!file_handle_); + + // 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}; + FATAL_ERROR_IF(fwrite(blank_header, kWavHeaderSize, 1, file_handle_) != 1); +} + +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_); + FATAL_ERROR_IF(written != num_samples); + num_samples_ += static_cast(written); + FATAL_ERROR_IF(written > std::numeric_limits::max() || + num_samples_ < written); // detect uint32_t overflow + FATAL_ERROR_IF(!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); + RoundToInt16(samples + i, chunk, isamples); + WriteSamples(isamples, chunk); + } +} + +void WavFile::Close() { + FATAL_ERROR_IF(fseek(file_handle_, 0, SEEK_SET) != 0); + uint8_t header[kWavHeaderSize]; + WriteWavHeader(header, num_channels_, sample_rate_, kWavFormat, + kBytesPerSample, num_samples_); + FATAL_ERROR_IF(fwrite(header, kWavHeaderSize, 1, file_handle_) != 1); + FATAL_ERROR_IF(fclose(file_handle_) != 0); + file_handle_ = NULL; +} + +} // namespace webrtc + +rtc_WavFile* rtc_WavOpen(const char* filename, + int sample_rate, + int num_channels) { + return reinterpret_cast( + new webrtc::WavFile(filename, sample_rate, num_channels)); +} + +void rtc_WavClose(rtc_WavFile* wf) { + delete reinterpret_cast(wf); +} + +void rtc_WavWriteSamples(rtc_WavFile* wf, + const float* samples, + size_t num_samples) { + reinterpret_cast(wf)->WriteSamples(samples, num_samples); +} diff --git a/webrtc/common_audio/wav_writer.h b/webrtc/common_audio/wav_writer.h new file mode 100644 index 000000000..a8fee51e3 --- /dev/null +++ b/webrtc/common_audio/wav_writer.h @@ -0,0 +1,65 @@ +/* + * 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 +#include +#include + +namespace webrtc { + +// Simple C++ class for writing 16-bit PCM WAV files. All error handling is +// by calls to FATAL_ERROR(), 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); + + private: + void WriteSamples(const int16_t* samples, size_t num_samples); + 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); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBRTC_COMMON_AUDIO_WAV_WRITER_H_ diff --git a/webrtc/common_audio/wav_writer_unittest.cc b/webrtc/common_audio/wav_writer_unittest.cc new file mode 100644 index 000000000..9efe96e30 --- /dev/null +++ b/webrtc/common_audio/wav_writer_unittest.cc @@ -0,0 +1,124 @@ +/* + * 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. + */ + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include +#include + +#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/test/testsupport/fileutils.h" + +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"; + static const int kNumSamples = 3; + { + webrtc::WavFile w(outfile, 14099, 1); + w.WriteSamples(kSamples, kNumSamples); + } + static const uint8_t kExpectedContents[] = { + 'R', 'I', 'F', 'F', + 42, 0, 0, 0, // size of whole file - 8: 6 + 44 - 8 + 'W', 'A', 'V', 'E', + 'f', 'm', 't', ' ', + 16, 0, 0, 0, // size of fmt block - 8: 24 - 8 + 1, 0, // format: PCM (1) + 1, 0, // channels: 1 + 0x13, 0x37, 0, 0, // sample rate: 14099 + 0x26, 0x6e, 0, 0, // byte rate: 2 * 14099 + 2, 0, // block align: NumChannels * BytesPerSample + 16, 0, // bits per sample: 2 * 8 + 'd', 'a', 't', 'a', + 6, 0, 0, 0, // size of payload: 6 + 0, 0, // first sample: 0.0 + 10, 0, // second sample: 10.0 + 0xff, 0x7f, // third sample: 4e4 (saturated) + }; + static const int kContentSize = + webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); + COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size); + EXPECT_EQ(size_t(kContentSize), webrtc::test::GetFileSize(outfile)); + FILE* f = fopen(outfile.c_str(), "rb"); + ASSERT_TRUE(f); + uint8_t contents[kContentSize]; + ASSERT_EQ(1u, fread(contents, kContentSize, 1, f)); + EXPECT_EQ(0, fclose(f)); + EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); +} + +// 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); + static const int kNumSamples = 4; + rtc_WavWriteSamples(w, &kSamples[0], 2); + rtc_WavWriteSamples(w, &kSamples[2], kNumSamples - 2); + rtc_WavClose(w); + static const uint8_t kExpectedContents[] = { + 'R', 'I', 'F', 'F', + 44, 0, 0, 0, // size of whole file - 8: 8 + 44 - 8 + 'W', 'A', 'V', 'E', + 'f', 'm', 't', ' ', + 16, 0, 0, 0, // size of fmt block - 8: 24 - 8 + 1, 0, // format: PCM (1) + 2, 0, // channels: 2 + 0x80, 0x2e, 0, 0, // sample rate: 11904 + 0, 0xba, 0, 0, // byte rate: 2 * 2 * 11904 + 4, 0, // block align: NumChannels * BytesPerSample + 16, 0, // bits per sample: 2 * 8 + 'd', 'a', 't', 'a', + 8, 0, 0, 0, // size of payload: 8 + 0, 0, // first sample: 0.0 + 10, 0, // second sample: 10.0 + 0xff, 0x7f, // third sample: 4e4 (saturated) + 0, 0x80, // fourth sample: -1e9 (saturated) + }; + static const int kContentSize = + webrtc::kWavHeaderSize + kNumSamples * sizeof(int16_t); + COMPILE_ASSERT(sizeof(kExpectedContents) == kContentSize, content_size); + EXPECT_EQ(size_t(kContentSize), webrtc::test::GetFileSize(outfile)); + FILE* f = fopen(outfile.c_str(), "rb"); + ASSERT_TRUE(f); + uint8_t contents[kContentSize]; + ASSERT_EQ(1u, fread(contents, kContentSize, 1, f)); + EXPECT_EQ(0, fclose(f)); + EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); +} + +// 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"; + static const int kSampleRate = 8000; + static const int kNumChannels = 2; + static const int kNumSamples = 3 * kSampleRate * kNumChannels; + float samples[kNumSamples]; + for (int i = 0; i < kNumSamples; i += kNumChannels) { + // A nice periodic beeping sound. + static const double kToneHz = 440; + const double t = static_cast(i) / (kNumChannels * kSampleRate); + const double x = + std::numeric_limits::max() * std::sin(t * kToneHz * 2 * M_PI); + samples[i] = std::pow(std::sin(t * 2 * 2 * M_PI), 10) * x; + samples[i + 1] = std::pow(std::cos(t * 2 * 2 * M_PI), 10) * x; + } + { + webrtc::WavFile w(outfile, kSampleRate, kNumChannels); + w.WriteSamples(samples, kNumSamples); + } + EXPECT_EQ(sizeof(int16_t) * kNumSamples + webrtc::kWavHeaderSize, + webrtc::test::GetFileSize(outfile)); +} diff --git a/webrtc/modules/media_file/source/media_file_utility.cc b/webrtc/modules/media_file/source/media_file_utility.cc index e8883c9a4..71d62a19d 100644 --- a/webrtc/modules/media_file/source/media_file_utility.cc +++ b/webrtc/modules/media_file/source/media_file_utility.cc @@ -14,6 +14,7 @@ #include #include +#include "webrtc/common_audio/wav_header.h" #include "webrtc/common_types.h" #include "webrtc/engine_configurations.h" #include "webrtc/modules/interface/module_common_types.h" @@ -25,12 +26,6 @@ #endif namespace { -enum WaveFormats -{ - kWaveFormatPcm = 0x0001, - kWaveFormatALaw = 0x0006, - kWaveFormatMuLaw = 0x0007 -}; // First 16 bytes the WAVE header. ckID should be "RIFF", wave_ckID should be // "WAVE" and ckSize is the chunk size (4 + n) @@ -183,7 +178,7 @@ int32_t ModuleFileUtility::InitAviWriting( waveFormatHeader.nSamplesPerSec = 8000; waveFormatHeader.wBitsPerSample = 8; waveFormatHeader.nBlockAlign = 1; - waveFormatHeader.wFormatTag = kWaveFormatMuLaw; + waveFormatHeader.wFormatTag = kWavFormatMuLaw; } else if (strncmp(audioCodecInst.plname, "PCMA", 4) == 0) { @@ -196,7 +191,7 @@ int32_t ModuleFileUtility::InitAviWriting( waveFormatHeader.nSamplesPerSec = 8000; waveFormatHeader.wBitsPerSample = 8; waveFormatHeader.nBlockAlign = 1; - waveFormatHeader.wFormatTag = kWaveFormatALaw; + waveFormatHeader.wFormatTag = kWavFormatALaw; } else if (strncmp(audioCodecInst.plname, "L16", 3) == 0) { @@ -210,7 +205,7 @@ int32_t ModuleFileUtility::InitAviWriting( waveFormatHeader.nSamplesPerSec = audioCodecInst.plfreq; waveFormatHeader.wBitsPerSample = 16; waveFormatHeader.nBlockAlign = 2; - waveFormatHeader.wFormatTag = kWaveFormatPcm; + waveFormatHeader.wFormatTag = kWavFormatPcm; } else { return -1; @@ -499,8 +494,7 @@ int32_t ModuleFileUtility::ReadWavHeader(InStream& wav) memcpy(tmpStr2, &_wavFormatObj.formatTag, 2); _wavFormatObj.formatTag = - (WaveFormats) ((uint32_t)tmpStr2[0] + - (((uint32_t)tmpStr2[1])<<8)); + (uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8); memcpy(tmpStr2, &_wavFormatObj.nChannels, 2); _wavFormatObj.nChannels = (int16_t) ((uint32_t)tmpStr2[0] + @@ -575,9 +569,9 @@ int32_t ModuleFileUtility::ReadWavHeader(InStream& wav) // Either a proper format chunk has been read or a data chunk was come // across. - if( (_wavFormatObj.formatTag != kWaveFormatPcm) && - (_wavFormatObj.formatTag != kWaveFormatALaw) && - (_wavFormatObj.formatTag != kWaveFormatMuLaw)) + if( (_wavFormatObj.formatTag != kWavFormatPcm) && + (_wavFormatObj.formatTag != kWavFormatALaw) && + (_wavFormatObj.formatTag != kWavFormatMuLaw)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Coding formatTag value=%d not supported!", @@ -603,7 +597,7 @@ int32_t ModuleFileUtility::ReadWavHeader(InStream& wav) } // Calculate the number of bytes that 10 ms of audio data correspond to. - if(_wavFormatObj.formatTag == kWaveFormatPcm) + if(_wavFormatObj.formatTag == kWavFormatPcm) { // TODO (hellner): integer division for 22050 and 11025 would yield // the same result as the else statement. Remove those @@ -643,19 +637,19 @@ int32_t ModuleFileUtility::InitWavCodec(uint32_t samplesPerSec, // Calculate the packet size for 10ms frames switch(formatTag) { - case kWaveFormatALaw: + case kWavFormatALaw: strcpy(codec_info_.plname, "PCMA"); _codecId = kCodecPcma; codec_info_.pltype = 8; codec_info_.pacsize = codec_info_.plfreq / 100; break; - case kWaveFormatMuLaw: + case kWavFormatMuLaw: strcpy(codec_info_.plname, "PCMU"); _codecId = kCodecPcmu; codec_info_.pltype = 0; codec_info_.pacsize = codec_info_.plfreq / 100; break; - case kWaveFormatPcm: + case kWavFormatPcm: codec_info_.pacsize = (bitsPerSample * (codec_info_.plfreq / 100)) / 8; if(samplesPerSec == 8000) { @@ -1054,14 +1048,14 @@ int32_t ModuleFileUtility::InitWavWriting(OutStream& wav, { _bytesPerSample = 1; if(WriteWavHeader(wav, 8000, _bytesPerSample, channels, - kWaveFormatMuLaw, 0) == -1) + kWavFormatMuLaw, 0) == -1) { return -1; } }else if(STR_CASE_CMP(codecInst.plname, "PCMA") == 0) { _bytesPerSample = 1; - if(WriteWavHeader(wav, 8000, _bytesPerSample, channels, kWaveFormatALaw, + if(WriteWavHeader(wav, 8000, _bytesPerSample, channels, kWavFormatALaw, 0) == -1) { return -1; @@ -1071,7 +1065,7 @@ int32_t ModuleFileUtility::InitWavWriting(OutStream& wav, { _bytesPerSample = 2; if(WriteWavHeader(wav, codecInst.plfreq, _bytesPerSample, channels, - kWaveFormatPcm, 0) == -1) + kWavFormatPcm, 0) == -1) { return -1; } @@ -1124,103 +1118,18 @@ int32_t ModuleFileUtility::WriteWavHeader( const uint32_t format, const uint32_t lengthInBytes) { - // Frame size in bytes for 10 ms of audio. // TODO (hellner): 44.1 kHz has 440 samples frame size. Doesn't seem to // be taken into consideration here! - int32_t frameSize = (freq / 100) * bytesPerSample * channels; + const int32_t frameSize = (freq / 100) * channels; // Calculate the number of full frames that the wave file contain. - const int32_t dataLengthInBytes = frameSize * - (lengthInBytes / frameSize); - - int8_t tmpStr[4]; - int8_t tmpChar; - uint32_t tmpLong; - - memcpy(tmpStr, "RIFF", 4); - wav.Write(tmpStr, 4); - - tmpLong = dataLengthInBytes + 36; - tmpChar = (int8_t)(tmpLong); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 8); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 16); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 24); - wav.Write(&tmpChar, 1); - - memcpy(tmpStr, "WAVE", 4); - wav.Write(tmpStr, 4); - - memcpy(tmpStr, "fmt ", 4); - wav.Write(tmpStr, 4); - - tmpChar = 16; - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - - tmpChar = (int8_t)(format); - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - - tmpChar = (int8_t)(channels); - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - - tmpLong = freq; - tmpChar = (int8_t)(tmpLong); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 8); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 16); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 24); - wav.Write(&tmpChar, 1); - - // nAverageBytesPerSec = Sample rate * Bytes per sample * Channels - tmpLong = bytesPerSample * freq * channels; - tmpChar = (int8_t)(tmpLong); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 8); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 16); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 24); - wav.Write(&tmpChar, 1); - - // nBlockAlign = Bytes per sample * Channels - tmpChar = (int8_t)(bytesPerSample * channels); - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - - tmpChar = (int8_t)(bytesPerSample*8); - wav.Write(&tmpChar, 1); - tmpChar = 0; - wav.Write(&tmpChar, 1); - - memcpy(tmpStr, "data", 4); - wav.Write(tmpStr, 4); - - tmpLong = dataLengthInBytes; - tmpChar = (int8_t)(tmpLong); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 8); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 16); - wav.Write(&tmpChar, 1); - tmpChar = (int8_t)(tmpLong >> 24); - wav.Write(&tmpChar, 1); + const int32_t dataLengthInBytes = frameSize * (lengthInBytes / frameSize); + uint8_t buf[kWavHeaderSize]; + webrtc::WriteWavHeader(buf, channels, freq, static_cast(format), + bytesPerSample, dataLengthInBytes / bytesPerSample); + wav.Write(buf, kWavHeaderSize); return 0; } @@ -1237,12 +1146,12 @@ int32_t ModuleFileUtility::UpdateWavHeader(OutStream& wav) if(STR_CASE_CMP(codec_info_.plname, "L16") == 0) { res = WriteWavHeader(wav, codec_info_.plfreq, 2, channels, - kWaveFormatPcm, _bytesWritten); + kWavFormatPcm, _bytesWritten); } else if(STR_CASE_CMP(codec_info_.plname, "PCMU") == 0) { - res = WriteWavHeader(wav, 8000, 1, channels, kWaveFormatMuLaw, + res = WriteWavHeader(wav, 8000, 1, channels, kWavFormatMuLaw, _bytesWritten); } else if(STR_CASE_CMP(codec_info_.plname, "PCMA") == 0) { - res = WriteWavHeader(wav, 8000, 1, channels, kWaveFormatALaw, + res = WriteWavHeader(wav, 8000, 1, channels, kWavFormatALaw, _bytesWritten); } else { // Allow calling this API even if not writing to a WAVE file.