New utility class for easy debug dumping to WAV files
There are currently a number of places in the code where we dump audio data in various stages of processing for debug purposes. Currently these all write raw, uncompressed PCM files, which isn't supported by the most common audio players, and requires the user to supply metadata such as sample rate, sample size and endianness, etc. This patch adds a simple class that makes it easy to write WAV files instead. WAV files still contain the same uncompressed PCM data, but they have a small header that contains all the requisite metadata, and are supported by virtually all audio players. Since some of the debug code that will be writing WAV files is written in plain C, a C API is included as well. R=andrew@webrtc.org, bjornv@webrtc.org, henrike@webrtc.org, tommi@webrtc.org Review URL: https://webrtc-codereview.appspot.com/16809004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6932 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
71d9572e9c
commit
877083c4d4
@ -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
|
||||
|
152
webrtc/common_audio/wav_header.cc
Normal file
152
webrtc/common_audio/wav_header.cc
Normal file
@ -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 <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
#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<uint64_t>(sample_rate) > std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
if (static_cast<uint64_t>(num_channels) >
|
||||
std::numeric_limits<uint16_t>::max())
|
||||
return false;
|
||||
if (static_cast<uint64_t>(bytes_per_sample) * 8 >
|
||||
std::numeric_limits<uint16_t>::max())
|
||||
return false;
|
||||
if (static_cast<uint64_t>(sample_rate) * num_channels * bytes_per_sample >
|
||||
std::numeric_limits<uint32_t>::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<uint32_t>::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<uint32_t>(a)
|
||||
| static_cast<uint32_t>(b) << 8
|
||||
| static_cast<uint32_t>(c) << 16
|
||||
| static_cast<uint32_t>(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<uint32_t>(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
|
46
webrtc/common_audio/wav_header.h
Normal file
46
webrtc/common_audio/wav_header.h
Normal file
@ -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 <stdint.h>
|
||||
|
||||
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_
|
77
webrtc/common_audio/wav_header_unittest.cc
Normal file
77
webrtc/common_audio/wav_header_unittest.cc
Normal file
@ -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 <limits>
|
||||
|
||||
#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<uint32_t>::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));
|
||||
}
|
103
webrtc/common_audio/wav_writer.cc
Normal file
103
webrtc/common_audio/wav_writer.cc
Normal file
@ -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 <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")) {
|
||||
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<uint32_t>(written);
|
||||
FATAL_ERROR_IF(written > std::numeric_limits<uint32_t>::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<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);
|
||||
}
|
65
webrtc/common_audio/wav_writer.h
Normal file
65
webrtc/common_audio/wav_writer.h
Normal file
@ -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 <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 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_
|
124
webrtc/common_audio/wav_writer_unittest.cc
Normal file
124
webrtc/common_audio/wav_writer_unittest.cc
Normal file
@ -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 <cmath>
|
||||
#include <limits>
|
||||
|
||||
#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<double>(i) / (kNumChannels * kSampleRate);
|
||||
const double x =
|
||||
std::numeric_limits<int16_t>::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));
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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<WavFormat>(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.
|
||||
|
Loading…
Reference in New Issue
Block a user