diff options
author | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-20 01:59:39 +0000 |
---|---|---|
committer | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-20 01:59:39 +0000 |
commit | a9721dd681919183d4a9c9062475e6c21a7a339a (patch) | |
tree | 8deb998359ca3133b5da2c9259b36d5f37f9e8d0 /media | |
parent | 185fd4761b276f12d7dc0e6d90f547a9c6570055 (diff) | |
download | chromium_src-a9721dd681919183d4a9c9062475e6c21a7a339a.zip chromium_src-a9721dd681919183d4a9c9062475e6c21a7a339a.tar.gz chromium_src-a9721dd681919183d4a9c9062475e6c21a7a339a.tar.bz2 |
Introduce AudioHash for fuzzy audio matching.
The old MD5 approach is too strict due to differences in floating
point implementations on each platform. AudioHash provides a simple
algorithm for ensuring signals are within roughly -40 dBFS.
BUG=168204
TEST=media_unittests on Linux && Win.
Review URL: https://chromiumcodereview.appspot.com/12925002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@189197 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/null_audio_sink.cc | 50 | ||||
-rw-r--r-- | media/audio/null_audio_sink.h | 15 | ||||
-rw-r--r-- | media/base/audio_hash.cc | 51 | ||||
-rw-r--r-- | media/base/audio_hash.h | 59 | ||||
-rw-r--r-- | media/base/audio_hash_unittest.cc | 146 | ||||
-rw-r--r-- | media/filters/audio_file_reader_unittest.cc | 52 | ||||
-rw-r--r-- | media/filters/pipeline_integration_test.cc | 7 | ||||
-rw-r--r-- | media/filters/pipeline_integration_test_base.h | 2 | ||||
-rw-r--r-- | media/media.gyp | 3 |
9 files changed, 289 insertions, 96 deletions
diff --git a/media/audio/null_audio_sink.cc b/media/audio/null_audio_sink.cc index 8fee966..5c4f67a 100644 --- a/media/audio/null_audio_sink.cc +++ b/media/audio/null_audio_sink.cc @@ -6,9 +6,8 @@ #include "base/bind.h" #include "base/message_loop_proxy.h" -#include "base/stringprintf.h" -#include "base/sys_byteorder.h" #include "media/audio/fake_audio_consumer.h" +#include "media/base/audio_hash.h" namespace media { @@ -17,8 +16,6 @@ NullAudioSink::NullAudioSink( : initialized_(false), playing_(false), callback_(NULL), - hash_audio_for_testing_(false), - channels_(0), message_loop_(message_loop) { } @@ -27,16 +24,7 @@ NullAudioSink::~NullAudioSink() {} void NullAudioSink::Initialize(const AudioParameters& params, RenderCallback* callback) { DCHECK(!initialized_); - fake_consumer_.reset(new FakeAudioConsumer(message_loop_, params)); - - if (hash_audio_for_testing_) { - channels_ = params.channels(); - md5_channel_contexts_.reset(new base::MD5Context[params.channels()]); - for (int i = 0; i < params.channels(); i++) - base::MD5Init(&md5_channel_contexts_[i]); - } - callback_ = callback; initialized_ = true; } @@ -85,47 +73,19 @@ void NullAudioSink::CallRender(AudioBus* audio_bus) { DCHECK(message_loop_->BelongsToCurrentThread()); int frames_received = callback_->Render(audio_bus, 0); - if (!hash_audio_for_testing_ || frames_received <= 0) + if (!audio_hash_ || frames_received <= 0) return; - DCHECK_EQ(sizeof(float), sizeof(uint32)); - int channels = audio_bus->channels(); - for (int channel_idx = 0; channel_idx < channels; ++channel_idx) { - float* channel = audio_bus->channel(channel_idx); - for (int frame_idx = 0; frame_idx < frames_received; frame_idx++) { - // Convert float to uint32 w/o conversion loss. - uint32 frame = base::ByteSwapToLE32(bit_cast<uint32>(channel[frame_idx])); - base::MD5Update(&md5_channel_contexts_[channel_idx], base::StringPiece( - reinterpret_cast<char*>(&frame), sizeof(frame))); - } - } + audio_hash_->Update(audio_bus, frames_received); } void NullAudioSink::StartAudioHashForTesting() { DCHECK(!initialized_); - hash_audio_for_testing_ = true; + audio_hash_.reset(new AudioHash()); } std::string NullAudioSink::GetAudioHashForTesting() { - DCHECK(hash_audio_for_testing_); - - base::MD5Digest digest; - if (channels_ == 0) { - // If initialize failed or was never called, ensure we return an empty hash. - base::MD5Context context; - base::MD5Init(&context); - base::MD5Final(&digest, &context); - } else { - // Hash all channels into the first channel. - for (int i = 1; i < channels_; i++) { - base::MD5Final(&digest, &md5_channel_contexts_[i]); - base::MD5Update(&md5_channel_contexts_[0], base::StringPiece( - reinterpret_cast<char*>(&digest), sizeof(base::MD5Digest))); - } - base::MD5Final(&digest, &md5_channel_contexts_[0]); - } - - return base::MD5DigestToBase16(digest); + return audio_hash_ ? audio_hash_->ToString() : ""; } } // namespace media diff --git a/media/audio/null_audio_sink.h b/media/audio/null_audio_sink.h index 9fb7237..37022b7 100644 --- a/media/audio/null_audio_sink.h +++ b/media/audio/null_audio_sink.h @@ -5,7 +5,8 @@ #ifndef MEDIA_AUDIO_NULL_AUDIO_SINK_H_ #define MEDIA_AUDIO_NULL_AUDIO_SINK_H_ -#include "base/md5.h" +#include <string> + #include "base/memory/scoped_ptr.h" #include "media/base/audio_renderer_sink.h" @@ -15,6 +16,7 @@ class MessageLoopProxy; namespace media { class AudioBus; +class AudioHash; class FakeAudioConsumer; class MEDIA_EXPORT NullAudioSink @@ -31,11 +33,10 @@ class MEDIA_EXPORT NullAudioSink virtual void Play() OVERRIDE; virtual bool SetVolume(double volume) OVERRIDE; - // Enables audio frame hashing and reinitializes the MD5 context. Must be - // called prior to Initialize(). + // Enables audio frame hashing. Must be called prior to Initialize(). void StartAudioHashForTesting(); - // Returns the MD5 hash of all audio frames seen since the last reset. + // Returns the hash of all audio frames seen since construction. std::string GetAudioHashForTesting(); protected: @@ -49,10 +50,8 @@ class MEDIA_EXPORT NullAudioSink bool playing_; RenderCallback* callback_; - // Controls whether or not a running MD5 hash is computed for audio frames. - bool hash_audio_for_testing_; - int channels_; - scoped_array<base::MD5Context> md5_channel_contexts_; + // Controls whether or not a running hash is computed for audio frames. + scoped_ptr<AudioHash> audio_hash_; scoped_refptr<base::MessageLoopProxy> message_loop_; scoped_ptr<FakeAudioConsumer> fake_consumer_; diff --git a/media/base/audio_hash.cc b/media/base/audio_hash.cc new file mode 100644 index 0000000..5cea0c3 --- /dev/null +++ b/media/base/audio_hash.cc @@ -0,0 +1,51 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES +#include <cmath> + +#include "media/base/audio_hash.h" + +#include "base/stringprintf.h" +#include "media/base/audio_bus.h" + +namespace media { + +AudioHash::AudioHash() + : audio_hash_(), + hash_count_(0) { + COMPILE_ASSERT(arraysize(audio_hash_) == kHashBuckets, audio_hash_size_error); +} + +AudioHash::~AudioHash() {} + +void AudioHash::Update(const AudioBus* audio_bus, int frames) { + for (int ch = 0; ch < audio_bus->channels(); ++ch) { + const float* channel = audio_bus->channel(ch); + for (int i = 0; i < frames; ++i) { + const int kHashIndex = + (i * (ch + 1) + hash_count_) % arraysize(audio_hash_); + + // Mix in a sine wave with the result so we ensure that sequences of empty + // buffers don't result in an empty hash. + if (ch == 0) { + audio_hash_[kHashIndex] += channel[i] + sin(2.0 * M_PI * M_PI * i); + } else { + audio_hash_[kHashIndex] += channel[i]; + } + } + } + + ++hash_count_; +} + +std::string AudioHash::ToString() const { + std::string result; + for (size_t i = 0; i < arraysize(audio_hash_); ++i) + result += base::StringPrintf("%.2f,", audio_hash_[i]); + return result; +} + +} // namespace media
\ No newline at end of file diff --git a/media/base/audio_hash.h b/media/base/audio_hash.h new file mode 100644 index 0000000..66a2cf9 --- /dev/null +++ b/media/base/audio_hash.h @@ -0,0 +1,59 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_AUDIO_HASH_H_ +#define MEDIA_BASE_AUDIO_HASH_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "media/base/media_export.h" + +namespace media { + +class AudioBus; + +// Computes a running hash for a series of AudioBus objects. The hash is the +// sum of each sample bucketed based on the frame index, channel number, and +// current hash count. The hash was designed with two properties in mind: +// +// 1. Uniform error distribution across the input sample. +// 2. Resilience to error below a certain threshold. +// +// The first is achieved by using a simple summing approach and moving position +// weighting into the bucket choice. The second is handled during conversion to +// string by rounding out values to only two decimal places. +// +// Using only two decimal places allows for roughly -40 dBFS of error. For +// reference, SincResampler produces an RMS error of around -15 dBFS. See +// http://en.wikipedia.org/wiki/DBFS and http://crbug.com/168204 for more info. +class MEDIA_EXPORT AudioHash { + public: + AudioHash(); + ~AudioHash(); + + // Update current hash with the contents of the provided AudioBus. + void Update(const AudioBus* audio_bus, int frames); + + // Return a string representation of the current hash. + std::string ToString() const; + + private: + // Storage for the audio hash. The number of buckets controls the importance + // of position in the hash. A higher number reduces the chance of false + // positives related to incorrect sample position. Value chosen by dice roll. + enum { kHashBuckets = 6 }; + float audio_hash_[kHashBuckets]; + + // Essentially the number of times Update() has been called. Adds weight to + // the hash for the order of calls. + size_t hash_count_; + + DISALLOW_COPY_AND_ASSIGN(AudioHash); +}; + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_HASH_H_
\ No newline at end of file diff --git a/media/base/audio_hash_unittest.cc b/media/base/audio_hash_unittest.cc new file mode 100644 index 0000000..02b8310 --- /dev/null +++ b/media/base/audio_hash_unittest.cc @@ -0,0 +1,146 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "media/base/audio_bus.h" +#include "media/base/audio_hash.h" +#include "media/base/fake_audio_render_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kChannelCount = 2; +static const int kFrameCount = 1024; + +class AudioHashTest : public testing::Test { + public: + AudioHashTest() + : bus_one_(AudioBus::Create(kChannelCount, kFrameCount)), + bus_two_(AudioBus::Create(kChannelCount, kFrameCount)), + fake_callback_(0.01) { + + // Fill each channel in each bus with unique data. + GenerateUniqueChannels(bus_one_.get()); + GenerateUniqueChannels(bus_two_.get()); + } + + void GenerateUniqueChannels(AudioBus* audio_bus) { + // Use an AudioBus wrapper to avoid an extra memcpy when filling channels. + scoped_ptr<AudioBus> wrapped_bus = AudioBus::CreateWrapper(1); + wrapped_bus->set_frames(audio_bus->frames()); + + // Since FakeAudioRenderCallback generates only a single channel of unique + // audio data, we need to fill each channel manually. + for (int ch = 0; ch < audio_bus->channels(); ++ch) { + wrapped_bus->SetChannelData(0, audio_bus->channel(ch)); + fake_callback_.Render(wrapped_bus.get(), 0); + } + } + + virtual ~AudioHashTest() {} + + protected: + scoped_ptr<AudioBus> bus_one_; + scoped_ptr<AudioBus> bus_two_; + FakeAudioRenderCallback fake_callback_; + + DISALLOW_COPY_AND_ASSIGN(AudioHashTest); +}; + +// Ensure the same data hashes the same. +TEST_F(AudioHashTest, Equivalence) { + AudioHash hash_one; + hash_one.Update(bus_one_.get(), bus_one_->frames()); + + AudioHash hash_two; + hash_two.Update(bus_one_.get(), bus_one_->frames()); + + EXPECT_EQ(hash_one.ToString(), hash_two.ToString()); +} + +// Ensure sample order matters to the hash. +TEST_F(AudioHashTest, SampleOrder) { + AudioHash original_hash; + original_hash.Update(bus_one_.get(), bus_one_->frames()); + + // Swap a sample in the bus. + std::swap(bus_one_->channel(0)[0], bus_one_->channel(0)[1]); + + AudioHash swapped_hash; + swapped_hash.Update(bus_one_.get(), bus_one_->frames()); + + EXPECT_NE(original_hash.ToString(), swapped_hash.ToString()); +} + +// Ensure channel order matters to the hash. +TEST_F(AudioHashTest, ChannelOrder) { + AudioHash original_hash; + original_hash.Update(bus_one_.get(), bus_one_->frames()); + + // Reverse channel order for the same sample data. + const int channels = bus_one_->channels(); + scoped_ptr<AudioBus> swapped_ch_bus = AudioBus::CreateWrapper(channels); + swapped_ch_bus->set_frames(bus_one_->frames()); + for (int i = channels - 1; i >= 0; --i) + swapped_ch_bus->SetChannelData(channels - (i + 1), bus_one_->channel(i)); + + AudioHash swapped_hash; + swapped_hash.Update(swapped_ch_bus.get(), swapped_ch_bus->frames()); + + EXPECT_NE(original_hash.ToString(), swapped_hash.ToString()); +} + +// Ensure bus order matters to the hash. +TEST_F(AudioHashTest, BusOrder) { + AudioHash original_hash; + original_hash.Update(bus_one_.get(), bus_one_->frames()); + original_hash.Update(bus_two_.get(), bus_two_->frames()); + + AudioHash reordered_hash; + reordered_hash.Update(bus_two_.get(), bus_two_->frames()); + reordered_hash.Update(bus_one_.get(), bus_one_->frames()); + + EXPECT_NE(original_hash.ToString(), reordered_hash.ToString()); +} + +// Ensure bus order matters to the hash even with empty buses. +TEST_F(AudioHashTest, EmptyBusOrder) { + bus_one_->Zero(); + bus_two_->Zero(); + + AudioHash one_bus_hash; + one_bus_hash.Update(bus_one_.get(), bus_one_->frames()); + + AudioHash two_bus_hash; + two_bus_hash.Update(bus_one_.get(), bus_one_->frames()); + two_bus_hash.Update(bus_two_.get(), bus_two_->frames()); + + EXPECT_NE(one_bus_hash.ToString(), two_bus_hash.ToString()); +} + +// Ensure approximate hashes pass verification. +TEST_F(AudioHashTest, VerifySimilarHash) { + AudioHash hash_one; + hash_one.Update(bus_one_.get(), bus_one_->frames()); + + // Twiddle the values inside the first bus. + float* channel = bus_one_->channel(0); + for (int i = 0; i < bus_one_->frames(); i += bus_one_->frames() / 64) + channel[i] += 0.0001f; + + AudioHash hash_two; + hash_two.Update(bus_one_.get(), bus_one_->frames()); + + EXPECT_EQ(hash_one.ToString(), hash_two.ToString()); + + // Twiddle the values too much... + for (int i = 0; i < bus_one_->frames(); ++i) + channel[i] += 0.0001f; + + AudioHash hash_three; + hash_three.Update(bus_one_.get(), bus_one_->frames()); + EXPECT_NE(hash_one.ToString(), hash_three.ToString()); +} + +} // namespace media
\ No newline at end of file diff --git a/media/filters/audio_file_reader_unittest.cc b/media/filters/audio_file_reader_unittest.cc index 64c58e0..df0298a 100644 --- a/media/filters/audio_file_reader_unittest.cc +++ b/media/filters/audio_file_reader_unittest.cc @@ -3,11 +3,10 @@ // found in the LICENSE file. #include "base/logging.h" -#include "base/md5.h" #include "base/memory/scoped_ptr.h" -#include "base/sys_byteorder.h" #include "build/build_config.h" #include "media/base/audio_bus.h" +#include "media/base/audio_hash.h" #include "media/base/decoder_buffer.h" #include "media/base/test_data_util.h" #include "media/filters/audio_file_reader.h" @@ -28,38 +27,17 @@ class AudioFileReaderTest : public testing::Test { reader_.reset(new AudioFileReader(protocol_.get())); } - // Reads and the entire file provided to Initialize(). If NULL is specified - // for |audio_hash| MD5 checks are skipped. - void ReadAndVerify(const char* audio_hash, int expected_frames) { + // Reads and the entire file provided to Initialize(). + void ReadAndVerify(const char* expected_audio_hash, int expected_frames) { scoped_ptr<AudioBus> decoded_audio_data = AudioBus::Create( reader_->channels(), reader_->number_of_frames()); int actual_frames = reader_->Read(decoded_audio_data.get()); ASSERT_LE(actual_frames, decoded_audio_data->frames()); ASSERT_EQ(expected_frames, actual_frames); - // TODO(dalecurtis): Audio decoded in float does not have a consistent hash - // across platforms. Fix this: http://crbug.com/168204 - if (!audio_hash) - return; - - base::MD5Context md5_context; - base::MD5Init(&md5_context); - - DCHECK_EQ(sizeof(float), sizeof(uint32)); - int channels = decoded_audio_data->channels(); - for (int ch = 0; ch < channels; ++ch) { - float* channel = decoded_audio_data->channel(ch); - for (int i = 0; i < actual_frames; ++i) { - // Convert float to uint32 w/o conversion loss. - uint32 frame = base::ByteSwapToLE32(bit_cast<uint32>(channel[i])); - base::MD5Update(&md5_context, base::StringPiece( - reinterpret_cast<char*>(&frame), sizeof(frame))); - } - } - - base::MD5Digest digest; - base::MD5Final(&digest, &md5_context); - EXPECT_EQ(audio_hash, base::MD5DigestToBase16(digest)); + AudioHash audio_hash; + audio_hash.Update(decoded_audio_data.get(), actual_frames); + EXPECT_EQ(expected_audio_hash, audio_hash.ToString()); } void RunTest(const char* fn, const char* hash, int channels, int sample_rate, @@ -103,43 +81,43 @@ TEST_F(AudioFileReaderTest, InvalidFile) { } TEST_F(AudioFileReaderTest, WithVideo) { - RunTest("bear.ogv", NULL, 2, 44100, + RunTest("bear.ogv", "-2.49,-0.75,0.38,1.60,-0.15,-1.22,", 2, 44100, base::TimeDelta::FromMicroseconds(1011520), 44608, 44608); } TEST_F(AudioFileReaderTest, Vorbis) { - RunTest("sfx.ogg", NULL, 1, 44100, + RunTest("sfx.ogg", "4.36,4.81,4.84,4.34,4.61,4.63,", 1, 44100, base::TimeDelta::FromMicroseconds(350001), 15435, 15435); } TEST_F(AudioFileReaderTest, WaveU8) { - RunTest("sfx_u8.wav", "d7e255a8e634fffdf9f744c5803632f8", 1, 44100, + RunTest("sfx_u8.wav", "-1.23,-1.57,-1.14,-0.91,-0.87,-0.07,", 1, 44100, base::TimeDelta::FromMicroseconds(288414), 12719, 12719); } TEST_F(AudioFileReaderTest, WaveS16LE) { - RunTest("sfx_s16le.wav", "2a5847207fdcba1c05e52f65ad010f66", 1, 44100, + RunTest("sfx_s16le.wav", "3.05,2.87,3.00,3.32,3.58,4.08,", 1, 44100, base::TimeDelta::FromMicroseconds(288414), 12719, 12719); } TEST_F(AudioFileReaderTest, WaveS24LE) { - RunTest("sfx_s24le.wav", "66296b4ec633290581f9abf3c21cd5e7", 1, 44100, + RunTest("sfx_s24le.wav", "3.03,2.86,2.99,3.31,3.57,4.06,", 1, 44100, base::TimeDelta::FromMicroseconds(288414), 12719, 12719); } TEST_F(AudioFileReaderTest, WaveF32LE) { - RunTest("sfx_f32le.wav", "66296b4ec633290581f9abf3c21cd5e7", 1, 44100, + RunTest("sfx_f32le.wav", "3.03,2.86,2.99,3.31,3.57,4.06,", 1, 44100, base::TimeDelta::FromMicroseconds(288414), 12719, 12719); } #if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS) TEST_F(AudioFileReaderTest, MP3) { - RunTest("sfx.mp3", NULL, 1, 44100, + RunTest("sfx.mp3", "3.05,2.87,3.00,3.32,3.58,4.08,", 1, 44100, base::TimeDelta::FromMicroseconds(313470), 13824, 12719); } TEST_F(AudioFileReaderTest, AAC) { - RunTest("sfx.m4a", NULL, 1, 44100, + RunTest("sfx.m4a", "1.81,1.66,2.32,3.27,4.46,3.36,", 1, 44100, base::TimeDelta::FromMicroseconds(312001), 13759, 13312); } @@ -153,7 +131,7 @@ TEST_F(AudioFileReaderTest, VorbisInvalidChannelLayout) { } TEST_F(AudioFileReaderTest, WaveValidFourChannelLayout) { - RunTest("4ch.wav", "d40bb7dbe532b2f1cf2e3558e780caa2", 4, 44100, + RunTest("4ch.wav", "131.71,38.02,130.31,44.89,135.98,42.52,", 4, 44100, base::TimeDelta::FromMicroseconds(100001), 4410, 4410); } diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index d1720c1..5aec7c1 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -418,11 +418,8 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) { ASSERT_TRUE(WaitUntilOnEnded()); - EXPECT_EQ(GetVideoHash(), "f0be120a90a811506777c99a2cdf7cc1"); - - // TODO(dalecurtis): Audio decoded in float does not have a consistent hash - // across platforms. Fix this: http://crbug.com/168204 - // EXPECT_EQ(GetAudioHash(), ""); + EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash()); + EXPECT_EQ("-5.15,2.31,-4.78,5.24,-2.23,4.54,", GetAudioHash()); } // TODO(fgalligan): Enable test when code to parse encrypted WebM files lands diff --git a/media/filters/pipeline_integration_test_base.h b/media/filters/pipeline_integration_test_base.h index 05e2e7e..75f67ba 100644 --- a/media/filters/pipeline_integration_test_base.h +++ b/media/filters/pipeline_integration_test_base.h @@ -65,7 +65,7 @@ class PipelineIntegrationTestBase { // with hashing enabled. std::string GetVideoHash(); - // Returns the MD5 hash of all audio frames seen. Should only be called once + // Returns the hash of all audio frames seen. Should only be called once // after playback completes. Pipeline must have been started with hashing // enabled. std::string GetAudioHash(); diff --git a/media/media.gyp b/media/media.gyp index 63cd33a..29113ed 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -198,6 +198,8 @@ 'base/audio_fifo.h', 'base/audio_hardware_config.cc', 'base/audio_hardware_config.h', + 'base/audio_hash.cc', + 'base/audio_hash.h', 'base/audio_pull_fifo.cc', 'base/audio_pull_fifo.h', 'base/audio_renderer.cc', @@ -875,6 +877,7 @@ 'base/audio_converter_unittest.cc', 'base/audio_fifo_unittest.cc', 'base/audio_hardware_config_unittest.cc', + 'base/audio_hash_unittest.cc', 'base/audio_pull_fifo_unittest.cc', 'base/audio_renderer_mixer_input_unittest.cc', 'base/audio_renderer_mixer_unittest.cc', |