Commit ccabe3b1 authored by Mingyu Kang's avatar Mingyu Kang Committed by Commit Bot

Add IEEE float support for WavAudioHandler

Bug: 1060860
Test: WavAudioHandlerTest.SampleFloatDataTest
Change-Id: If973db35bdf71ed8364de9dec6fc3e0d51285c04
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2099716Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Commit-Queue: Mingyu Kang <kenkangxgwe@google.com>
Cr-Commit-Position: refs/heads/master@{#750824}
parent bea39391
...@@ -13,6 +13,13 @@ const char kTestAudioData[] = ...@@ -13,6 +13,13 @@ const char kTestAudioData[] =
"data\x04\x00\x00\x00\x01\x00\x01\x00"; "data\x04\x00\x00\x00\x01\x00\x01\x00";
const size_t kTestAudioDataSize = base::size(kTestAudioData) - 1; const size_t kTestAudioDataSize = base::size(kTestAudioData) - 1;
// Extensible format with 48kHz rate stereo 32 bit float samples
const char kTestFloatAudioData[] =
"RIFF\x26\x00\x00\x00WAVEfmt \x10\x00\x00\x00"
"\x03\x00\x02\x00\x80\xbb\x00\x00\x00\xdc\x05\x00\x08\x00\x20\x00"
"data\x08\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";
const size_t kTestFloatAudioDataSize = base::size(kTestFloatAudioData) - 1;
// Extensible format with 48kHz rate stereo 32 bit PCM samples // Extensible format with 48kHz rate stereo 32 bit PCM samples
const char kTestExtensibleAudioData[] = const char kTestExtensibleAudioData[] =
"RIFF\x44\x00\x00\x00WAVEfmt \x28\x00\x00\x00" "RIFF\x44\x00\x00\x00WAVEfmt \x28\x00\x00\x00"
......
...@@ -42,14 +42,10 @@ const size_t kBitsPerSampleOffset = 14; ...@@ -42,14 +42,10 @@ const size_t kBitsPerSampleOffset = 14;
const size_t kValidBitsPerSampleOffset = 18; const size_t kValidBitsPerSampleOffset = 18;
const size_t kSubFormatOffset = 24; const size_t kSubFormatOffset = 24;
// Some constants for audio format.
const int kAudioFormatPCM = 1;
const int kAudioFormatExtensible = 0xfffe;
// A convenience struct for passing WAV parameters around. AudioParameters is // A convenience struct for passing WAV parameters around. AudioParameters is
// too heavyweight for this. Keep this class internal to this implementation. // too heavyweight for this. Keep this class internal to this implementation.
struct WavAudioParameters { struct WavAudioParameters {
int audio_format; WavAudioHandler::AudioFormat audio_format;
uint16_t num_channels; uint16_t num_channels;
uint32_t sample_rate; uint32_t sample_rate;
uint16_t bits_per_sample; uint16_t bits_per_sample;
...@@ -58,15 +54,25 @@ struct WavAudioParameters { ...@@ -58,15 +54,25 @@ struct WavAudioParameters {
}; };
bool ParamsAreValid(const WavAudioParameters& params) { bool ParamsAreValid(const WavAudioParameters& params) {
const uint16_t bytes_per_sample = params.bits_per_sample / 8; return (
return (params.audio_format == kAudioFormatPCM && params.num_channels != 0u && // Check number of channels
params.num_channels <= static_cast<uint16_t>(limits::kMaxChannels) && params.num_channels != 0u &&
params.sample_rate != 0u && params.bits_per_sample != 0u && params.num_channels <= static_cast<uint16_t>(limits::kMaxChannels) &&
params.bits_per_sample % 8u == 0u && // Check sample rate
(bytes_per_sample == 1 || bytes_per_sample == 2 || params.sample_rate != 0u &&
bytes_per_sample == 4) && (
(!params.is_extensible || // Check bits per second for PCM data
params.valid_bits_per_sample == params.bits_per_sample)); (params.audio_format ==
WavAudioHandler::AudioFormat::kAudioFormatPCM &&
(params.bits_per_sample == 8u || params.bits_per_sample == 16u ||
params.bits_per_sample == 32u)) ||
// Check bits per second for float data
(params.audio_format ==
WavAudioHandler::AudioFormat::kAudioFormatFloat &&
(params.bits_per_sample == 32u || params.bits_per_sample == 64u))) &&
// Check extensible format bps
(!params.is_extensible ||
params.valid_bits_per_sample == params.bits_per_sample));
} }
// Reads an integer from |data| with |offset|. // Reads an integer from |data| with |offset|.
...@@ -92,19 +98,22 @@ bool ParseFmtChunk(const base::StringPiece data, WavAudioParameters* params) { ...@@ -92,19 +98,22 @@ bool ParseFmtChunk(const base::StringPiece data, WavAudioParameters* params) {
} }
// Read in serialized parameters. // Read in serialized parameters.
params->audio_format = ReadInt<uint16_t>(data, kAudioFormatOffset); params->audio_format =
ReadInt<WavAudioHandler::AudioFormat>(data, kAudioFormatOffset);
params->num_channels = ReadInt<uint16_t>(data, kChannelOffset); params->num_channels = ReadInt<uint16_t>(data, kChannelOffset);
params->sample_rate = ReadInt<uint32_t>(data, kSampleRateOffset); params->sample_rate = ReadInt<uint32_t>(data, kSampleRateOffset);
params->bits_per_sample = ReadInt<uint16_t>(data, kBitsPerSampleOffset); params->bits_per_sample = ReadInt<uint16_t>(data, kBitsPerSampleOffset);
if (params->audio_format == kAudioFormatExtensible) { if (params->audio_format ==
WavAudioHandler::AudioFormat::kAudioFormatExtensible) {
if (data.size() < kFmtChunkExtensibleMinimumSize) { if (data.size() < kFmtChunkExtensibleMinimumSize) {
LOG(ERROR) << "Data size " << data.size() << " is too short."; LOG(ERROR) << "Data size " << data.size() << " is too short.";
return false; return false;
} }
params->is_extensible = true; params->is_extensible = true;
params->audio_format = ReadInt<uint16_t>(data, kSubFormatOffset); params->audio_format =
ReadInt<WavAudioHandler::AudioFormat>(data, kSubFormatOffset);
params->valid_bits_per_sample = params->valid_bits_per_sample =
ReadInt<uint16_t>(data, kValidBitsPerSampleOffset); ReadInt<uint16_t>(data, kValidBitsPerSampleOffset);
} else { } else {
...@@ -192,11 +201,13 @@ bool ParseWavData(const base::StringPiece wav_data, ...@@ -192,11 +201,13 @@ bool ParseWavData(const base::StringPiece wav_data,
WavAudioHandler::WavAudioHandler(base::StringPiece audio_data, WavAudioHandler::WavAudioHandler(base::StringPiece audio_data,
uint16_t num_channels, uint16_t num_channels,
uint32_t sample_rate, uint32_t sample_rate,
uint16_t bits_per_sample) uint16_t bits_per_sample,
AudioFormat audio_format)
: data_(audio_data), : data_(audio_data),
num_channels_(num_channels), num_channels_(num_channels),
sample_rate_(sample_rate), sample_rate_(sample_rate),
bits_per_sample_(bits_per_sample) { bits_per_sample_(bits_per_sample),
audio_format_(audio_format) {
DCHECK_NE(num_channels_, 0u); DCHECK_NE(num_channels_, 0u);
DCHECK_NE(sample_rate_, 0u); DCHECK_NE(sample_rate_, 0u);
DCHECK_NE(bits_per_sample_, 0u); DCHECK_NE(bits_per_sample_, 0u);
...@@ -215,9 +226,9 @@ std::unique_ptr<WavAudioHandler> WavAudioHandler::Create( ...@@ -215,9 +226,9 @@ std::unique_ptr<WavAudioHandler> WavAudioHandler::Create(
if (!ParseWavData(wav_data, &audio_data, &params)) if (!ParseWavData(wav_data, &audio_data, &params))
return nullptr; return nullptr;
return base::WrapUnique(new WavAudioHandler(audio_data, params.num_channels, return base::WrapUnique(
params.sample_rate, new WavAudioHandler(audio_data, params.num_channels, params.sample_rate,
params.bits_per_sample)); params.bits_per_sample, params.audio_format));
} }
bool WavAudioHandler::AtEnd(size_t cursor) const { bool WavAudioHandler::AtEnd(size_t cursor) const {
...@@ -240,8 +251,53 @@ bool WavAudioHandler::CopyTo(AudioBus* bus, ...@@ -240,8 +251,53 @@ bool WavAudioHandler::CopyTo(AudioBus* bus,
const int bytes_per_frame = num_channels_ * bits_per_sample_ / 8; const int bytes_per_frame = num_channels_ * bits_per_sample_ / 8;
const int remaining_frames = (data_.size() - cursor) / bytes_per_frame; const int remaining_frames = (data_.size() - cursor) / bytes_per_frame;
const int frames = std::min(bus->frames(), remaining_frames); const int frames = std::min(bus->frames(), remaining_frames);
const auto* source = data_.data() + cursor;
switch (audio_format_) {
case AudioFormat::kAudioFormatPCM:
switch (bits_per_sample_) {
case 8:
bus->FromInterleaved<UnsignedInt8SampleTypeTraits>(
reinterpret_cast<const uint8_t*>(source), frames);
break;
case 16:
bus->FromInterleaved<SignedInt16SampleTypeTraits>(
reinterpret_cast<const int16_t*>(source), frames);
break;
case 32:
bus->FromInterleaved<SignedInt32SampleTypeTraits>(
reinterpret_cast<const int32_t*>(source), frames);
break;
default:
NOTREACHED()
<< "Unsupported bytes per sample encountered for integer PCM: "
<< bytes_per_frame;
bus->ZeroFrames(frames);
}
break;
case AudioFormat::kAudioFormatFloat:
switch (bits_per_sample_) {
case 32:
bus->FromInterleaved<Float32SampleTypeTraitsNoClip>(
reinterpret_cast<const float*>(source), frames);
break;
case 64:
bus->FromInterleaved<Float64SampleTypeTraits>(
reinterpret_cast<const double*>(source), frames);
break;
default:
NOTREACHED()
<< "Unsupported bytes per sample encountered for float PCM: "
<< bytes_per_frame;
bus->ZeroFrames(frames);
}
break;
default:
NOTREACHED() << "Unsupported audio format encountered: "
<< static_cast<uint16_t>(audio_format_);
bus->ZeroFrames(frames);
}
bus->FromInterleaved(data_.data() + cursor, frames, bits_per_sample_ / 8);
*bytes_written = frames * bytes_per_frame; *bytes_written = frames * bytes_per_frame;
bus->ZeroFramesPartial(frames, bus->frames() - frames); bus->ZeroFramesPartial(frames, bus->frames() - frames);
return true; return true;
......
...@@ -23,6 +23,13 @@ class AudioBus; ...@@ -23,6 +23,13 @@ class AudioBus;
// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ // https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
class MEDIA_EXPORT WavAudioHandler { class MEDIA_EXPORT WavAudioHandler {
public: public:
// Supported audio format.
enum class AudioFormat : uint16_t {
kAudioFormatPCM = 0x0001,
kAudioFormatFloat = 0x0003,
kAudioFormatExtensible = 0xfffe
};
virtual ~WavAudioHandler(); virtual ~WavAudioHandler();
// Create a WavAudioHandler using |wav_data|. If |wav_data| cannot be parsed // Create a WavAudioHandler using |wav_data|. If |wav_data| cannot be parsed
...@@ -42,10 +49,11 @@ class MEDIA_EXPORT WavAudioHandler { ...@@ -42,10 +49,11 @@ class MEDIA_EXPORT WavAudioHandler {
// Accessors. // Accessors.
const base::StringPiece& data() const { return data_; } const base::StringPiece& data() const { return data_; }
uint16_t num_channels() const { return num_channels_; } int num_channels() const { return static_cast<int>(num_channels_); }
uint32_t sample_rate() const { return sample_rate_; } int sample_rate() const { return static_cast<int>(sample_rate_); }
uint16_t bits_per_sample() const { return bits_per_sample_; } int bits_per_sample() const { return static_cast<int>(bits_per_sample_); }
uint32_t total_frames() const { return total_frames_; } AudioFormat audio_format() const { return audio_format_; }
int total_frames() const { return static_cast<int>(total_frames_); }
// Returns the duration of the entire audio chunk. // Returns the duration of the entire audio chunk.
base::TimeDelta GetDuration() const; base::TimeDelta GetDuration() const;
...@@ -55,13 +63,15 @@ class MEDIA_EXPORT WavAudioHandler { ...@@ -55,13 +63,15 @@ class MEDIA_EXPORT WavAudioHandler {
WavAudioHandler(base::StringPiece audio_data, WavAudioHandler(base::StringPiece audio_data,
uint16_t num_channels, uint16_t num_channels,
uint32_t sample_rate, uint32_t sample_rate,
uint16_t bits_per_sample); uint16_t bits_per_sample,
AudioFormat audio_format);
// Data part of the |wav_data_|. // Data part of the |wav_data_|.
const base::StringPiece data_; const base::StringPiece data_;
const uint16_t num_channels_; const uint16_t num_channels_;
const uint32_t sample_rate_; const uint32_t sample_rate_;
const uint16_t bits_per_sample_; const uint16_t bits_per_sample_;
const AudioFormat audio_format_;
uint32_t total_frames_; uint32_t total_frames_;
DISALLOW_COPY_AND_ASSIGN(WavAudioHandler); DISALLOW_COPY_AND_ASSIGN(WavAudioHandler);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stddef.h> #include <stddef.h>
#include <memory> #include <memory>
#include <string>
#include "base/logging.h" #include "base/logging.h"
#include "base/stl_util.h" #include "base/stl_util.h"
...@@ -39,10 +40,10 @@ TEST(WavAudioHandlerTest, SampleDataTest) { ...@@ -39,10 +40,10 @@ TEST(WavAudioHandlerTest, SampleDataTest) {
std::string data(kTestAudioData, kTestAudioDataSize); std::string data(kTestAudioData, kTestAudioDataSize);
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
ASSERT_TRUE(handler); ASSERT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(16u, handler->bits_per_sample()); ASSERT_EQ(16, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1u, handler->total_frames()); ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(4U, handler->data().size()); ASSERT_EQ(4U, handler->data().size());
...@@ -58,14 +59,37 @@ TEST(WavAudioHandlerTest, SampleDataTest) { ...@@ -58,14 +59,37 @@ TEST(WavAudioHandlerTest, SampleDataTest) {
ASSERT_EQ(static_cast<size_t>(handler->data().size()), bytes_written); ASSERT_EQ(static_cast<size_t>(handler->data().size()), bytes_written);
} }
TEST(WavAudioHandlerTest, SampleFloatDataTest) {
std::string data(kTestFloatAudioData, kTestFloatAudioDataSize);
auto handler = WavAudioHandler::Create(data);
ASSERT_TRUE(handler);
ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(32, handler->bits_per_sample());
ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(8U, handler->data().size());
const char kData[] = "\x00\x01\x00\x00\x01\x00\x00\x00";
ASSERT_EQ(base::StringPiece(kData, base::size(kData) - 1), handler->data());
std::unique_ptr<AudioBus> bus =
AudioBus::Create(handler->num_channels(),
handler->data().size() / handler->num_channels());
size_t bytes_written = 0u;
ASSERT_TRUE(handler->CopyTo(bus.get(), 0, &bytes_written));
ASSERT_EQ(static_cast<size_t>(handler->data().size()), bytes_written);
}
TEST(WavAudioHandlerTest, SampleExtensibleDataTest) { TEST(WavAudioHandlerTest, SampleExtensibleDataTest) {
std::string data(kTestExtensibleAudioData, kTestExtensibleAudioDataSize); std::string data(kTestExtensibleAudioData, kTestExtensibleAudioDataSize);
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
ASSERT_TRUE(handler); ASSERT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(32u, handler->bits_per_sample()); ASSERT_EQ(32, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1u, handler->total_frames()); ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(8U, handler->data().size()); ASSERT_EQ(8U, handler->data().size());
...@@ -121,10 +145,10 @@ TEST(WavAudioHandlerTest, TestTooBigTotalSizeIsOkay) { ...@@ -121,10 +145,10 @@ TEST(WavAudioHandlerTest, TestTooBigTotalSizeIsOkay) {
data[kWavDataSizeIndex + 3] = '\x00'; data[kWavDataSizeIndex + 3] = '\x00';
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
EXPECT_TRUE(handler); EXPECT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(16u, handler->bits_per_sample()); ASSERT_EQ(16, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1u, handler->total_frames()); ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(4U, handler->data().size()); ASSERT_EQ(4U, handler->data().size());
...@@ -143,10 +167,10 @@ TEST(WavAudioHandlerTest, TestTooBigDataChunkSizeIsOkay) { ...@@ -143,10 +167,10 @@ TEST(WavAudioHandlerTest, TestTooBigDataChunkSizeIsOkay) {
data[kDataHeaderIndex + 7] = '\x00'; data[kDataHeaderIndex + 7] = '\x00';
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
EXPECT_TRUE(handler); EXPECT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(16u, handler->bits_per_sample()); ASSERT_EQ(16, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1u, handler->total_frames()); ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(4U, handler->data().size()); ASSERT_EQ(4U, handler->data().size());
...@@ -176,10 +200,10 @@ TEST(WavAudioHandlerTest, TestOtherSectionTypesIsOkay) { ...@@ -176,10 +200,10 @@ TEST(WavAudioHandlerTest, TestOtherSectionTypesIsOkay) {
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
EXPECT_TRUE(handler); EXPECT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(16u, handler->bits_per_sample()); ASSERT_EQ(16, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(1u, handler->total_frames()); ASSERT_EQ(1, handler->total_frames());
ASSERT_EQ(20u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(20u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(4u, handler->data().size()); ASSERT_EQ(4u, handler->data().size());
} }
...@@ -206,10 +230,10 @@ TEST(WavAudioHandlerTest, TestNoDataSectionIsOkay) { ...@@ -206,10 +230,10 @@ TEST(WavAudioHandlerTest, TestNoDataSectionIsOkay) {
data[kDataHeaderIndex + 3] = 'd'; data[kDataHeaderIndex + 3] = 'd';
auto handler = WavAudioHandler::Create(data); auto handler = WavAudioHandler::Create(data);
EXPECT_TRUE(handler); EXPECT_TRUE(handler);
ASSERT_EQ(2u, handler->num_channels()); ASSERT_EQ(2, handler->num_channels());
ASSERT_EQ(16u, handler->bits_per_sample()); ASSERT_EQ(16, handler->bits_per_sample());
ASSERT_EQ(48000u, handler->sample_rate()); ASSERT_EQ(48000, handler->sample_rate());
ASSERT_EQ(0u, handler->total_frames()); ASSERT_EQ(0, handler->total_frames());
ASSERT_EQ(0u, handler->GetDuration().InMicroseconds()); ASSERT_EQ(0u, handler->GetDuration().InMicroseconds());
ASSERT_EQ(0u, handler->data().size()); ASSERT_EQ(0u, handler->data().size());
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment