diff options
author | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-21 02:49:49 +0000 |
---|---|---|
committer | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-21 02:49:49 +0000 |
commit | 23ff5a176c299bd3b498efbee249891eb58f3b74 (patch) | |
tree | 0d0d6f2e33436575a996e6ac37df820bf3f81b98 /media | |
parent | e8c4612dd7454464fac243645d1f6d01ca9596ff (diff) | |
download | chromium_src-23ff5a176c299bd3b498efbee249891eb58f3b74.zip chromium_src-23ff5a176c299bd3b498efbee249891eb58f3b74.tar.gz chromium_src-23ff5a176c299bd3b498efbee249891eb58f3b74.tar.bz2 |
Upgrade AudioBus to support wrapping, interleaving.
Introduces AudioBus::WrapMemory(...) for constructing an
AudioBus object via an existing block of memory.
Introduces AudioBus::CalculateMemorySize(...) for sizing
the shared memory without having to create an empty AudioBus.
Introduces AudioBus::FromInterleaved(...) and AudioBus::
ToInterleaved() for converting between formats. Fixes off
by one errors in our existing audio_util implmentations.
Removes InterleaveFloatToInt from audio_util.
Removes data(), data_size() methods in favor of CopyTo()
helper method combined with AudioBus::WrapMemory().
And of course adds new tests for all of the above.
This CL is an extraction of all AudioBus functionality
required to land the WIP CL which converts browser side to
use AudioBus: http://codereview.chromium.org/10832285/
BUG=114700, 120319
TEST=unit tests under ASAN.
Review URL: https://chromiumcodereview.appspot.com/10824304
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@152494 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_input_device.cc | 11 | ||||
-rw-r--r-- | media/audio/audio_output_device.cc | 8 | ||||
-rw-r--r-- | media/audio/audio_util.cc | 50 | ||||
-rw-r--r-- | media/audio/audio_util.h | 11 | ||||
-rw-r--r-- | media/base/audio_bus.cc | 203 | ||||
-rw-r--r-- | media/base/audio_bus.h | 47 | ||||
-rw-r--r-- | media/base/audio_bus_unittest.cc | 199 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer.cc | 3 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl.cc | 21 | ||||
-rw-r--r-- | media/filters/pipeline_integration_test.cc | 2 |
10 files changed, 406 insertions, 149 deletions
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc index b1ab6f8..41bd491 100644 --- a/media/audio/audio_input_device.cc +++ b/media/audio/audio_input_device.cc @@ -9,7 +9,6 @@ #include "base/threading/thread_restrictions.h" #include "base/time.h" #include "media/audio/audio_manager_base.h" -#include "media/audio/audio_util.h" #include "media/base/audio_bus.h" namespace media { @@ -333,15 +332,7 @@ void AudioInputDevice::AudioThreadCallback::Process(int pending_data) { // Deinterleave each channel and convert to 32-bit floating-point // with nominal range -1.0 -> +1.0. - for (int channel_index = 0; channel_index < audio_bus_->channels(); - ++channel_index) { - DeinterleaveAudioChannel(memory, - audio_bus_->channel(channel_index), - audio_bus_->channels(), - channel_index, - bytes_per_sample, - audio_bus_->frames()); - } + audio_bus_->FromInterleaved(memory, audio_bus_->frames(), bytes_per_sample); // Deliver captured data to the client in floating point format // and update the audio-delay measurement. diff --git a/media/audio/audio_output_device.cc b/media/audio/audio_output_device.cc index 994fa40..2f8f811 100644 --- a/media/audio/audio_output_device.cc +++ b/media/audio/audio_output_device.cc @@ -270,10 +270,10 @@ void AudioOutputDevice::AudioThreadCallback::Process(int pending_data) { audio_bus_.get(), audio_delay_milliseconds); // Interleave, scale, and clip to int. - // TODO(dalecurtis): Remove this when we have float everywhere. - InterleaveFloatToInt( - audio_bus_.get(), shared_memory_.memory(), num_frames, - audio_parameters_.bits_per_sample() / 8); + // TODO(dalecurtis): Remove this when we have float everywhere: + // http://crbug.com/114700 + audio_bus_->ToInterleaved(num_frames, audio_parameters_.bits_per_sample() / 8, + shared_memory_.memory()); // Let the host know we are done. SetActualDataSizeInBytes( diff --git a/media/audio/audio_util.cc b/media/audio/audio_util.cc index d54f277..56bc14d 100644 --- a/media/audio/audio_util.cc +++ b/media/audio/audio_util.cc @@ -182,6 +182,8 @@ bool FoldChannels(void* buf, return false; } +// TODO(dalecurtis): Delete once everywhere is using the AudioBus version: +// http://crbug.com/120319. bool DeinterleaveAudioChannel(void* source, float* destination, int channels, @@ -228,54 +230,6 @@ bool DeinterleaveAudioChannel(void* source, return false; } -// |Format| is the destination type, |Fixed| is a type larger than |Format| -// such that operations can be made without overflowing. -template<class Format, class Fixed> -static void InterleaveFloatToInt(const AudioBus* source, - void* dst_bytes, size_t number_of_frames) { - Format* destination = reinterpret_cast<Format*>(dst_bytes); - Fixed max_value = std::numeric_limits<Format>::max(); - Fixed min_value = std::numeric_limits<Format>::min(); - - Format bias = 0; - if (!std::numeric_limits<Format>::is_signed) { - bias = max_value / 2; - max_value = bias; - min_value = -(bias - 1); - } - - int channels = source->channels(); - for (int i = 0; i < channels; ++i) { - const float* channel_data = source->channel(i); - for (size_t j = 0; j < number_of_frames; ++j) { - Fixed sample = max_value * channel_data[j]; - if (sample > max_value) - sample = max_value; - else if (sample < min_value) - sample = min_value; - - destination[j * channels + i] = static_cast<Format>(sample) + bias; - } - } -} - -void InterleaveFloatToInt(const AudioBus* source, void* dst, - size_t number_of_frames, int bytes_per_sample) { - switch (bytes_per_sample) { - case 1: - InterleaveFloatToInt<uint8, int32>(source, dst, number_of_frames); - break; - case 2: - InterleaveFloatToInt<int16, int32>(source, dst, number_of_frames); - break; - case 4: - InterleaveFloatToInt<int32, int64>(source, dst, number_of_frames); - break; - default: - break; - } -} - // TODO(enal): use template specialization and size-specific intrinsics. // Call is on the time-critical path, and by using SSE/AVX // instructions we can speed things up by ~4-8x, more for the case diff --git a/media/audio/audio_util.h b/media/audio/audio_util.h index d25fdf9..3a2f80e 100644 --- a/media/audio/audio_util.h +++ b/media/audio/audio_util.h @@ -80,17 +80,6 @@ MEDIA_EXPORT bool DeinterleaveAudioChannel(void* source, int bytes_per_sample, size_t number_of_frames); -// InterleaveFloatToInt scales, clips, and interleaves the planar -// floating-point audio contained in |source| to the |destination|. -// The floating-point data is in a canonical range of -1.0 -> +1.0. -// The size of the |source| vector determines the number of channels. -// The |destination| buffer is assumed to be large enough to hold the -// result. Thus it must be at least size: number_of_frames * source.size() -MEDIA_EXPORT void InterleaveFloatToInt(const AudioBus* audio_bus, - void* destination, - size_t number_of_frames, - int bytes_per_sample); - // Returns the default audio output hardware sample-rate. MEDIA_EXPORT int GetAudioHardwareSampleRate(); diff --git a/media/base/audio_bus.cc b/media/base/audio_bus.cc index 4c43af7..a7d9ad0 100644 --- a/media/base/audio_bus.cc +++ b/media/base/audio_bus.cc @@ -12,40 +12,116 @@ namespace media { -// Ensure each channel is 16-byte aligned for easy SSE optimizations. -static const int kChannelAlignment = 16; - static bool IsAligned(void* ptr) { - return (reinterpret_cast<uintptr_t>(ptr) & (kChannelAlignment - 1)) == 0U; + return (reinterpret_cast<uintptr_t>(ptr) & + (AudioBus::kChannelAlignment - 1)) == 0U; } -AudioBus::AudioBus(int channels, int frames) - : frames_(frames) { +// Calculates the required size for an AudioBus with the given params, sets +// |aligned_frames| to the actual frame length of each channel array. +static int CalculateMemorySizeInternal(int channels, int frames, + int* aligned_frames) { + CHECK(aligned_frames); + // Choose a size such that each channel will be aligned by kChannelAlignment + // when stored in a contiguous block. + *aligned_frames = + ((frames * sizeof(float) + AudioBus::kChannelAlignment - 1) & + ~(AudioBus::kChannelAlignment - 1)) / sizeof(float); + return sizeof(float) * channels * (*aligned_frames); +} + +// |Format| is the destination type, |Fixed| is a type larger than |Format| +// such that operations can be made without overflowing. +template<class Format, class Fixed> +static void FromInterleavedInternal(const void* src, int frames, + AudioBus* dest) { + const Format* source = static_cast<const Format*>(src); + + static const Fixed kBias = std::numeric_limits<Format>::is_signed ? 0 : + std::numeric_limits<Format>::max() / 2 + 1; + static const float kMaxScale = 1.0f / (kBias ? kBias - 1 : + std::numeric_limits<Format>::max()); + static const float kMinScale = 1.0f / (kBias ? kBias : + -static_cast<Fixed>(std::numeric_limits<Format>::min())); + + int channels = dest->channels(); + for (int ch = 0; ch < channels; ++ch) { + float* channel_data = dest->channel(ch); + for (int i = 0, offset = ch; i < frames; ++i, offset += channels) { + Fixed v = static_cast<Fixed>(source[offset]) - kBias; + channel_data[i] = v * (v < 0 ? kMinScale : kMaxScale); + } + } +} + +// |Format| is the destination type, |Fixed| is a type larger than |Format| +// such that operations can be made without overflowing. +template<class Format, class Fixed> +static void ToInterleavedInternal(const AudioBus* source, int frames, + void* dst) { + Format* dest = static_cast<Format*>(dst); + + static const Format kBias = std::numeric_limits<Format>::is_signed ? 0 : + std::numeric_limits<Format>::max() / 2 + 1; + static const Fixed kMaxValue = kBias ? kBias - 1 : + std::numeric_limits<Format>::max(); + static const Fixed kMinValue = kBias ? -kBias : + std::numeric_limits<Format>::min(); + + int channels = source->channels(); + for (int ch = 0; ch < channels; ++ch) { + const float* channel_data = source->channel(ch); + for (int i = 0, offset = ch; i < frames; ++i, offset += channels) { + float v = channel_data[i]; + Fixed sample = v * (v < 0 ? -kMinValue : kMaxValue); + + if (sample > kMaxValue) + sample = kMaxValue; + else if (sample < kMinValue) + sample = kMinValue; + + dest[offset] = static_cast<Format>(sample) + kBias; + } + } +} + +static void ValidateConfig(int channels, int frames) { CHECK_GT(frames, 0); CHECK_LE(frames, limits::kMaxSamplesPerPacket); CHECK_GT(channels, 0); CHECK_LE(channels, limits::kMaxChannels); DCHECK_LT(limits::kMaxSamplesPerPacket * limits::kMaxChannels, std::numeric_limits<int>::max()); +} - // Choose a size such that each channel is aligned by kChannelAlignment. - int aligned_frames = - (frames_ + kChannelAlignment - 1) & ~(kChannelAlignment - 1); - data_size_ = sizeof(float) * channels * aligned_frames; +AudioBus::AudioBus(int channels, int frames) + : frames_(frames) { + ValidateConfig(channels, frames_); + + int aligned_frames = 0; + int size = CalculateMemorySizeInternal(channels, frames, &aligned_frames); data_.reset(static_cast<float*>(base::AlignedAlloc( - data_size_, kChannelAlignment))); + size, AudioBus::kChannelAlignment))); - // Separate audio data out into channels for easy lookup later. - channel_data_.reserve(channels); - for (int i = 0; i < channels; ++i) - channel_data_.push_back(data_.get() + i * aligned_frames); + BuildChannelData(channels, aligned_frames, data_.get()); +} + +AudioBus::AudioBus(int channels, int frames, float* data) + : frames_(frames) { + ValidateConfig(channels, frames_); + + int aligned_frames = 0; + CalculateMemorySizeInternal(channels, frames, &aligned_frames); + + BuildChannelData(channels, aligned_frames, data); } AudioBus::AudioBus(int frames, const std::vector<float*>& channel_data) - : data_size_(0), - channel_data_(channel_data), + : channel_data_(channel_data), frames_(frames) { + ValidateConfig(channel_data_.size(), frames_); + // Sanity check wrapped vector for alignment and channel count. for (size_t i = 0; i < channel_data_.size(); ++i) DCHECK(IsAligned(channel_data_[i])); @@ -67,14 +143,21 @@ scoped_ptr<AudioBus> AudioBus::WrapVector( return scoped_ptr<AudioBus>(new AudioBus(frames, channel_data)); } -void* AudioBus::data() { - DCHECK(data_.get()); - return data_.get(); +scoped_ptr<AudioBus> AudioBus::WrapMemory(int channels, int frames, + void* data) { + // |data| must be aligned by AudioBus::kChannelAlignment. + CHECK(IsAligned(data)); + return scoped_ptr<AudioBus>(new AudioBus( + channels, frames, static_cast<float*>(data))); } -int AudioBus::data_size() const { - DCHECK(data_.get()); - return data_size_; +scoped_ptr<AudioBus> AudioBus::WrapMemory(const AudioParameters& params, + void* data) { + // |data| must be aligned by AudioBus::kChannelAlignment. + CHECK(IsAligned(data)); + return scoped_ptr<AudioBus>(new AudioBus( + params.channels(), params.frames_per_buffer(), + static_cast<float*>(data))); } void AudioBus::ZeroFrames(int frames) { @@ -87,4 +170,78 @@ void AudioBus::Zero() { ZeroFrames(frames_); } +int AudioBus::CalculateMemorySize(const AudioParameters& params) { + int aligned_frames = 0; + return CalculateMemorySizeInternal( + params.channels(), params.frames_per_buffer(), &aligned_frames); +} + +void AudioBus::BuildChannelData(int channels, int aligned_frames, float* data) { + DCHECK(IsAligned(data)); + DCHECK_EQ(channel_data_.size(), 0U); + // Separate audio data out into channels for easy lookup later. Figure out + channel_data_.reserve(channels); + for (int i = 0; i < channels; ++i) + channel_data_.push_back(data + i * aligned_frames); +} + +// TODO(dalecurtis): See if intrinsic optimizations help any here. +void AudioBus::FromInterleaved(const void* source, int frames, + int bytes_per_sample) { + DCHECK_LE(frames, frames_); + switch (bytes_per_sample) { + case 1: + FromInterleavedInternal<uint8, int16>(source, frames, this); + break; + case 2: + FromInterleavedInternal<int16, int32>(source, frames, this); + break; + case 4: + FromInterleavedInternal<int32, int64>(source, frames, this); + break; + default: + NOTREACHED() << "Unsupported bytes per sample encountered."; + Zero(); + return; + } + + // Zero any remaining frames. + int remaining_frames = (frames_ - frames); + if (remaining_frames) { + for (int ch = 0; ch < channels(); ++ch) + memset(channel(ch) + frames, 0, sizeof(*channel(ch)) * remaining_frames); + } +} + +// TODO(dalecurtis): See if intrinsic optimizations help any here. +void AudioBus::ToInterleaved(int frames, int bytes_per_sample, + void* dest) const { + DCHECK_LE(frames, frames_); + switch (bytes_per_sample) { + case 1: + ToInterleavedInternal<uint8, int16>(this, frames, dest); + break; + case 2: + ToInterleavedInternal<int16, int32>(this, frames, dest); + break; + case 4: + ToInterleavedInternal<int32, int64>(this, frames, dest); + break; + default: + NOTREACHED() << "Unsupported bytes per sample encountered."; + memset(dest, 0, frames * bytes_per_sample); + return; + } +} + +void AudioBus::CopyTo(AudioBus* dest) const { + DCHECK_EQ(channels(), dest->channels()); + DCHECK_EQ(frames(), dest->frames()); + + // Since we don't know if the other AudioBus is wrapped or not (and we don't + // want to care), just copy using the public channel() accessors. + for (int i = 0; i < channels(); ++i) + memcpy(dest->channel(i), channel(i), sizeof(*channel(i)) * frames()); +} + } // namespace media diff --git a/media/base/audio_bus.h b/media/base/audio_bus.h index 0dff195..acf6491 100644 --- a/media/base/audio_bus.h +++ b/media/base/audio_bus.h @@ -15,28 +15,47 @@ namespace media { class AudioParameters; // Scoped container for "busing" audio channel data around. Each channel is -// stored in planar format and guaranteed to have a 16-byte alignment. AudioBus -// objects can be created normally or via wrapping. Normally, AudioBus will -// dice up a contiguous memory block for channel data. When wrapped, AudioBus -// instead routes requests for channel data to the wrapped object. +// stored in planar format and guaranteed to be aligned by kChannelAlignment. +// AudioBus objects can be created normally or via wrapping. Normally, AudioBus +// will dice up a contiguous memory block for channel data. When wrapped, +// AudioBus instead routes requests for channel data to the wrapped object. class MEDIA_EXPORT AudioBus { public: + // Guaranteed alignment of each channel's data; use 16-byte alignment for easy + // SSE optimizations. + enum { kChannelAlignment = 16 }; + // Creates a new AudioBus and allocates |channels| of length |frames|. Uses - // channels() and frames_per_buffer() if given an AudioParameters object. + // channels() and frames_per_buffer() from AudioParameters if given. static scoped_ptr<AudioBus> Create(int channels, int frames); static scoped_ptr<AudioBus> Create(const AudioParameters& params); // Creates a new AudioBus from an existing channel vector. Does not transfer // ownership of |channel_data| to AudioBus; i.e., |channel_data| must outlive - // the returned AudioBus. Each channel pointer must be 16-byte aligned. + // the returned AudioBus. Each channel must be aligned by kChannelAlignment. static scoped_ptr<AudioBus> WrapVector( int frames, const std::vector<float*>& channel_data); - // Returns a raw pointer to internal channel data. Useful for copying state - // between two AudioBus objects created with the same parameters. data_size() - // is in bytes. Can not be used with an AudioBus constructed via wrapping. - void* data(); - int data_size() const; + // Creates a new AudioBus by wrapping an existing block of memory. Block must + // be at least CalculateMemorySize() bytes in size. |data| must outlive the + // returned AudioBus. |data| must be aligned by kChannelAlignment. + static scoped_ptr<AudioBus> WrapMemory(int channels, int frames, void* data); + static scoped_ptr<AudioBus> WrapMemory(const AudioParameters& params, + void* data); + // Returns the required memory size to use the WrapMemory() method. + static int CalculateMemorySize(const AudioParameters& params); + + // Helper methods for converting an AudioBus from and to interleaved integer + // data. Expects interleaving to be [ch0, ch1, ..., chN, ch0, ch1, ...] with + // |bytes_per_sample| per value. Values are scaled and bias corrected during + // conversion. ToInterleaved() will also clip values to format range. + // Handles uint8, int16, and int32 currently. + void FromInterleaved(const void* source, int frames, int bytes_per_sample); + void ToInterleaved(int frames, int bytes_per_sample, void* dest) const; + + // Helper method for copying channel data from one AudioBus to another. Both + // AudioBus object must have the same frames() and channels(). + void CopyTo(AudioBus* dest) const; // Returns a raw pointer to the requested channel. Pointer is guaranteed to // have a 16-byte alignment. @@ -55,11 +74,15 @@ class MEDIA_EXPORT AudioBus { ~AudioBus(); AudioBus(int channels, int frames); + AudioBus(int channels, int frames, float* data); AudioBus(int frames, const std::vector<float*>& channel_data); + // Helper method for building |channel_data_| from a block of memory. |data| + // must be at least BlockSize() bytes in size. + void BuildChannelData(int channels, int aligned_frame, float* data); + // Contiguous block of channel memory. scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data_; - int data_size_; std::vector<float*> channel_data_; int frames_; diff --git a/media/base/audio_bus_unittest.cc b/media/base/audio_bus_unittest.cc index 1f60c32..f6c4b64 100644 --- a/media/base/audio_bus_unittest.cc +++ b/media/base/audio_bus_unittest.cc @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <limits> + +#include "base/stringprintf.h" +#include "base/time.h" #include "media/audio/audio_parameters.h" #include "media/base/audio_bus.h" #include "media/base/channel_layout.h" @@ -9,9 +13,9 @@ static const int kChannels = 6; static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_5_1; -// Use a buffer size which is intentionally not a multiple of 16; see -// kChannelAlignment in audio_bus.cc. -static const int kFrameCount = 1234; +// Use a buffer size which is intentionally not a multiple of kChannelAlignment. +static const int kFrameCount = media::AudioBus::kChannelAlignment * 128 - 1; +static const int kSampleRate = 48000; namespace media { @@ -34,12 +38,25 @@ class AudioBusTest : public testing::Test { ASSERT_FLOAT_EQ(value, data[i]); } + // Verify values for each channel in |result| against |expected|. + void VerifyBus(const AudioBus* result, const AudioBus* expected) { + ASSERT_EQ(expected->channels(), result->channels()); + ASSERT_EQ(expected->frames(), result->frames()); + for (int ch = 0; ch < result->channels(); ++ch) { + for (int i = 0; i < result->frames(); ++i) { + SCOPED_TRACE(base::StringPrintf("ch=%d, i=%d", ch, i)); + ASSERT_FLOAT_EQ(expected->channel(ch)[i], result->channel(ch)[i]); + } + } + } + // Read and write to the full extent of the allocated channel data. Also test // the Zero() method and verify it does as advertised. Also test data if data - // is 16-byte aligned as advertised (see kChannelAlignment in audio_bus.cc). + // is 16-byte aligned as advertised (see kChannelAlignment in audio_bus.h). void VerifyChannelData(AudioBus* bus) { for (int i = 0; i < bus->channels(); ++i) { - ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(bus->channel(i)) & 0x0F); + ASSERT_EQ(0U, reinterpret_cast<uintptr_t>( + bus->channel(i)) & (AudioBus::kChannelAlignment - 1)); std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i); } @@ -51,6 +68,22 @@ class AudioBusTest : public testing::Test { VerifyValue(bus->channel(i), bus->frames(), 0); } + // Verify copying to and from |bus1| and |bus2|. + void CopyTest(AudioBus* bus1, AudioBus* bus2) { + // Fill |bus1| with dummy data. + for (int i = 0; i < bus1->channels(); ++i) + std::fill(bus1->channel(i), bus1->channel(i) + bus1->frames(), i); + + // Verify copy from |bus1| to |bus2|. + bus2->Zero(); + bus1->CopyTo(bus2); + VerifyBus(bus1, bus2); + + // Verify copy from |bus2| to |bus1|. + bus1->Zero(); + bus2->CopyTo(bus1); + VerifyBus(bus2, bus1); + } protected: std::vector<float*> data_; @@ -68,18 +101,18 @@ TEST_F(AudioBusTest, Create) { // Verify Create(...) using AudioParameters works as advertised. TEST_F(AudioBusTest, CreateUsingAudioParameters) { scoped_ptr<AudioBus> bus = AudioBus::Create(AudioParameters( - AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, 48000, 32, + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32, kFrameCount)); VerifyParams(bus.get()); VerifyChannelData(bus.get()); } -// Verify an AudioBus created via wrapping works as advertised. -TEST_F(AudioBusTest, Wrap) { +// Verify an AudioBus created via wrapping a vector works as advertised. +TEST_F(AudioBusTest, WrapVector) { data_.reserve(kChannels); for (int i = 0; i < kChannels; ++i) { data_.push_back(static_cast<float*>(base::AlignedAlloc( - sizeof(*data_[i]) * kFrameCount, 16))); + sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment))); } scoped_ptr<AudioBus> bus = AudioBus::WrapVector(kFrameCount, data_); @@ -87,22 +120,70 @@ TEST_F(AudioBusTest, Wrap) { VerifyChannelData(bus.get()); } +// Verify an AudioBus created via wrapping a memory block works as advertised. +TEST_F(AudioBusTest, WrapMemory) { + AudioParameters params( + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32, + kFrameCount); + int data_size = AudioBus::CalculateMemorySize(params); + scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data(static_cast<float*>( + base::AlignedAlloc(data_size, AudioBus::kChannelAlignment))); + + // Fill the memory with a test value we can check for after wrapping. + static const float kTestValue = 3; + std::fill( + data.get(), data.get() + data_size / sizeof(*data.get()), kTestValue); + + scoped_ptr<AudioBus> bus = AudioBus::WrapMemory(params, data.get()); + // Verify the test value we filled prior to wrapping. + for (int i = 0; i < bus->channels(); ++i) + VerifyValue(bus->channel(i), bus->frames(), kTestValue); + VerifyParams(bus.get()); + VerifyChannelData(bus.get()); + + // Verify the channel vectors lie within the provided memory block. + EXPECT_GE(bus->channel(0), data.get()); + EXPECT_LT(bus->channel(bus->channels() - 1) + bus->frames(), + data.get() + data_size / sizeof(*data.get())); +} + // Simulate a shared memory transfer and verify results. -TEST_F(AudioBusTest, AudioData) { +TEST_F(AudioBusTest, CopyTo) { + // Create one bus with AudioParameters and the other through direct values to + // test for parity between the Create() functions. + AudioParameters params( + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32, + kFrameCount); scoped_ptr<AudioBus> bus1 = AudioBus::Create(kChannels, kFrameCount); - scoped_ptr<AudioBus> bus2 = AudioBus::Create(kChannels, kFrameCount); + scoped_ptr<AudioBus> bus2 = AudioBus::Create(params); - // Fill |bus1| with dummy data and zero out |bus2|. - for (int i = 0; i < bus1->channels(); ++i) - std::fill(bus1->channel(i), bus1->channel(i) + bus1->frames(), i); - bus2->Zero(); + { + SCOPED_TRACE("Created"); + CopyTest(bus1.get(), bus2.get()); + } + { + SCOPED_TRACE("Wrapped Vector"); + // Try a copy to an AudioBus wrapping a vector. + data_.reserve(kChannels); + for (int i = 0; i < kChannels; ++i) { + data_.push_back(static_cast<float*>(base::AlignedAlloc( + sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment))); + } - // Transfer audio data from |bus1| to |bus2|. - ASSERT_EQ(bus1->data_size(), bus2->data_size()); - memcpy(bus2->data(), bus1->data(), bus1->data_size()); + bus2 = AudioBus::WrapVector(kFrameCount, data_); + CopyTest(bus1.get(), bus2.get()); + } + { + SCOPED_TRACE("Wrapped Memory"); + // Try a copy to an AudioBus wrapping a memory block. + scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data( + static_cast<float*>(base::AlignedAlloc( + AudioBus::CalculateMemorySize(params), + AudioBus::kChannelAlignment))); - for (int i = 0; i < bus2->channels(); ++i) - VerifyValue(bus2->channel(i), bus2->frames(), i); + bus2 = AudioBus::WrapMemory(params, data.get()); + CopyTest(bus1.get(), bus2.get()); + } } // Verify Zero() and ZeroFrames(...) utility methods work as advertised. @@ -124,4 +205,82 @@ TEST_F(AudioBusTest, Zero) { VerifyValue(bus->channel(i), bus->frames(), 0); } +// Each test vector represents two channels of data in the following arbitrary +// layout: <min, zero, max, min, zero, max, zero, zero>. +static const int kTestVectorSize = 8; +static const uint8 kTestVectorUint8[kTestVectorSize] = { + 0, -kint8min, kuint8max, 0, -kint8min, kuint8max, -kint8min, -kint8min }; +static const int16 kTestVectorInt16[kTestVectorSize] = { + kint16min, 0, kint16max, kint16min, 0, kint16max, 0, 0 }; +static const int32 kTestVectorInt32[kTestVectorSize] = { + kint32min, 0, kint32max, kint32min, 0, kint32max, 0, 0 }; + +// Expected results. +static const int kTestVectorFrames = kTestVectorSize / 2; +static const float kTestVectorResult[][kTestVectorFrames] = { + { -1, 1, 0, 0 }, { 0, -1, 1, 0 }}; +static const int kTestVectorChannels = arraysize(kTestVectorResult); + +// Verify FromInterleaved() deinterleaves audio in supported formats correctly. +TEST_F(AudioBusTest, FromInterleaved) { + scoped_ptr<AudioBus> bus = AudioBus::Create( + kTestVectorChannels, kTestVectorFrames); + scoped_ptr<AudioBus> expected = AudioBus::Create( + kTestVectorChannels, kTestVectorFrames); + for (int ch = 0; ch < kTestVectorChannels; ++ch) { + memcpy(expected->channel(ch), kTestVectorResult[ch], + kTestVectorFrames * sizeof(*expected->channel(ch))); + } + { + SCOPED_TRACE("uint8"); + bus->FromInterleaved( + kTestVectorUint8, kTestVectorFrames, sizeof(*kTestVectorUint8)); + VerifyBus(bus.get(), expected.get()); + } + { + SCOPED_TRACE("int16"); + bus->FromInterleaved( + kTestVectorInt16, kTestVectorFrames, sizeof(*kTestVectorInt16)); + VerifyBus(bus.get(), expected.get()); + } + { + SCOPED_TRACE("int32"); + bus->FromInterleaved( + kTestVectorInt32, kTestVectorFrames, sizeof(*kTestVectorInt32)); + VerifyBus(bus.get(), expected.get()); + } +} + +// Verify ToInterleaved() interleaves audio in suported formats correctly. +TEST_F(AudioBusTest, ToInterleaved) { + scoped_ptr<AudioBus> bus = AudioBus::Create( + kTestVectorChannels, kTestVectorFrames); + // Fill the bus with our test vector. + for (int ch = 0; ch < kTestVectorChannels; ++ch) { + memcpy(bus->channel(ch), kTestVectorResult[ch], + kTestVectorFrames * sizeof(*bus->channel(ch))); + } + { + SCOPED_TRACE("uint8"); + uint8 test_array[arraysize(kTestVectorUint8)]; + bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorUint8), test_array); + ASSERT_EQ(memcmp( + test_array, kTestVectorUint8, arraysize(kTestVectorUint8)), 0); + } + { + SCOPED_TRACE("int16"); + int16 test_array[arraysize(kTestVectorInt16)]; + bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt16), test_array); + ASSERT_EQ(memcmp( + test_array, kTestVectorInt16, arraysize(kTestVectorInt16)), 0); + } + { + SCOPED_TRACE("int32"); + int32 test_array[arraysize(kTestVectorInt32)]; + bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt32), test_array); + ASSERT_EQ(memcmp( + test_array, kTestVectorInt32, arraysize(kTestVectorInt32)), 0); + } +} + } // namespace media diff --git a/media/base/audio_renderer_mixer.cc b/media/base/audio_renderer_mixer.cc index 503f139..b9ec259 100644 --- a/media/base/audio_renderer_mixer.cc +++ b/media/base/audio_renderer_mixer.cc @@ -120,9 +120,6 @@ void AudioRendererMixer::ProvideInput(AudioBus* audio_bus) { VectorFMAC(mixer_input_audio_bus_->channel(i), volume, frames_filled, audio_bus->channel(i)); } - - // No need to clamp values as InterleaveFloatToInt() will take care of this - // for us later when data is transferred to the browser process. } } diff --git a/media/filters/audio_renderer_impl.cc b/media/filters/audio_renderer_impl.cc index 948e688..94ee20f 100644 --- a/media/filters/audio_renderer_impl.cc +++ b/media/filters/audio_renderer_impl.cc @@ -352,23 +352,10 @@ int AudioRendererImpl::Render(AudioBus* audio_bus, DCHECK_LE(bytes_filled, buf_size); UpdateEarliestEndTime(bytes_filled, request_delay, base::Time::Now()); - // Deinterleave each audio channel. - int channels = audio_bus->channels(); - for (int channel_index = 0; channel_index < channels; ++channel_index) { - media::DeinterleaveAudioChannel(buf.get(), - audio_bus->channel(channel_index), - channels, - channel_index, - bytes_per_frame / channels, - frames_filled); - - // If FillBuffer() didn't give us enough data then zero out the remainder. - if (frames_filled < audio_bus->frames()) { - int frames_to_zero = audio_bus->frames() - frames_filled; - memset(audio_bus->channel(channel_index) + frames_filled, 0, - sizeof(*audio_bus->channel(channel_index)) * frames_to_zero); - } - } + // Deinterleave audio data into the output bus. + audio_bus->FromInterleaved( + buf.get(), frames_filled, audio_parameters_.bits_per_sample() / 8); + return frames_filled; } diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index 5d982a4..c5353f3 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -265,7 +265,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) { ASSERT_TRUE(WaitUntilOnEnded()); EXPECT_EQ(GetVideoHash(), "f0be120a90a811506777c99a2cdf7cc1"); - EXPECT_EQ(GetAudioHash(), "6138555be3389e6aba4c8e6f70195d50"); + EXPECT_EQ(GetAudioHash(), "5699a4415b620e45b9d0aae531c9df76"); } TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) { |