diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-09-19 22:36:51 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-09-19 22:36:51 +0100 |
commit | d0247b1b59f9c528cb6df88b4f2b9afaf80d181e (patch) | |
tree | 5c397fadc190cc71bffe2ffad1efc27a5b95309d /media/audio | |
parent | f7571f5f07547e2f3e0addf48d1f2a7ec3632957 (diff) | |
download | chromium_org-d0247b1b59f9c528cb6df88b4f2b9afaf80d181e.tar.gz |
Merge from Chromium at DEPS revision 224184
This commit was generated by merge_to_master.py.
Change-Id: Ia3424df5abed9bea642c522b9e2358dceabd8423
Diffstat (limited to 'media/audio')
28 files changed, 1797 insertions, 599 deletions
diff --git a/media/audio/android/audio_android_unittest.cc b/media/audio/android/audio_android_unittest.cc new file mode 100644 index 0000000000..a8e448f821 --- /dev/null +++ b/media/audio/android/audio_android_unittest.cc @@ -0,0 +1,769 @@ +// 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/basictypes.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/test_timeouts.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "media/audio/android/audio_manager_android.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager_base.h" +#include "media/base/decoder_buffer.h" +#include "media/base/seekable_buffer.h" +#include "media/base/test_data_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; + +namespace media { + +ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { + if (++*count >= limit) { + loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); + } +} + +static const char kSpeechFile_16b_s_48k[] = "speech_16b_stereo_48kHz.raw"; +static const char kSpeechFile_16b_m_48k[] = "speech_16b_mono_48kHz.raw"; +static const char kSpeechFile_16b_s_44k[] = "speech_16b_stereo_44kHz.raw"; +static const char kSpeechFile_16b_m_44k[] = "speech_16b_mono_44kHz.raw"; + +static const float kCallbackTestTimeMs = 2000.0; +static const int kBitsPerSample = 16; +static const int kBytesPerSample = kBitsPerSample / 8; + +// Converts AudioParameters::Format enumerator to readable string. +static std::string FormatToString(AudioParameters::Format format) { + switch (format) { + case AudioParameters::AUDIO_PCM_LINEAR: + return std::string("AUDIO_PCM_LINEAR"); + case AudioParameters::AUDIO_PCM_LOW_LATENCY: + return std::string("AUDIO_PCM_LOW_LATENCY"); + case AudioParameters::AUDIO_FAKE: + return std::string("AUDIO_FAKE"); + case AudioParameters::AUDIO_LAST_FORMAT: + return std::string("AUDIO_LAST_FORMAT"); + default: + return std::string(); + } +} + +// Converts ChannelLayout enumerator to readable string. Does not include +// multi-channel cases since these layouts are not supported on Android. +static std::string LayoutToString(ChannelLayout channel_layout) { + switch (channel_layout) { + case CHANNEL_LAYOUT_NONE: + return std::string("CHANNEL_LAYOUT_NONE"); + case CHANNEL_LAYOUT_MONO: + return std::string("CHANNEL_LAYOUT_MONO"); + case CHANNEL_LAYOUT_STEREO: + return std::string("CHANNEL_LAYOUT_STEREO"); + case CHANNEL_LAYOUT_UNSUPPORTED: + default: + return std::string("CHANNEL_LAYOUT_UNSUPPORTED"); + } +} + +static double ExpectedTimeBetweenCallbacks(AudioParameters params) { + return (base::TimeDelta::FromMicroseconds( + params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond / + static_cast<double>(params.sample_rate()))).InMillisecondsF(); +} + +std::ostream& operator<<(std::ostream& os, const AudioParameters& params) { + using namespace std; + os << endl << "format: " << FormatToString(params.format()) << endl + << "channel layout: " << LayoutToString(params.channel_layout()) << endl + << "sample rate: " << params.sample_rate() << endl + << "bits per sample: " << params.bits_per_sample() << endl + << "frames per buffer: " << params.frames_per_buffer() << endl + << "channels: " << params.channels() << endl + << "bytes per buffer: " << params.GetBytesPerBuffer() << endl + << "bytes per second: " << params.GetBytesPerSecond() << endl + << "bytes per frame: " << params.GetBytesPerFrame() << endl + << "frame size in ms: " << ExpectedTimeBetweenCallbacks(params); + return os; +} + +// Gmock implementation of AudioInputStream::AudioInputCallback. +class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { + public: + MOCK_METHOD5(OnData, + void(AudioInputStream* stream, + const uint8* src, + uint32 size, + uint32 hardware_delay_bytes, + double volume)); + MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); + MOCK_METHOD1(OnError, void(AudioInputStream* stream)); +}; + +// Gmock implementation of AudioOutputStream::AudioSourceCallback. +class MockAudioOutputCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD2(OnMoreData, + int(AudioBus* dest, AudioBuffersState buffers_state)); + MOCK_METHOD3(OnMoreIOData, + int(AudioBus* source, + AudioBus* dest, + AudioBuffersState buffers_state)); + MOCK_METHOD1(OnError, void(AudioOutputStream* stream)); + + // We clear the data bus to ensure that the test does not cause noise. + int RealOnMoreData(AudioBus* dest, AudioBuffersState buffers_state) { + dest->Zero(); + return dest->frames(); + } +}; + +// Implements AudioOutputStream::AudioSourceCallback and provides audio data +// by reading from a data file. +class FileAudioSource : public AudioOutputStream::AudioSourceCallback { + public: + explicit FileAudioSource(base::WaitableEvent* event, const std::string& name) + : event_(event), pos_(0) { + // Reads a test file from media/test/data directory and stores it in + // a DecoderBuffer. + file_ = ReadTestDataFile(name); + + // Log the name of the file which is used as input for this test. + base::FilePath file_path = GetTestDataFilePath(name); + LOG(INFO) << "Reading from file: " << file_path.value().c_str(); + } + + virtual ~FileAudioSource() {} + + // AudioOutputStream::AudioSourceCallback implementation. + + // Use samples read from a data file and fill up the audio buffer + // provided to us in the callback. + virtual int OnMoreData(AudioBus* audio_bus, + AudioBuffersState buffers_state) OVERRIDE { + bool stop_playing = false; + int max_size = + audio_bus->frames() * audio_bus->channels() * kBytesPerSample; + + // Adjust data size and prepare for end signal if file has ended. + if (pos_ + max_size > file_size()) { + stop_playing = true; + max_size = file_size() - pos_; + } + + // File data is stored as interleaved 16-bit values. Copy data samples from + // the file and deinterleave to match the audio bus format. + // FromInterleaved() will zero out any unfilled frames when there is not + // sufficient data remaining in the file to fill up the complete frame. + int frames = max_size / (audio_bus->channels() * kBytesPerSample); + if (max_size) { + audio_bus->FromInterleaved(file_->data() + pos_, frames, kBytesPerSample); + pos_ += max_size; + } + + // Set event to ensure that the test can stop when the file has ended. + if (stop_playing) + event_->Signal(); + + return frames; + } + + virtual int OnMoreIOData(AudioBus* source, + AudioBus* dest, + AudioBuffersState buffers_state) OVERRIDE { + NOTREACHED(); + return 0; + } + + virtual void OnError(AudioOutputStream* stream) OVERRIDE {} + + int file_size() { return file_->data_size(); } + + private: + base::WaitableEvent* event_; + int pos_; + scoped_refptr<DecoderBuffer> file_; + + DISALLOW_COPY_AND_ASSIGN(FileAudioSource); +}; + +// Implements AudioInputStream::AudioInputCallback and writes the recorded +// audio data to a local output file. Note that this implementation should +// only be used for manually invoked and evaluated tests, hence the created +// file will not be destroyed after the test is done since the intention is +// that it shall be available for off-line analysis. +class FileAudioSink : public AudioInputStream::AudioInputCallback { + public: + explicit FileAudioSink(base::WaitableEvent* event, + const AudioParameters& params, + const std::string& file_name) + : event_(event), params_(params) { + // Allocate space for ~10 seconds of data. + const int kMaxBufferSize = 10 * params.GetBytesPerSecond(); + buffer_.reset(new media::SeekableBuffer(0, kMaxBufferSize)); + + // Open up the binary file which will be written to in the destructor. + base::FilePath file_path; + EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); + file_path = file_path.AppendASCII(file_name.c_str()); + binary_file_ = file_util::OpenFile(file_path, "wb"); + DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file."; + LOG(INFO) << "Writing to file: " << file_path.value().c_str(); + } + + virtual ~FileAudioSink() { + int bytes_written = 0; + while (bytes_written < buffer_->forward_capacity()) { + const uint8* chunk; + int chunk_size; + + // Stop writing if no more data is available. + if (!buffer_->GetCurrentChunk(&chunk, &chunk_size)) + break; + + // Write recorded data chunk to the file and prepare for next chunk. + // TODO(henrika): use file_util:: instead. + fwrite(chunk, 1, chunk_size, binary_file_); + buffer_->Seek(chunk_size); + bytes_written += chunk_size; + } + file_util::CloseFile(binary_file_); + } + + // AudioInputStream::AudioInputCallback implementation. + virtual void OnData(AudioInputStream* stream, + const uint8* src, + uint32 size, + uint32 hardware_delay_bytes, + double volume) OVERRIDE { + // Store data data in a temporary buffer to avoid making blocking + // fwrite() calls in the audio callback. The complete buffer will be + // written to file in the destructor. + if (!buffer_->Append(src, size)) + event_->Signal(); + } + + virtual void OnClose(AudioInputStream* stream) OVERRIDE {} + virtual void OnError(AudioInputStream* stream) OVERRIDE {} + + private: + base::WaitableEvent* event_; + AudioParameters params_; + scoped_ptr<media::SeekableBuffer> buffer_; + FILE* binary_file_; + + DISALLOW_COPY_AND_ASSIGN(FileAudioSink); +}; + +// Implements AudioInputCallback and AudioSourceCallback to support full +// duplex audio where captured samples are played out in loopback after +// reading from a temporary FIFO storage. +class FullDuplexAudioSinkSource + : public AudioInputStream::AudioInputCallback, + public AudioOutputStream::AudioSourceCallback { + public: + explicit FullDuplexAudioSinkSource(const AudioParameters& params) + : params_(params), + previous_time_(base::TimeTicks::Now()), + started_(false) { + // Start with a reasonably small FIFO size. It will be increased + // dynamically during the test if required. + fifo_.reset(new media::SeekableBuffer(0, 2 * params.GetBytesPerBuffer())); + buffer_.reset(new uint8[params_.GetBytesPerBuffer()]); + } + + virtual ~FullDuplexAudioSinkSource() {} + + // AudioInputStream::AudioInputCallback implementation + virtual void OnData(AudioInputStream* stream, + const uint8* src, + uint32 size, + uint32 hardware_delay_bytes, + double volume) OVERRIDE { + const base::TimeTicks now_time = base::TimeTicks::Now(); + const int diff = (now_time - previous_time_).InMilliseconds(); + + base::AutoLock lock(lock_); + if (diff > 1000) { + started_ = true; + previous_time_ = now_time; + + // Log out the extra delay added by the FIFO. This is a best effort + // estimate. We might be +- 10ms off here. + int extra_fifo_delay = + static_cast<int>(BytesToMilliseconds(fifo_->forward_bytes() + size)); + DVLOG(1) << extra_fifo_delay; + } + + // We add an initial delay of ~1 second before loopback starts to ensure + // a stable callback sequence and to avoid initial bursts which might add + // to the extra FIFO delay. + if (!started_) + return; + + // Append new data to the FIFO and extend the size if the max capacity + // was exceeded. Flush the FIFO when extended just in case. + if (!fifo_->Append(src, size)) { + fifo_->set_forward_capacity(2 * fifo_->forward_capacity()); + fifo_->Clear(); + } + } + + virtual void OnClose(AudioInputStream* stream) OVERRIDE {} + virtual void OnError(AudioInputStream* stream) OVERRIDE {} + + // AudioOutputStream::AudioSourceCallback implementation + virtual int OnMoreData(AudioBus* dest, + AudioBuffersState buffers_state) OVERRIDE { + const int size_in_bytes = + (params_.bits_per_sample() / 8) * dest->frames() * dest->channels(); + EXPECT_EQ(size_in_bytes, params_.GetBytesPerBuffer()); + + base::AutoLock lock(lock_); + + // We add an initial delay of ~1 second before loopback starts to ensure + // a stable callback sequences and to avoid initial bursts which might add + // to the extra FIFO delay. + if (!started_) { + dest->Zero(); + return dest->frames(); + } + + // Fill up destination with zeros if the FIFO does not contain enough + // data to fulfill the request. + if (fifo_->forward_bytes() < size_in_bytes) { + dest->Zero(); + } else { + fifo_->Read(buffer_.get(), size_in_bytes); + dest->FromInterleaved( + buffer_.get(), dest->frames(), params_.bits_per_sample() / 8); + } + + return dest->frames(); + } + + virtual int OnMoreIOData(AudioBus* source, + AudioBus* dest, + AudioBuffersState buffers_state) OVERRIDE { + NOTREACHED(); + return 0; + } + + virtual void OnError(AudioOutputStream* stream) OVERRIDE {} + + private: + // Converts from bytes to milliseconds given number of bytes and existing + // audio parameters. + double BytesToMilliseconds(int bytes) const { + const int frames = bytes / params_.GetBytesPerFrame(); + return (base::TimeDelta::FromMicroseconds( + frames * base::Time::kMicrosecondsPerSecond / + static_cast<double>(params_.sample_rate()))).InMillisecondsF(); + } + + AudioParameters params_; + base::TimeTicks previous_time_; + base::Lock lock_; + scoped_ptr<media::SeekableBuffer> fifo_; + scoped_ptr<uint8[]> buffer_; + bool started_; + + DISALLOW_COPY_AND_ASSIGN(FullDuplexAudioSinkSource); +}; + +// Test fixture class. +class AudioAndroidTest : public testing::Test { + public: + AudioAndroidTest() {} + + protected: + virtual void SetUp() { + audio_manager_.reset(AudioManager::Create()); + loop_.reset(new base::MessageLoopForUI()); + } + + virtual void TearDown() {} + + AudioManager* audio_manager() { return audio_manager_.get(); } + base::MessageLoopForUI* loop() { return loop_.get(); } + + AudioParameters GetDefaultInputStreamParameters() { + return audio_manager()->GetInputStreamParameters( + AudioManagerBase::kDefaultDeviceId); + } + + AudioParameters GetDefaultOutputStreamParameters() { + return audio_manager()->GetDefaultOutputStreamParameters(); + } + + double AverageTimeBetweenCallbacks(int num_callbacks) const { + return ((end_time_ - start_time_) / static_cast<double>(num_callbacks - 1)) + .InMillisecondsF(); + } + + void StartInputStreamCallbacks(const AudioParameters& params) { + double expected_time_between_callbacks_ms = + ExpectedTimeBetweenCallbacks(params); + const int num_callbacks = + (kCallbackTestTimeMs / expected_time_between_callbacks_ms); + AudioInputStream* stream = audio_manager()->MakeAudioInputStream( + params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(stream); + + int count = 0; + MockAudioInputCallback sink; + + EXPECT_CALL(sink, + OnData(stream, NotNull(), params.GetBytesPerBuffer(), _, _)) + .Times(AtLeast(num_callbacks)) + .WillRepeatedly( + CheckCountAndPostQuitTask(&count, num_callbacks, loop())); + EXPECT_CALL(sink, OnError(stream)).Times(0); + EXPECT_CALL(sink, OnClose(stream)).Times(1); + + EXPECT_TRUE(stream->Open()); + stream->Start(&sink); + start_time_ = base::TimeTicks::Now(); + loop()->Run(); + end_time_ = base::TimeTicks::Now(); + stream->Stop(); + stream->Close(); + + double average_time_between_callbacks_ms = + AverageTimeBetweenCallbacks(num_callbacks); + LOG(INFO) << "expected time between callbacks: " + << expected_time_between_callbacks_ms << " ms"; + LOG(INFO) << "average time between callbacks: " + << average_time_between_callbacks_ms << " ms"; + EXPECT_GE(average_time_between_callbacks_ms, + 0.70 * expected_time_between_callbacks_ms); + EXPECT_LE(average_time_between_callbacks_ms, + 1.30 * expected_time_between_callbacks_ms); + } + + void StartOutputStreamCallbacks(const AudioParameters& params) { + double expected_time_between_callbacks_ms = + ExpectedTimeBetweenCallbacks(params); + const int num_callbacks = + (kCallbackTestTimeMs / expected_time_between_callbacks_ms); + AudioOutputStream* stream = audio_manager()->MakeAudioOutputStream( + params, std::string(), std::string()); + EXPECT_TRUE(stream); + + int count = 0; + MockAudioOutputCallback source; + + EXPECT_CALL(source, OnMoreData(NotNull(), _)) + .Times(AtLeast(num_callbacks)) + .WillRepeatedly( + DoAll(CheckCountAndPostQuitTask(&count, num_callbacks, loop()), + Invoke(&source, &MockAudioOutputCallback::RealOnMoreData))); + EXPECT_CALL(source, OnError(stream)).Times(0); + EXPECT_CALL(source, OnMoreIOData(_, _, _)).Times(0); + + EXPECT_TRUE(stream->Open()); + stream->Start(&source); + start_time_ = base::TimeTicks::Now(); + loop()->Run(); + end_time_ = base::TimeTicks::Now(); + stream->Stop(); + stream->Close(); + + double average_time_between_callbacks_ms = + AverageTimeBetweenCallbacks(num_callbacks); + LOG(INFO) << "expected time between callbacks: " + << expected_time_between_callbacks_ms << " ms"; + LOG(INFO) << "average time between callbacks: " + << average_time_between_callbacks_ms << " ms"; + EXPECT_GE(average_time_between_callbacks_ms, + 0.70 * expected_time_between_callbacks_ms); + EXPECT_LE(average_time_between_callbacks_ms, + 1.30 * expected_time_between_callbacks_ms); + } + + scoped_ptr<base::MessageLoopForUI> loop_; + scoped_ptr<AudioManager> audio_manager_; + base::TimeTicks start_time_; + base::TimeTicks end_time_; + + DISALLOW_COPY_AND_ASSIGN(AudioAndroidTest); +}; + +// Get the default audio input parameters and log the result. +TEST_F(AudioAndroidTest, GetInputStreamParameters) { + AudioParameters params = GetDefaultInputStreamParameters(); + EXPECT_TRUE(params.IsValid()); + VLOG(1) << params; +} + +// Get the default audio output parameters and log the result. +TEST_F(AudioAndroidTest, GetDefaultOutputStreamParameters) { + AudioParameters params = GetDefaultOutputStreamParameters(); + EXPECT_TRUE(params.IsValid()); + VLOG(1) << params; +} + +// Check if low-latency output is supported and log the result as output. +TEST_F(AudioAndroidTest, IsAudioLowLatencySupported) { + AudioManagerAndroid* manager = + static_cast<AudioManagerAndroid*>(audio_manager()); + bool low_latency = manager->IsAudioLowLatencySupported(); + low_latency ? LOG(INFO) << "Low latency output is supported" + : LOG(INFO) << "Low latency output is *not* supported"; +} + +// Ensure that a default input stream can be created and closed. +TEST_F(AudioAndroidTest, CreateAndCloseInputStream) { + AudioParameters params = GetDefaultInputStreamParameters(); + AudioInputStream* ais = audio_manager()->MakeAudioInputStream( + params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(ais); + ais->Close(); +} + +// Ensure that a default output stream can be created and closed. +// TODO(henrika): should we also verify that this API changes the audio mode +// to communication mode, and calls RegisterHeadsetReceiver, the first time +// it is called? +TEST_F(AudioAndroidTest, CreateAndCloseOutputStream) { + AudioParameters params = GetDefaultOutputStreamParameters(); + AudioOutputStream* aos = audio_manager()->MakeAudioOutputStream( + params, std::string(), std::string()); + EXPECT_TRUE(aos); + aos->Close(); +} + +// Ensure that a default input stream can be opened and closed. +TEST_F(AudioAndroidTest, OpenAndCloseInputStream) { + AudioParameters params = GetDefaultInputStreamParameters(); + AudioInputStream* ais = audio_manager()->MakeAudioInputStream( + params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(ais); + EXPECT_TRUE(ais->Open()); + ais->Close(); +} + +// Ensure that a default output stream can be opened and closed. +TEST_F(AudioAndroidTest, OpenAndCloseOutputStream) { + AudioParameters params = GetDefaultOutputStreamParameters(); + AudioOutputStream* aos = audio_manager()->MakeAudioOutputStream( + params, std::string(), std::string()); + EXPECT_TRUE(aos); + EXPECT_TRUE(aos->Open()); + aos->Close(); +} + +// Start input streaming using default input parameters and ensure that the +// callback sequence is sane. +TEST_F(AudioAndroidTest, StartInputStreamCallbacks) { + AudioParameters params = GetDefaultInputStreamParameters(); + StartInputStreamCallbacks(params); +} + +// Start input streaming using non default input parameters and ensure that the +// callback sequence is sane. The only change we make in this test is to select +// a 10ms buffer size instead of the default size. +// TODO(henrika): possibly add support for more variations. +TEST_F(AudioAndroidTest, StartInputStreamCallbacksNonDefaultParameters) { + AudioParameters native_params = GetDefaultInputStreamParameters(); + AudioParameters params(native_params.format(), + native_params.channel_layout(), + native_params.sample_rate(), + native_params.bits_per_sample(), + native_params.sample_rate() / 100); + StartInputStreamCallbacks(params); +} + +// Start output streaming using default output parameters and ensure that the +// callback sequence is sane. +TEST_F(AudioAndroidTest, StartOutputStreamCallbacks) { + AudioParameters params = GetDefaultOutputStreamParameters(); + StartOutputStreamCallbacks(params); +} + +// Start output streaming using non default output parameters and ensure that +// the callback sequence is sane. The only change we make in this test is to +// select a 10ms buffer size instead of the default size and to open up the +// device in mono. +// TODO(henrika): possibly add support for more variations. +TEST_F(AudioAndroidTest, StartOutputStreamCallbacksNonDefaultParameters) { + AudioParameters native_params = GetDefaultOutputStreamParameters(); + AudioParameters params(native_params.format(), + CHANNEL_LAYOUT_MONO, + native_params.sample_rate(), + native_params.bits_per_sample(), + native_params.sample_rate() / 100); + StartOutputStreamCallbacks(params); +} + +// Play out a PCM file segment in real time and allow the user to verify that +// the rendered audio sounds OK. +// NOTE: this test requires user interaction and is not designed to run as an +// automatized test on bots. +TEST_F(AudioAndroidTest, DISABLED_RunOutputStreamWithFileAsSource) { + AudioParameters params = GetDefaultOutputStreamParameters(); + VLOG(1) << params; + AudioOutputStream* aos = audio_manager()->MakeAudioOutputStream( + params, std::string(), std::string()); + EXPECT_TRUE(aos); + + std::string file_name; + if (params.sample_rate() == 48000 && params.channels() == 2) { + file_name = kSpeechFile_16b_s_48k; + } else if (params.sample_rate() == 48000 && params.channels() == 1) { + file_name = kSpeechFile_16b_m_48k; + } else if (params.sample_rate() == 44100 && params.channels() == 2) { + file_name = kSpeechFile_16b_s_44k; + } else if (params.sample_rate() == 44100 && params.channels() == 1) { + file_name = kSpeechFile_16b_m_44k; + } else { + FAIL() << "This test supports 44.1kHz and 48kHz mono/stereo only."; + return; + } + + base::WaitableEvent event(false, false); + FileAudioSource source(&event, file_name); + + EXPECT_TRUE(aos->Open()); + aos->SetVolume(1.0); + aos->Start(&source); + LOG(INFO) << ">> Verify that the file is played out correctly..."; + EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout())); + aos->Stop(); + aos->Close(); +} + +// Start input streaming and run it for ten seconds while recording to a +// local audio file. +// NOTE: this test requires user interaction and is not designed to run as an +// automatized test on bots. +TEST_F(AudioAndroidTest, DISABLED_RunSimplexInputStreamWithFileAsSink) { + AudioParameters params = GetDefaultInputStreamParameters(); + VLOG(1) << params; + AudioInputStream* ais = audio_manager()->MakeAudioInputStream( + params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(ais); + + std::string file_name = base::StringPrintf("out_simplex_%d_%d_%d.pcm", + params.sample_rate(), + params.frames_per_buffer(), + params.channels()); + + base::WaitableEvent event(false, false); + FileAudioSink sink(&event, params, file_name); + + EXPECT_TRUE(ais->Open()); + ais->Start(&sink); + LOG(INFO) << ">> Speak into the microphone to record audio..."; + EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout())); + ais->Stop(); + ais->Close(); +} + +// Same test as RunSimplexInputStreamWithFileAsSink but this time output +// streaming is active as well (reads zeros only). +// NOTE: this test requires user interaction and is not designed to run as an +// automatized test on bots. +TEST_F(AudioAndroidTest, DISABLED_RunDuplexInputStreamWithFileAsSink) { + AudioParameters in_params = GetDefaultInputStreamParameters(); + AudioInputStream* ais = audio_manager()->MakeAudioInputStream( + in_params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(ais); + + AudioParameters out_params = + audio_manager()->GetDefaultOutputStreamParameters(); + AudioOutputStream* aos = audio_manager()->MakeAudioOutputStream( + out_params, std::string(), std::string()); + EXPECT_TRUE(aos); + + std::string file_name = base::StringPrintf("out_duplex_%d_%d_%d.pcm", + in_params.sample_rate(), + in_params.frames_per_buffer(), + in_params.channels()); + + base::WaitableEvent event(false, false); + FileAudioSink sink(&event, in_params, file_name); + MockAudioOutputCallback source; + + EXPECT_CALL(source, OnMoreData(NotNull(), _)).WillRepeatedly( + Invoke(&source, &MockAudioOutputCallback::RealOnMoreData)); + EXPECT_CALL(source, OnError(aos)).Times(0); + EXPECT_CALL(source, OnMoreIOData(_, _, _)).Times(0); + + EXPECT_TRUE(ais->Open()); + EXPECT_TRUE(aos->Open()); + ais->Start(&sink); + aos->Start(&source); + LOG(INFO) << ">> Speak into the microphone to record audio"; + EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout())); + aos->Stop(); + ais->Stop(); + aos->Close(); + ais->Close(); +} + +// Start audio in both directions while feeding captured data into a FIFO so +// it can be read directly (in loopback) by the render side. A small extra +// delay will be added by the FIFO and an estimate of this delay will be +// printed out during the test. +// NOTE: this test requires user interaction and is not designed to run as an +// automatized test on bots. +TEST_F(AudioAndroidTest, + DISABLED_RunSymmetricInputAndOutputStreamsInFullDuplex) { + // Get native audio parameters for the input side. + AudioParameters default_input_params = GetDefaultInputStreamParameters(); + + // Modify the parameters so that both input and output can use the same + // parameters by selecting 10ms as buffer size. This will also ensure that + // the output stream will be a mono stream since mono is default for input + // audio on Android. + AudioParameters io_params(default_input_params.format(), + default_input_params.channel_layout(), + default_input_params.sample_rate(), + default_input_params.bits_per_sample(), + default_input_params.sample_rate() / 100); + VLOG(1) << io_params; + + // Create input and output streams using the common audio parameters. + AudioInputStream* ais = audio_manager()->MakeAudioInputStream( + io_params, AudioManagerBase::kDefaultDeviceId); + EXPECT_TRUE(ais); + AudioOutputStream* aos = audio_manager()->MakeAudioOutputStream( + io_params, std::string(), std::string()); + EXPECT_TRUE(aos); + + FullDuplexAudioSinkSource full_duplex(io_params); + + // Start a full duplex audio session and print out estimates of the extra + // delay we should expect from the FIFO. If real-time delay measurements are + // performed, the result should be reduced by this extra delay since it is + // something that has been added by the test. + EXPECT_TRUE(ais->Open()); + EXPECT_TRUE(aos->Open()); + ais->Start(&full_duplex); + aos->Start(&full_duplex); + VLOG(1) << "HINT: an estimate of the extra FIFO delay will be updated " + << "once per second during this test."; + LOG(INFO) << ">> Speak into the mic and listen to the audio in loopback..."; + fflush(stdout); + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(20)); + printf("\n"); + aos->Stop(); + ais->Stop(); + aos->Close(); + ais->Close(); +} + +} // namespace media diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc index e6eed7fea4..04b226fa64 100644 --- a/media/audio/android/audio_manager_android.cc +++ b/media/audio/android/audio_manager_android.cc @@ -16,6 +16,13 @@ namespace media { +static void AddDefaultDevice(AudioDeviceNames* device_names) { + DCHECK(device_names->empty()); + device_names->push_front( + AudioDeviceName(AudioManagerBase::kDefaultDeviceName, + AudioManagerBase::kDefaultDeviceId)); +} + // Maximum number of output streams that can be open simultaneously. static const int kMaxOutputStreams = 10; @@ -51,10 +58,13 @@ bool AudioManagerAndroid::HasAudioInputDevices() { } void AudioManagerAndroid::GetAudioInputDeviceNames( - media::AudioDeviceNames* device_names) { - DCHECK(device_names->empty()); - device_names->push_front( - media::AudioDeviceName(kDefaultDeviceName, kDefaultDeviceId)); + AudioDeviceNames* device_names) { + AddDefaultDevice(device_names); +} + +void AudioManagerAndroid::GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) { + AddDefaultDevice(device_names); } AudioParameters AudioManagerAndroid::GetInputStreamParameters( @@ -90,7 +100,7 @@ AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream( AudioInputStream* AudioManagerAndroid::MakeAudioInputStream( const AudioParameters& params, const std::string& device_id) { AudioInputStream* stream = - AudioManagerBase::MakeAudioInputStream(params, device_id); + AudioManagerBase::MakeAudioInputStream(params, device_id); return stream; } diff --git a/media/audio/android/audio_manager_android.h b/media/audio/android/audio_manager_android.h index ba5bed61e3..ed2b2c3ce9 100644 --- a/media/audio/android/audio_manager_android.h +++ b/media/audio/android/audio_manager_android.h @@ -6,6 +6,7 @@ #define MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_ #include "base/android/jni_android.h" +#include "base/gtest_prod_util.h" #include "media/audio/audio_manager_base.h" namespace media { @@ -18,8 +19,10 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { // Implementation of AudioManager. virtual bool HasAudioOutputDevices() OVERRIDE; virtual bool HasAudioInputDevices() OVERRIDE; - virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; @@ -28,7 +31,8 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { const std::string& device_id, const std::string& input_device_id) OVERRIDE; virtual AudioInputStream* MakeAudioInputStream( - const AudioParameters& params, const std::string& device_id) OVERRIDE; + const AudioParameters& params, + const std::string& device_id) OVERRIDE; virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE; virtual void ReleaseInputStream(AudioInputStream* stream) OVERRIDE; @@ -40,9 +44,11 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { const std::string& device_id, const std::string& input_device_id) OVERRIDE; virtual AudioInputStream* MakeLinearInputStream( - const AudioParameters& params, const std::string& device_id) OVERRIDE; + const AudioParameters& params, + const std::string& device_id) OVERRIDE; virtual AudioInputStream* MakeLowLatencyInputStream( - const AudioParameters& params, const std::string& device_id) OVERRIDE; + const AudioParameters& params, + const std::string& device_id) OVERRIDE; static bool RegisterAudioManager(JNIEnv* env); @@ -62,6 +68,9 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { int GetAudioLowLatencyOutputFrameSize(); int GetOptimalOutputFrameSize(int sample_rate, int channels); + // Allow the AudioAndroidTest to access private methods. + FRIEND_TEST_ALL_PREFIXES(AudioAndroidTest, IsAudioLowLatencySupported); + // Java AudioManager instance. base::android::ScopedJavaGlobalRef<jobject> j_audio_manager_; diff --git a/media/audio/android/opensles_input.cc b/media/audio/android/opensles_input.cc index 15c3eac372..a0e4ce3b98 100644 --- a/media/audio/android/opensles_input.cc +++ b/media/audio/android/opensles_input.cc @@ -4,16 +4,17 @@ #include "media/audio/android/opensles_input.h" +#include "base/debug/trace_event.h" #include "base/logging.h" #include "media/audio/android/audio_manager_android.h" -#define LOG_ON_FAILURE_AND_RETURN(op, ...) \ - do { \ - SLresult err = (op); \ - if (err != SL_RESULT_SUCCESS) { \ +#define LOG_ON_FAILURE_AND_RETURN(op, ...) \ + do { \ + SLresult err = (op); \ + if (err != SL_RESULT_SUCCESS) { \ DLOG(ERROR) << #op << " failed: " << err; \ - return __VA_ARGS__; \ - } \ + return __VA_ARGS__; \ + } \ } while (0) namespace media { @@ -24,9 +25,10 @@ OpenSLESInputStream::OpenSLESInputStream(AudioManagerAndroid* audio_manager, callback_(NULL), recorder_(NULL), simple_buffer_queue_(NULL), - active_queue_(0), + active_buffer_index_(0), buffer_size_bytes_(0), started_(false) { + DVLOG(2) << "OpenSLESInputStream::OpenSLESInputStream()"; format_.formatType = SL_DATAFORMAT_PCM; format_.numChannels = static_cast<SLuint32>(params.channels()); // Provides sampling rate in milliHertz to OpenSLES. @@ -47,6 +49,8 @@ OpenSLESInputStream::OpenSLESInputStream(AudioManagerAndroid* audio_manager, } OpenSLESInputStream::~OpenSLESInputStream() { + DVLOG(2) << "OpenSLESInputStream::~OpenSLESInputStream()"; + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!recorder_object_.Get()); DCHECK(!engine_object_.Get()); DCHECK(!recorder_); @@ -55,6 +59,8 @@ OpenSLESInputStream::~OpenSLESInputStream() { } bool OpenSLESInputStream::Open() { + DVLOG(2) << "OpenSLESInputStream::Open()"; + DCHECK(thread_checker_.CalledOnValidThread()); if (engine_object_.Get()) return false; @@ -67,44 +73,59 @@ bool OpenSLESInputStream::Open() { } void OpenSLESInputStream::Start(AudioInputCallback* callback) { + DVLOG(2) << "OpenSLESInputStream::Start()"; + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(callback); DCHECK(recorder_); DCHECK(simple_buffer_queue_); if (started_) return; - // Enable the flags before streaming. + base::AutoLock lock(lock_); + DCHECK(callback_ == NULL || callback_ == callback); callback_ = callback; - active_queue_ = 0; - started_ = true; + active_buffer_index_ = 0; + // Enqueues kMaxNumOfBuffersInQueue zero buffers to get the ball rolling. + // TODO(henrika): add support for Start/Stop/Start sequences when we are + // able to clear the buffer queue. There is currently a bug in the OpenSLES + // implementation which forces us to always call Stop() and Close() before + // calling Start() again. SLresult err = SL_RESULT_UNKNOWN_ERROR; - // Enqueues |kNumOfQueuesInBuffer| zero buffers to get the ball rolling. - for (int i = 0; i < kNumOfQueuesInBuffer; ++i) { + for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) { err = (*simple_buffer_queue_)->Enqueue( - simple_buffer_queue_, - audio_data_[i], - buffer_size_bytes_); + simple_buffer_queue_, audio_data_[i], buffer_size_bytes_); if (SL_RESULT_SUCCESS != err) { HandleError(err); + started_ = false; return; } } - // Start the recording by setting the state to |SL_RECORDSTATE_RECORDING|. + // Start the recording by setting the state to SL_RECORDSTATE_RECORDING. + // When the object is in the SL_RECORDSTATE_RECORDING state, adding buffers + // will implicitly start the filling process. err = (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING); - if (SL_RESULT_SUCCESS != err) + if (SL_RESULT_SUCCESS != err) { HandleError(err); + started_ = false; + return; + } + + started_ = true; } void OpenSLESInputStream::Stop() { + DVLOG(2) << "OpenSLESInputStream::Stop()"; + DCHECK(thread_checker_.CalledOnValidThread()); if (!started_) return; - // Stop recording by setting the record state to |SL_RECORDSTATE_STOPPED|. + base::AutoLock lock(lock_); + + // Stop recording by setting the record state to SL_RECORDSTATE_STOPPED. LOG_ON_FAILURE_AND_RETURN( - (*recorder_)->SetRecordState(recorder_, - SL_RECORDSTATE_STOPPED)); + (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED)); // Clear the buffer queue to get rid of old data when resuming recording. LOG_ON_FAILURE_AND_RETURN( @@ -114,17 +135,32 @@ void OpenSLESInputStream::Stop() { } void OpenSLESInputStream::Close() { + DVLOG(2) << "OpenSLESInputStream::Close()"; + DCHECK(thread_checker_.CalledOnValidThread()); + // Stop the stream if it is still recording. Stop(); + { + base::AutoLock lock(lock_); + + // TODO(henrika): we use |callback_| in Close() but |callback_| is set + // in Start(). Hence, it should be cleared in Stop() and not used here. + if (callback_) { + callback_->OnClose(this); + callback_ = NULL; + } - // Explicitly free the player objects and invalidate their associated - // interfaces. They have to be done in the correct order. - recorder_object_.Reset(); - engine_object_.Reset(); - simple_buffer_queue_ = NULL; - recorder_ = NULL; + // Destroy the buffer queue recorder object and invalidate all associated + // interfaces. + recorder_object_.Reset(); + simple_buffer_queue_ = NULL; + recorder_ = NULL; - ReleaseAudioBuffer(); + // Destroy the engine object. We don't store any associated interface for + // this object. + engine_object_.Reset(); + ReleaseAudioBuffer(); + } audio_manager_->ReleaseInputStream(this); } @@ -134,9 +170,7 @@ double OpenSLESInputStream::GetMaxVolume() { return 0.0; } -void OpenSLESInputStream::SetVolume(double volume) { - NOTIMPLEMENTED(); -} +void OpenSLESInputStream::SetVolume(double volume) { NOTIMPLEMENTED(); } double OpenSLESInputStream::GetVolume() { NOTIMPLEMENTED(); @@ -153,54 +187,47 @@ bool OpenSLESInputStream::GetAutomaticGainControl() { } bool OpenSLESInputStream::CreateRecorder() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!engine_object_.Get()); + DCHECK(!recorder_object_.Get()); + DCHECK(!recorder_); + DCHECK(!simple_buffer_queue_); + // Initializes the engine object with specific option. After working with the // object, we need to free the object and its resources. SLEngineOption option[] = { - { SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE) } - }; - LOG_ON_FAILURE_AND_RETURN(slCreateEngine(engine_object_.Receive(), - 1, - option, - 0, - NULL, - NULL), - false); + {SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}}; + LOG_ON_FAILURE_AND_RETURN( + slCreateEngine(engine_object_.Receive(), 1, option, 0, NULL, NULL), + false); // Realize the SL engine object in synchronous mode. - LOG_ON_FAILURE_AND_RETURN(engine_object_->Realize(engine_object_.Get(), - SL_BOOLEAN_FALSE), - false); + LOG_ON_FAILURE_AND_RETURN( + engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE), false); // Get the SL engine interface which is implicit. SLEngineItf engine; - LOG_ON_FAILURE_AND_RETURN(engine_object_->GetInterface(engine_object_.Get(), - SL_IID_ENGINE, - &engine), + LOG_ON_FAILURE_AND_RETURN(engine_object_->GetInterface( + engine_object_.Get(), SL_IID_ENGINE, &engine), false); // Audio source configuration. SLDataLocator_IODevice mic_locator = { - SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, - SL_DEFAULTDEVICEID_AUDIOINPUT, NULL - }; - SLDataSource audio_source = { &mic_locator, NULL }; + SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audio_source = {&mic_locator, NULL}; // Audio sink configuration. SLDataLocator_AndroidSimpleBufferQueue buffer_queue = { - SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // Locator type. - static_cast<SLuint32>(kNumOfQueuesInBuffer) // Number of buffers. - }; - SLDataSink audio_sink = { &buffer_queue, &format_ }; + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + static_cast<SLuint32>(kMaxNumOfBuffersInQueue)}; + SLDataSink audio_sink = {&buffer_queue, &format_}; // Create an audio recorder. - const SLInterfaceID interface_id[] = { - SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - SL_IID_ANDROIDCONFIGURATION - }; - const SLboolean interface_required[] = { - SL_BOOLEAN_TRUE, - SL_BOOLEAN_TRUE - }; + const SLInterfaceID interface_id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION}; + const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + // Create AudioRecorder and specify SL_IID_ANDROIDCONFIGURATION. LOG_ON_FAILURE_AND_RETURN( (*engine)->CreateAudioRecorder(engine, @@ -219,24 +246,24 @@ bool OpenSLESInputStream::CreateRecorder() { &recorder_config), false); + // Uses the main microphone tuned for audio communications. SLint32 stream_type = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; LOG_ON_FAILURE_AND_RETURN( (*recorder_config)->SetConfiguration(recorder_config, SL_ANDROID_KEY_RECORDING_PRESET, - &stream_type, sizeof(SLint32)), + &stream_type, + sizeof(SLint32)), false); // Realize the recorder object in synchronous mode. LOG_ON_FAILURE_AND_RETURN( - recorder_object_->Realize(recorder_object_.Get(), - SL_BOOLEAN_FALSE), + recorder_object_->Realize(recorder_object_.Get(), SL_BOOLEAN_FALSE), false); // Get an implicit recorder interface. LOG_ON_FAILURE_AND_RETURN( - recorder_object_->GetInterface(recorder_object_.Get(), - SL_IID_RECORD, - &recorder_), + recorder_object_->GetInterface( + recorder_object_.Get(), SL_IID_RECORD, &recorder_), false); // Get the simple buffer queue interface. @@ -249,61 +276,67 @@ bool OpenSLESInputStream::CreateRecorder() { // Register the input callback for the simple buffer queue. // This callback will be called when receiving new data from the device. LOG_ON_FAILURE_AND_RETURN( - (*simple_buffer_queue_)->RegisterCallback(simple_buffer_queue_, - SimpleBufferQueueCallback, - this), + (*simple_buffer_queue_)->RegisterCallback( + simple_buffer_queue_, SimpleBufferQueueCallback, this), false); return true; } void OpenSLESInputStream::SimpleBufferQueueCallback( - SLAndroidSimpleBufferQueueItf buffer_queue, void* instance) { + SLAndroidSimpleBufferQueueItf buffer_queue, + void* instance) { OpenSLESInputStream* stream = reinterpret_cast<OpenSLESInputStream*>(instance); stream->ReadBufferQueue(); } void OpenSLESInputStream::ReadBufferQueue() { + base::AutoLock lock(lock_); if (!started_) return; - // TODO(xians): Get an accurate delay estimation. + TRACE_EVENT0("audio", "OpenSLESOutputStream::ReadBufferQueue"); + + // TODO(henrika): Investigate if it is possible to get an accurate + // delay estimation. callback_->OnData(this, - audio_data_[active_queue_], + audio_data_[active_buffer_index_], buffer_size_bytes_, buffer_size_bytes_, 0.0); // Done with this buffer. Send it to device for recording. - SLresult err = (*simple_buffer_queue_)->Enqueue( - simple_buffer_queue_, - audio_data_[active_queue_], - buffer_size_bytes_); + SLresult err = + (*simple_buffer_queue_)->Enqueue(simple_buffer_queue_, + audio_data_[active_buffer_index_], + buffer_size_bytes_); if (SL_RESULT_SUCCESS != err) HandleError(err); - active_queue_ = (active_queue_ + 1) % kNumOfQueuesInBuffer; + active_buffer_index_ = (active_buffer_index_ + 1) % kMaxNumOfBuffersInQueue; } void OpenSLESInputStream::SetupAudioBuffer() { + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!audio_data_[0]); - for (int i = 0; i < kNumOfQueuesInBuffer; ++i) { + for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) { audio_data_[i] = new uint8[buffer_size_bytes_]; } } void OpenSLESInputStream::ReleaseAudioBuffer() { + DCHECK(thread_checker_.CalledOnValidThread()); if (audio_data_[0]) { - for (int i = 0; i < kNumOfQueuesInBuffer; ++i) { - delete [] audio_data_[i]; + for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) { + delete[] audio_data_[i]; audio_data_[i] = NULL; } } } void OpenSLESInputStream::HandleError(SLresult error) { - DLOG(FATAL) << "OpenSLES Input error " << error; + DLOG(ERROR) << "OpenSLES Input error " << error; if (callback_) callback_->OnError(this); } diff --git a/media/audio/android/opensles_input.h b/media/audio/android/opensles_input.h index 9743992fc6..e05831c671 100644 --- a/media/audio/android/opensles_input.h +++ b/media/audio/android/opensles_input.h @@ -9,18 +9,23 @@ #include <SLES/OpenSLES_Android.h> #include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "media/audio/android/opensles_util.h" #include "media/audio/audio_io.h" #include "media/audio/audio_parameters.h" -#include "media/audio/android/opensles_util.h" namespace media { class AudioManagerAndroid; // Implements PCM audio input support for Android using the OpenSLES API. +// This class is created and lives on the Audio Manager thread but recorded +// audio buffers are given to us from an internal OpenSLES audio thread. +// All public methods should be called on the Audio Manager thread. class OpenSLESInputStream : public AudioInputStream { public: - static const int kNumOfQueuesInBuffer = 2; + static const int kMaxNumOfBuffersInQueue = 2; OpenSLESInputStream(AudioManagerAndroid* manager, const AudioParameters& params); @@ -41,9 +46,12 @@ class OpenSLESInputStream : public AudioInputStream { private: bool CreateRecorder(); + // Called from OpenSLES specific audio worker thread. static void SimpleBufferQueueCallback( - SLAndroidSimpleBufferQueueItf buffer_queue, void* instance); + SLAndroidSimpleBufferQueueItf buffer_queue, + void* instance); + // Called from OpenSLES specific audio worker thread. void ReadBufferQueue(); // Called in Open(); @@ -56,6 +64,12 @@ class OpenSLESInputStream : public AudioInputStream { // the attached AudioInputCallback::OnError(). void HandleError(SLresult error); + base::ThreadChecker thread_checker_; + + // Protects |callback_|, |active_buffer_index_|, |audio_data_|, + // |buffer_size_bytes_| and |simple_buffer_queue_|. + base::Lock lock_; + AudioManagerAndroid* audio_manager_; AudioInputCallback* callback_; @@ -73,9 +87,9 @@ class OpenSLESInputStream : public AudioInputStream { // Audio buffers that are allocated in the constructor based on // info from audio parameters. - uint8* audio_data_[kNumOfQueuesInBuffer]; + uint8* audio_data_[kMaxNumOfBuffersInQueue]; - int active_queue_; + int active_buffer_index_; int buffer_size_bytes_; bool started_; diff --git a/media/audio/android/opensles_output.cc b/media/audio/android/opensles_output.cc index c6d455715d..5643f833c3 100644 --- a/media/audio/android/opensles_output.cc +++ b/media/audio/android/opensles_output.cc @@ -8,13 +8,13 @@ #include "base/logging.h" #include "media/audio/android/audio_manager_android.h" -#define LOG_ON_FAILURE_AND_RETURN(op, ...) \ - do { \ - SLresult err = (op); \ - if (err != SL_RESULT_SUCCESS) { \ +#define LOG_ON_FAILURE_AND_RETURN(op, ...) \ + do { \ + SLresult err = (op); \ + if (err != SL_RESULT_SUCCESS) { \ DLOG(ERROR) << #op << " failed: " << err; \ - return __VA_ARGS__; \ - } \ + return __VA_ARGS__; \ + } \ } while (0) namespace media { @@ -25,10 +25,11 @@ OpenSLESOutputStream::OpenSLESOutputStream(AudioManagerAndroid* manager, callback_(NULL), player_(NULL), simple_buffer_queue_(NULL), - active_queue_(0), + active_buffer_index_(0), buffer_size_bytes_(0), started_(false), volume_(1.0) { + DVLOG(2) << "OpenSLESOutputStream::OpenSLESOutputStream()"; format_.formatType = SL_DATAFORMAT_PCM; format_.numChannels = static_cast<SLuint32>(params.channels()); // Provides sampling rate in milliHertz to OpenSLES. @@ -50,6 +51,8 @@ OpenSLESOutputStream::OpenSLESOutputStream(AudioManagerAndroid* manager, } OpenSLESOutputStream::~OpenSLESOutputStream() { + DVLOG(2) << "OpenSLESOutputStream::~OpenSLESOutputStream()"; + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!engine_object_.Get()); DCHECK(!player_object_.Get()); DCHECK(!output_mixer_.Get()); @@ -59,6 +62,8 @@ OpenSLESOutputStream::~OpenSLESOutputStream() { } bool OpenSLESOutputStream::Open() { + DVLOG(2) << "OpenSLESOutputStream::Open()"; + DCHECK(thread_checker_.CalledOnValidThread()); if (engine_object_.Get()) return false; @@ -66,37 +71,46 @@ bool OpenSLESOutputStream::Open() { return false; SetupAudioBuffer(); + active_buffer_index_ = 0; return true; } void OpenSLESOutputStream::Start(AudioSourceCallback* callback) { + DVLOG(2) << "OpenSLESOutputStream::Start()"; + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(callback); DCHECK(player_); DCHECK(simple_buffer_queue_); if (started_) return; - // Enable the flags before streaming. + base::AutoLock lock(lock_); + DCHECK(callback_ == NULL || callback_ == callback); callback_ = callback; - active_queue_ = 0; - started_ = true; // Avoid start-up glitches by filling up one buffer queue before starting // the stream. - FillBufferQueue(); + FillBufferQueueNoLock(); - // Start streaming data by setting the play state to |SL_PLAYSTATE_PLAYING|. + // Start streaming data by setting the play state to SL_PLAYSTATE_PLAYING. + // For a player object, when the object is in the SL_PLAYSTATE_PLAYING + // state, adding buffers will implicitly start playback. LOG_ON_FAILURE_AND_RETURN( (*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING)); + + started_ = true; } void OpenSLESOutputStream::Stop() { + DVLOG(2) << "OpenSLESOutputStream::Stop()"; + DCHECK(thread_checker_.CalledOnValidThread()); if (!started_) return; - started_ = false; - // Stop playing by setting the play state to |SL_PLAYSTATE_STOPPED|. + base::AutoLock lock(lock_); + + // Stop playing by setting the play state to SL_PLAYSTATE_STOPPED. LOG_ON_FAILURE_AND_RETURN( (*player_)->SetPlayState(player_, SL_PLAYSTATE_STOPPED)); @@ -104,26 +118,48 @@ void OpenSLESOutputStream::Stop() { // resuming playing. LOG_ON_FAILURE_AND_RETURN( (*simple_buffer_queue_)->Clear(simple_buffer_queue_)); + +#ifndef NDEBUG + // Verify that the buffer queue is in fact cleared as it should. + SLAndroidSimpleBufferQueueState buffer_queue_state; + LOG_ON_FAILURE_AND_RETURN((*simple_buffer_queue_)->GetState( + simple_buffer_queue_, &buffer_queue_state)); + DCHECK_EQ(0u, buffer_queue_state.count); + DCHECK_EQ(0u, buffer_queue_state.index); +#endif + + started_ = false; } void OpenSLESOutputStream::Close() { + DVLOG(2) << "OpenSLESOutputStream::Close()"; + DCHECK(thread_checker_.CalledOnValidThread()); + // Stop the stream if it is still playing. Stop(); - - // Explicitly free the player objects and invalidate their associated - // interfaces. They have to be done in the correct order. - player_object_.Reset(); - output_mixer_.Reset(); - engine_object_.Reset(); - simple_buffer_queue_ = NULL; - player_ = NULL; - - ReleaseAudioBuffer(); + { + // Destroy the buffer queue player object and invalidate all associated + // interfaces. + player_object_.Reset(); + simple_buffer_queue_ = NULL; + player_ = NULL; + + // Destroy the mixer object. We don't store any associated interface for + // this object. + output_mixer_.Reset(); + + // Destroy the engine object. We don't store any associated interface for + // this object. + engine_object_.Reset(); + ReleaseAudioBuffer(); + } audio_manager_->ReleaseOutputStream(this); } void OpenSLESOutputStream::SetVolume(double volume) { + DVLOG(2) << "OpenSLESOutputStream::SetVolume(" << volume << ")"; + DCHECK(thread_checker_.CalledOnValidThread()); float volume_float = static_cast<float>(volume); if (volume_float < 0.0f || volume_float > 1.0f) { return; @@ -132,70 +168,61 @@ void OpenSLESOutputStream::SetVolume(double volume) { } void OpenSLESOutputStream::GetVolume(double* volume) { + DCHECK(thread_checker_.CalledOnValidThread()); *volume = static_cast<double>(volume_); } bool OpenSLESOutputStream::CreatePlayer() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!engine_object_.Get()); + DCHECK(!player_object_.Get()); + DCHECK(!output_mixer_.Get()); + DCHECK(!player_); + DCHECK(!simple_buffer_queue_); + // Initializes the engine object with specific option. After working with the // object, we need to free the object and its resources. SLEngineOption option[] = { - { SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE) } - }; + {SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}}; LOG_ON_FAILURE_AND_RETURN( slCreateEngine(engine_object_.Receive(), 1, option, 0, NULL, NULL), false); // Realize the SL engine object in synchronous mode. LOG_ON_FAILURE_AND_RETURN( - engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE), - false); + engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE), false); // Get the SL engine interface which is implicit. SLEngineItf engine; - LOG_ON_FAILURE_AND_RETURN( - engine_object_->GetInterface(engine_object_.Get(), - SL_IID_ENGINE, - &engine), - false); + LOG_ON_FAILURE_AND_RETURN(engine_object_->GetInterface( + engine_object_.Get(), SL_IID_ENGINE, &engine), + false); // Create ouput mixer object to be used by the player. - LOG_ON_FAILURE_AND_RETURN( - (*engine)->CreateOutputMix(engine, - output_mixer_.Receive(), - 0, - NULL, - NULL), - false); + LOG_ON_FAILURE_AND_RETURN((*engine)->CreateOutputMix( + engine, output_mixer_.Receive(), 0, NULL, NULL), + false); // Realizing the output mix object in synchronous mode. LOG_ON_FAILURE_AND_RETURN( - output_mixer_->Realize(output_mixer_.Get(), SL_BOOLEAN_FALSE), - false); + output_mixer_->Realize(output_mixer_.Get(), SL_BOOLEAN_FALSE), false); // Audio source configuration. SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = { - SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, - static_cast<SLuint32>(kNumOfQueuesInBuffer) - }; - SLDataSource audio_source = { &simple_buffer_queue, &format_ }; + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + static_cast<SLuint32>(kMaxNumOfBuffersInQueue)}; + SLDataSource audio_source = {&simple_buffer_queue, &format_}; // Audio sink configuration. - SLDataLocator_OutputMix locator_output_mix = { - SL_DATALOCATOR_OUTPUTMIX, output_mixer_.Get() - }; - SLDataSink audio_sink = { &locator_output_mix, NULL }; + SLDataLocator_OutputMix locator_output_mix = {SL_DATALOCATOR_OUTPUTMIX, + output_mixer_.Get()}; + SLDataSink audio_sink = {&locator_output_mix, NULL}; // Create an audio player. - const SLInterfaceID interface_id[] = { - SL_IID_BUFFERQUEUE, - SL_IID_VOLUME, - SL_IID_ANDROIDCONFIGURATION - }; - const SLboolean interface_required[] = { - SL_BOOLEAN_TRUE, - SL_BOOLEAN_TRUE, - SL_BOOLEAN_TRUE - }; + const SLInterfaceID interface_id[] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, + SL_IID_ANDROIDCONFIGURATION}; + const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE}; LOG_ON_FAILURE_AND_RETURN( (*engine)->CreateAudioPlayer(engine, player_object_.Receive(), @@ -209,22 +236,21 @@ bool OpenSLESOutputStream::CreatePlayer() { // Create AudioPlayer and specify SL_IID_ANDROIDCONFIGURATION. SLAndroidConfigurationItf player_config; LOG_ON_FAILURE_AND_RETURN( - player_object_->GetInterface(player_object_.Get(), - SL_IID_ANDROIDCONFIGURATION, - &player_config), + player_object_->GetInterface( + player_object_.Get(), SL_IID_ANDROIDCONFIGURATION, &player_config), false); SLint32 stream_type = SL_ANDROID_STREAM_VOICE; LOG_ON_FAILURE_AND_RETURN( (*player_config)->SetConfiguration(player_config, SL_ANDROID_KEY_STREAM_TYPE, - &stream_type, sizeof(SLint32)), + &stream_type, + sizeof(SLint32)), false); // Realize the player object in synchronous mode. LOG_ON_FAILURE_AND_RETURN( - player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE), - false); + player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE), false); // Get an implicit player interface. LOG_ON_FAILURE_AND_RETURN( @@ -233,72 +259,104 @@ bool OpenSLESOutputStream::CreatePlayer() { // Get the simple buffer queue interface. LOG_ON_FAILURE_AND_RETURN( - player_object_->GetInterface(player_object_.Get(), - SL_IID_BUFFERQUEUE, - &simple_buffer_queue_), + player_object_->GetInterface( + player_object_.Get(), SL_IID_BUFFERQUEUE, &simple_buffer_queue_), false); // Register the input callback for the simple buffer queue. // This callback will be called when the soundcard needs data. LOG_ON_FAILURE_AND_RETURN( - (*simple_buffer_queue_)->RegisterCallback(simple_buffer_queue_, - SimpleBufferQueueCallback, - this), + (*simple_buffer_queue_)->RegisterCallback( + simple_buffer_queue_, SimpleBufferQueueCallback, this), false); return true; } void OpenSLESOutputStream::SimpleBufferQueueCallback( - SLAndroidSimpleBufferQueueItf buffer_queue, void* instance) { + SLAndroidSimpleBufferQueueItf buffer_queue, + void* instance) { OpenSLESOutputStream* stream = reinterpret_cast<OpenSLESOutputStream*>(instance); stream->FillBufferQueue(); } void OpenSLESOutputStream::FillBufferQueue() { + base::AutoLock lock(lock_); if (!started_) return; TRACE_EVENT0("audio", "OpenSLESOutputStream::FillBufferQueue"); + + // Verify that we are in a playing state. + SLuint32 state; + SLresult err = (*player_)->GetPlayState(player_, &state); + if (SL_RESULT_SUCCESS != err) { + HandleError(err); + return; + } + if (state != SL_PLAYSTATE_PLAYING) { + DLOG(WARNING) << "Received callback in non-playing state"; + return; + } + + // Fill up one buffer in the queue by asking the registered source for + // data using the OnMoreData() callback. + FillBufferQueueNoLock(); +} + +void OpenSLESOutputStream::FillBufferQueueNoLock() { + // Ensure that the calling thread has acquired the lock since it is not + // done in this method. + lock_.AssertAcquired(); + // Read data from the registered client source. - // TODO(xians): Get an accurate delay estimation. - uint32 hardware_delay = buffer_size_bytes_; + // TODO(henrika): Investigate if it is possible to get a more accurate + // delay estimation. + const uint32 hardware_delay = buffer_size_bytes_; int frames_filled = callback_->OnMoreData( audio_bus_.get(), AudioBuffersState(0, hardware_delay)); - if (frames_filled <= 0) - return; // Audio source is shutting down, or halted on error. - int num_filled_bytes = + if (frames_filled <= 0) { + // Audio source is shutting down, or halted on error. + return; + } + + // Note: If the internal representation ever changes from 16-bit PCM to + // raw float, the data must be clipped and sanitized since it may come + // from an untrusted source such as NaCl. + audio_bus_->Scale(volume_); + audio_bus_->ToInterleaved(frames_filled, + format_.bitsPerSample / 8, + audio_data_[active_buffer_index_]); + + const int num_filled_bytes = frames_filled * audio_bus_->channels() * format_.bitsPerSample / 8; DCHECK_LE(static_cast<size_t>(num_filled_bytes), buffer_size_bytes_); - // Note: If this ever changes to output raw float the data must be clipped and - // sanitized since it may come from an untrusted source such as NaCl. - audio_bus_->Scale(volume_); - audio_bus_->ToInterleaved( - frames_filled, format_.bitsPerSample / 8, audio_data_[active_queue_]); // Enqueue the buffer for playback. - SLresult err = (*simple_buffer_queue_)->Enqueue( - simple_buffer_queue_, - audio_data_[active_queue_], - num_filled_bytes); + SLresult err = + (*simple_buffer_queue_)->Enqueue(simple_buffer_queue_, + audio_data_[active_buffer_index_], + num_filled_bytes); if (SL_RESULT_SUCCESS != err) HandleError(err); - active_queue_ = (active_queue_ + 1) % kNumOfQueuesInBuffer; + active_buffer_index_ = (active_buffer_index_ + 1) % kMaxNumOfBuffersInQueue; } void OpenSLESOutputStream::SetupAudioBuffer() { + DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!audio_data_[0]); - for (int i = 0; i < kNumOfQueuesInBuffer; ++i) { + for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) { audio_data_[i] = new uint8[buffer_size_bytes_]; } } void OpenSLESOutputStream::ReleaseAudioBuffer() { + DCHECK(thread_checker_.CalledOnValidThread()); if (audio_data_[0]) { - for (int i = 0; i < kNumOfQueuesInBuffer; ++i) { - delete [] audio_data_[i]; + for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) { + delete[] audio_data_[i]; audio_data_[i] = NULL; } } diff --git a/media/audio/android/opensles_output.h b/media/audio/android/opensles_output.h index f505b5165c..7232d5da5f 100644 --- a/media/audio/android/opensles_output.h +++ b/media/audio/android/opensles_output.h @@ -9,6 +9,8 @@ #include <SLES/OpenSLES_Android.h> #include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" #include "media/audio/android/opensles_util.h" #include "media/audio/audio_io.h" #include "media/audio/audio_parameters.h" @@ -18,9 +20,12 @@ namespace media { class AudioManagerAndroid; // Implements PCM audio output support for Android using the OpenSLES API. +// This class is created and lives on the Audio Manager thread but recorded +// audio buffers are given to us from an internal OpenSLES audio thread. +// All public methods should be called on the Audio Manager thread. class OpenSLESOutputStream : public AudioOutputStream { public: - static const int kNumOfQueuesInBuffer = 2; + static const int kMaxNumOfBuffersInQueue = 2; OpenSLESOutputStream(AudioManagerAndroid* manager, const AudioParameters& params); @@ -38,11 +43,18 @@ class OpenSLESOutputStream : public AudioOutputStream { private: bool CreatePlayer(); + // Called from OpenSLES specific audio worker thread. static void SimpleBufferQueueCallback( - SLAndroidSimpleBufferQueueItf buffer_queue, void* instance); + SLAndroidSimpleBufferQueueItf buffer_queue, + void* instance); + // Fills up one buffer by asking the registered source for data. + // Called from OpenSLES specific audio worker thread. void FillBufferQueue(); + // Called from the audio manager thread. + void FillBufferQueueNoLock(); + // Called in Open(); void SetupAudioBuffer(); @@ -53,6 +65,12 @@ class OpenSLESOutputStream : public AudioOutputStream { // the attached AudioOutputCallback::OnError(). void HandleError(SLresult error); + base::ThreadChecker thread_checker_; + + // Protects |callback_|, |active_buffer_index_|, |audio_data_|, + // |buffer_size_bytes_| and |simple_buffer_queue_|. + base::Lock lock_; + AudioManagerAndroid* audio_manager_; AudioSourceCallback* callback_; @@ -69,10 +87,11 @@ class OpenSLESOutputStream : public AudioOutputStream { SLDataFormat_PCM format_; - // Audio buffer arrays that are allocated in the constructor. - uint8* audio_data_[kNumOfQueuesInBuffer]; + // Audio buffers that are allocated in the constructor based on + // info from audio parameters. + uint8* audio_data_[kMaxNumOfBuffersInQueue]; - int active_queue_; + int active_buffer_index_; size_t buffer_size_bytes_; bool started_; @@ -88,4 +107,4 @@ class OpenSLESOutputStream : public AudioOutputStream { } // namespace media -#endif // MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_ +#endif // MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_ diff --git a/media/audio/android/opensles_wrapper.cc b/media/audio/android/opensles_wrapper.cc new file mode 100644 index 0000000000..b8f9ea45e4 --- /dev/null +++ b/media/audio/android/opensles_wrapper.cc @@ -0,0 +1,109 @@ +// 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. + +// The file defines the symbols from OpenSLES that android is using. It then +// loads the library dynamically on first use. + +// The openSLES API is using constant as part of the API. This file will define +// proxies for those constants and redefine those when the library is first +// loaded. For this, it need to be able to change their content and so import +// the headers without const. This is correct because OpenSLES.h is a C API. +#define const +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> +#undef const + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/native_library.h" + +// The constants used in chromium. SLInterfaceID is actually a pointer to +// SLInterfaceID_. Those symbols are defined as extern symbols in the OpenSLES +// headers. They will be initialized to their correct values when the library is +// loaded. +SLInterfaceID SL_IID_ENGINE = NULL; +SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE = NULL; +SLInterfaceID SL_IID_ANDROIDCONFIGURATION = NULL; +SLInterfaceID SL_IID_RECORD = NULL; +SLInterfaceID SL_IID_BUFFERQUEUE = NULL; +SLInterfaceID SL_IID_VOLUME = NULL; +SLInterfaceID SL_IID_PLAY = NULL; + +namespace { + +// The name of the library to load. +const char kOpenSLLibraryName[] = "libOpenSLES.so"; + +// Loads the OpenSLES library, and initializes all the proxies. +base::NativeLibrary IntializeLibraryHandle() { + base::NativeLibrary handle = + base::LoadNativeLibrary(base::FilePath(kOpenSLLibraryName), NULL); + DCHECK(handle) << "Unable to load " << kOpenSLLibraryName; + + // Setup the proxy for each symbol. + // Attach the symbol name to the proxy address. + struct SymbolDefinition { + const char* name; + SLInterfaceID* sl_iid; + }; + + // The list of defined symbols. + const SymbolDefinition kSymbols[] = { + {"SL_IID_ENGINE", &SL_IID_ENGINE}, + {"SL_IID_ANDROIDSIMPLEBUFFERQUEUE", &SL_IID_ANDROIDSIMPLEBUFFERQUEUE}, + {"SL_IID_ANDROIDCONFIGURATION", &SL_IID_ANDROIDCONFIGURATION}, + {"SL_IID_RECORD", &SL_IID_RECORD}, + {"SL_IID_BUFFERQUEUE", &SL_IID_BUFFERQUEUE}, + {"SL_IID_VOLUME", &SL_IID_VOLUME}, + {"SL_IID_PLAY", &SL_IID_PLAY} + }; + + for (size_t i = 0; i < sizeof(kSymbols) / sizeof(kSymbols[0]); ++i) { + memcpy(kSymbols[i].sl_iid, + base::GetFunctionPointerFromNativeLibrary(handle, kSymbols[i].name), + sizeof(SLInterfaceID)); + DCHECK(*kSymbols[i].sl_iid) << "Unable to find symbol for " + << kSymbols[i].name; + } + return handle; +} + +// Returns the handler to the shared library. The library itself will be lazily +// loaded during the first call to this function. +base::NativeLibrary LibraryHandle() { + // The handle is lazily initialized on the first call. + static base::NativeLibrary g_opensles_LibraryHandle = + IntializeLibraryHandle(); + return g_opensles_LibraryHandle; +} + +} // namespace + +// Redefine slCreateEngine symbol. +SLresult slCreateEngine(SLObjectItf* engine, + SLuint32 num_options, + SLEngineOption* engine_options, + SLuint32 num_interfaces, + SLInterfaceID* interface_ids, + SLboolean* interfaces_required) { + typedef SLresult (*SlCreateEngineSignature)(SLObjectItf*, + SLuint32, + SLEngineOption*, + SLuint32, + SLInterfaceID*, + SLboolean*); + static SlCreateEngineSignature g_sl_create_engine_handle = + reinterpret_cast<SlCreateEngineSignature>( + base::GetFunctionPointerFromNativeLibrary(LibraryHandle(), + "slCreateEngine")); + DCHECK(g_sl_create_engine_handle) + << "Unable to find symbol for slCreateEngine"; + return g_sl_create_engine_handle(engine, + num_options, + engine_options, + num_interfaces, + interface_ids, + interfaces_required); +} diff --git a/media/audio/audio_input_device_unittest.cc b/media/audio/audio_input_device_unittest.cc deleted file mode 100644 index 61a97832f6..0000000000 --- a/media/audio/audio_input_device_unittest.cc +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2012 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/environment.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "media/audio/audio_manager.h" -#include "media/audio/audio_manager_base.h" -#include "testing/gtest/include/gtest/gtest.h" - -#if defined(OS_WIN) -#include "base/win/scoped_com_initializer.h" -#include "media/audio/win/audio_manager_win.h" -#include "media/audio/win/wavein_input_win.h" -#endif - -namespace media { - -// Test fixture which allows us to override the default enumeration API on -// Windows. -class AudioInputDeviceTest - : public ::testing::Test { - protected: - AudioInputDeviceTest() - : audio_manager_(AudioManager::Create()) -#if defined(OS_WIN) - , com_init_(base::win::ScopedCOMInitializer::kMTA) -#endif - { - } - -#if defined(OS_WIN) - bool SetMMDeviceEnumeration() { - AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); - // Windows Wave is used as default if Windows XP was detected => - // return false since MMDevice is not supported on XP. - if (amw->enumeration_type() == AudioManagerWin::kWaveEnumeration) - return false; - - amw->SetEnumerationType(AudioManagerWin::kMMDeviceEnumeration); - return true; - } - - void SetWaveEnumeration() { - AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); - amw->SetEnumerationType(AudioManagerWin::kWaveEnumeration); - } - - std::string GetDeviceIdFromPCMWaveInAudioInputStream( - const std::string& device_id) { - AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); - AudioParameters parameters( - AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, - AudioParameters::kAudioCDSampleRate, 16, - 1024); - scoped_ptr<PCMWaveInAudioInputStream> stream( - static_cast<PCMWaveInAudioInputStream*>( - amw->CreatePCMWaveInAudioInputStream(parameters, device_id))); - return stream.get() ? stream->device_id_ : std::string(); - } -#endif - - // Helper method which verifies that the device list starts with a valid - // default record followed by non-default device names. - static void CheckDeviceNames(const AudioDeviceNames& device_names) { - if (!device_names.empty()) { - AudioDeviceNames::const_iterator it = device_names.begin(); - - // The first device in the list should always be the default device. - EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceName), - it->device_name); - EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceId), it->unique_id); - ++it; - - // Other devices should have non-empty name and id and should not contain - // default name or id. - while (it != device_names.end()) { - EXPECT_FALSE(it->device_name.empty()); - EXPECT_FALSE(it->unique_id.empty()); - EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceName), - it->device_name); - EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceId), - it->unique_id); - ++it; - } - } else { - // Log a warning so we can see the status on the build bots. No need to - // break the test though since this does successfully test the code and - // some failure cases. - LOG(WARNING) << "No input devices detected"; - } - } - - bool CanRunAudioTest() { - return audio_manager_->HasAudioInputDevices(); - } - - scoped_ptr<AudioManager> audio_manager_; - -#if defined(OS_WIN) - // The MMDevice API requires COM to be initialized on the current thread. - base::win::ScopedCOMInitializer com_init_; -#endif -}; - -// Test that devices can be enumerated. -TEST_F(AudioInputDeviceTest, EnumerateDevices) { - if (!CanRunAudioTest()) - return; - - AudioDeviceNames device_names; - audio_manager_->GetAudioInputDeviceNames(&device_names); - CheckDeviceNames(device_names); -} - -// Run additional tests for Windows since enumeration can be done using -// two different APIs. MMDevice is default for Vista and higher and Wave -// is default for XP and lower. -#if defined(OS_WIN) - -// Override default enumeration API and force usage of Windows MMDevice. -// This test will only run on Windows Vista and higher. -TEST_F(AudioInputDeviceTest, EnumerateDevicesWinMMDevice) { - if (!CanRunAudioTest()) - return; - - AudioDeviceNames device_names; - if (!SetMMDeviceEnumeration()) { - // Usage of MMDevice will fail on XP and lower. - LOG(WARNING) << "MM device enumeration is not supported."; - return; - } - audio_manager_->GetAudioInputDeviceNames(&device_names); - CheckDeviceNames(device_names); -} - -// Override default enumeration API and force usage of Windows Wave. -// This test will run on Windows XP, Windows Vista and Windows 7. -TEST_F(AudioInputDeviceTest, EnumerateDevicesWinWave) { - if (!CanRunAudioTest()) - return; - - AudioDeviceNames device_names; - SetWaveEnumeration(); - audio_manager_->GetAudioInputDeviceNames(&device_names); - CheckDeviceNames(device_names); -} - -TEST_F(AudioInputDeviceTest, WinXPDeviceIdUnchanged) { - if (!CanRunAudioTest()) - return; - - AudioDeviceNames xp_device_names; - SetWaveEnumeration(); - audio_manager_->GetAudioInputDeviceNames(&xp_device_names); - CheckDeviceNames(xp_device_names); - - // Device ID should remain unchanged, including the default device ID. - for (AudioDeviceNames::iterator i = xp_device_names.begin(); - i != xp_device_names.end(); ++i) { - EXPECT_EQ(i->unique_id, - GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id)); - } -} - -TEST_F(AudioInputDeviceTest, ConvertToWinXPInputDeviceId) { - if (!CanRunAudioTest()) - return; - - if (!SetMMDeviceEnumeration()) { - // Usage of MMDevice will fail on XP and lower. - LOG(WARNING) << "MM device enumeration is not supported."; - return; - } - - AudioDeviceNames device_names; - audio_manager_->GetAudioInputDeviceNames(&device_names); - CheckDeviceNames(device_names); - - for (AudioDeviceNames::iterator i = device_names.begin(); - i != device_names.end(); ++i) { - std::string converted_id = - GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id); - if (i == device_names.begin()) { - // The first in the list is the default device ID, which should not be - // changed when passed to PCMWaveInAudioInputStream. - EXPECT_EQ(i->unique_id, converted_id); - } else { - // MMDevice-style device IDs should be converted to WaveIn-style device - // IDs. - EXPECT_NE(i->unique_id, converted_id); - } - } -} - -#endif - -} // namespace media diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc index 1477ce3c5a..5b1f4b3690 100644 --- a/media/audio/audio_manager_base.cc +++ b/media/audio/audio_manager_base.cc @@ -34,6 +34,7 @@ static const int kMaxInputChannels = 2; const char AudioManagerBase::kDefaultDeviceName[] = "Default"; const char AudioManagerBase::kDefaultDeviceId[] = "default"; +const char AudioManagerBase::kLoopbackInputDeviceId[] = "loopback"; struct AudioManagerBase::DispatcherParams { DispatcherParams(const AudioParameters& input, @@ -310,8 +311,6 @@ void AudioManagerBase::GetAudioInputDeviceNames( void AudioManagerBase::GetAudioOutputDeviceNames( AudioDeviceNames* device_names) { - // TODO(joi): Remove this and keep pure virtual once implemented everywhere. - NOTREACHED() << "Don't use this yet, it's not ready on all platforms!"; } void AudioManagerBase::ReleaseOutputStream(AudioOutputStream* stream) { diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h index 15c1b24066..cdf7d3a76a 100644 --- a/media/audio/audio_manager_base.h +++ b/media/audio/audio_manager_base.h @@ -32,11 +32,24 @@ class AudioOutputDispatcher; // AudioManagerBase provides AudioManager functions common for all platforms. class MEDIA_EXPORT AudioManagerBase : public AudioManager { public: + // TODO(sergeyu): The constants below belong to AudioManager interface, not + // to the base implementation. + // Name of the generic "default" device. static const char kDefaultDeviceName[]; // Unique Id of the generic "default" device. static const char kDefaultDeviceId[]; + // Input device ID used to capture the default system playback stream. When + // this device ID is passed to MakeAudioInputStream() the returned + // AudioInputStream will be capturing audio currently being played on the + // default playback device. At the moment this feature is supported only on + // some platforms. AudioInputStream::Intialize() will return an error on + // platforms that don't support it. GetInputStreamParameters() must be used + // to get the parameters of the loopback device before creating a loopback + // stream, otherwise stream initialization may fail. + static const char kLoopbackInputDeviceId[]; + virtual ~AudioManagerBase(); virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE; diff --git a/media/audio/audio_manager_unittest.cc b/media/audio/audio_manager_unittest.cc index 96300c9a83..4747c2e299 100644 --- a/media/audio/audio_manager_unittest.cc +++ b/media/audio/audio_manager_unittest.cc @@ -2,9 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/environment.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "media/audio/audio_manager.h" +#include "media/audio/audio_manager_base.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_LINUX) @@ -12,8 +14,10 @@ #endif // defined(OS_LINUX) #if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" #include "media/audio/win/audio_manager_win.h" -#endif // defined(OS_WIN) +#include "media/audio/win/wavein_input_win.h" +#endif #if defined(USE_PULSEAUDIO) #include "media/audio/pulse/audio_manager_pulse.h" @@ -21,109 +25,305 @@ namespace media { -void GetAudioOutputDeviceNamesImpl(AudioManager* audio_manager) { - AudioDeviceNames device_names; - audio_manager->GetAudioOutputDeviceNames(&device_names); - - VLOG(2) << "Got " << device_names.size() << " audio output devices."; - for (AudioDeviceNames::iterator it = device_names.begin(); - it != device_names.end(); - ++it) { - EXPECT_FALSE(it->unique_id.empty()); - EXPECT_FALSE(it->device_name.empty()); - VLOG(2) << "Device ID(" << it->unique_id << "), label: " << it->device_name; +// Test fixture which allows us to override the default enumeration API on +// Windows. +class AudioManagerTest + : public ::testing::Test { + protected: + AudioManagerTest() + : audio_manager_(AudioManager::Create()) +#if defined(OS_WIN) + , com_init_(base::win::ScopedCOMInitializer::kMTA) +#endif + { } -} -// So that tests herein can be friends of AudioManagerWin. -// -// TODO(joi): Make this go away by unifying audio_manager_unittest.cc -// and audio_input_device_unittest.cc -class AudioManagerTest : public ::testing::Test { - public: - bool SetupForSecondTest(AudioManager* amw) { #if defined(OS_WIN) - AudioManagerWin* audio_manager_win = static_cast<AudioManagerWin*>(amw); - if (audio_manager_win->enumeration_type() == - AudioManagerWin::kWaveEnumeration) { - // This will be true only if running on Windows XP. - VLOG(2) << "AudioManagerWin on WinXP; nothing more to test."; + bool SetMMDeviceEnumeration() { + AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); + // Windows Wave is used as default if Windows XP was detected => + // return false since MMDevice is not supported on XP. + if (amw->enumeration_type() == AudioManagerWin::kWaveEnumeration) + return false; + + amw->SetEnumerationType(AudioManagerWin::kMMDeviceEnumeration); + return true; + } + + void SetWaveEnumeration() { + AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); + amw->SetEnumerationType(AudioManagerWin::kWaveEnumeration); + } + + std::string GetDeviceIdFromPCMWaveInAudioInputStream( + const std::string& device_id) { + AudioManagerWin* amw = static_cast<AudioManagerWin*>(audio_manager_.get()); + AudioParameters parameters( + AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, + AudioParameters::kAudioCDSampleRate, 16, + 1024); + scoped_ptr<PCMWaveInAudioInputStream> stream( + static_cast<PCMWaveInAudioInputStream*>( + amw->CreatePCMWaveInAudioInputStream(parameters, device_id))); + return stream.get() ? stream->device_id_ : std::string(); + } +#endif + + // Helper method which verifies that the device list starts with a valid + // default record followed by non-default device names. + static void CheckDeviceNames(const AudioDeviceNames& device_names) { + VLOG(2) << "Got " << device_names.size() << " audio devices."; + if (!device_names.empty()) { + AudioDeviceNames::const_iterator it = device_names.begin(); + + // The first device in the list should always be the default device. + EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceName), + it->device_name); + EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceId), it->unique_id); + ++it; + + // Other devices should have non-empty name and id and should not contain + // default name or id. + while (it != device_names.end()) { + EXPECT_FALSE(it->device_name.empty()); + EXPECT_FALSE(it->unique_id.empty()); + VLOG(2) << "Device ID(" << it->unique_id + << "), label: " << it->device_name; + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceName), + it->device_name); + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceId), + it->unique_id); + ++it; + } } else { - VLOG(2) << "Testing AudioManagerWin in fallback WinXP mode."; - audio_manager_win->SetEnumerationType(AudioManagerWin::kWaveEnumeration); - return true; + // Log a warning so we can see the status on the build bots. No need to + // break the test though since this does successfully test the code and + // some failure cases. + LOG(WARNING) << "No input devices detected"; } -#endif // defined(OS_WIN) - return false; } -}; -TEST_F(AudioManagerTest, GetAudioOutputDeviceNames) { - // On Linux, we may be able to test both the Alsa and Pulseaudio - // versions of the audio manager. -#if defined(USE_PULSEAUDIO) - { - VLOG(2) << "Testing AudioManagerPulse."; - scoped_ptr<AudioManager> pulse_audio_manager(AudioManagerPulse::Create()); - if (pulse_audio_manager.get()) - GetAudioOutputDeviceNamesImpl(pulse_audio_manager.get()); - else - LOG(WARNING) << "No pulseaudio on this system."; + bool CanRunInputTest() { + return audio_manager_->HasAudioInputDevices(); } -#endif // defined(USE_PULSEAUDIO) -#if defined(USE_ALSA) - { - VLOG(2) << "Testing AudioManagerLinux."; - scoped_ptr<AudioManager> alsa_audio_manager(new AudioManagerLinux()); - GetAudioOutputDeviceNamesImpl(alsa_audio_manager.get()); + + bool CanRunOutputTest() { + return audio_manager_->HasAudioOutputDevices(); } -#endif // defined(USE_ALSA) -#if defined(OS_MACOSX) - VLOG(2) << "Testing platform-default AudioManager."; - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - GetAudioOutputDeviceNamesImpl(audio_manager.get()); -#endif // defined(OS_MACOSX) + scoped_ptr<AudioManager> audio_manager_; #if defined(OS_WIN) - { - // TODO(joi): Unify the tests in audio_input_device_unittest.cc - // with the tests in this file, and reuse the Windows-specific - // bits from that file. - VLOG(2) << "Testing AudioManagerWin in its default mode."; - scoped_ptr<AudioManager> audio_manager_win(AudioManager::Create()); - GetAudioOutputDeviceNamesImpl(audio_manager_win.get()); - - if (SetupForSecondTest(audio_manager_win.get())) { - GetAudioOutputDeviceNamesImpl(audio_manager_win.get()); + // The MMDevice API requires COM to be initialized on the current thread. + base::win::ScopedCOMInitializer com_init_; +#endif +}; + +// Test that devices can be enumerated. +TEST_F(AudioManagerTest, EnumerateInputDevices) { + if (!CanRunInputTest()) + return; + + AudioDeviceNames device_names; + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +// Test that devices can be enumerated. +TEST_F(AudioManagerTest, EnumerateOutputDevices) { + if (!CanRunOutputTest()) + return; + + AudioDeviceNames device_names; + audio_manager_->GetAudioOutputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +// Run additional tests for Windows since enumeration can be done using +// two different APIs. MMDevice is default for Vista and higher and Wave +// is default for XP and lower. +#if defined(OS_WIN) + +// Override default enumeration API and force usage of Windows MMDevice. +// This test will only run on Windows Vista and higher. +TEST_F(AudioManagerTest, EnumerateInputDevicesWinMMDevice) { + if (!CanRunInputTest()) + return; + + AudioDeviceNames device_names; + if (!SetMMDeviceEnumeration()) { + // Usage of MMDevice will fail on XP and lower. + LOG(WARNING) << "MM device enumeration is not supported."; + return; + } + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +TEST_F(AudioManagerTest, EnumerateOutputDevicesWinMMDevice) { + if (!CanRunOutputTest()) + return; + + AudioDeviceNames device_names; + if (!SetMMDeviceEnumeration()) { + // Usage of MMDevice will fail on XP and lower. + LOG(WARNING) << "MM device enumeration is not supported."; + return; + } + audio_manager_->GetAudioOutputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +// Override default enumeration API and force usage of Windows Wave. +// This test will run on Windows XP, Windows Vista and Windows 7. +TEST_F(AudioManagerTest, EnumerateInputDevicesWinWave) { + if (!CanRunInputTest()) + return; + + AudioDeviceNames device_names; + SetWaveEnumeration(); + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +TEST_F(AudioManagerTest, EnumerateOutputDevicesWinWave) { + if (!CanRunOutputTest()) + return; + + AudioDeviceNames device_names; + SetWaveEnumeration(); + audio_manager_->GetAudioOutputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +TEST_F(AudioManagerTest, WinXPDeviceIdUnchanged) { + if (!CanRunInputTest()) + return; + + AudioDeviceNames xp_device_names; + SetWaveEnumeration(); + audio_manager_->GetAudioInputDeviceNames(&xp_device_names); + CheckDeviceNames(xp_device_names); + + // Device ID should remain unchanged, including the default device ID. + for (AudioDeviceNames::iterator i = xp_device_names.begin(); + i != xp_device_names.end(); ++i) { + EXPECT_EQ(i->unique_id, + GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id)); + } +} + +TEST_F(AudioManagerTest, ConvertToWinXPInputDeviceId) { + if (!CanRunInputTest()) + return; + + if (!SetMMDeviceEnumeration()) { + // Usage of MMDevice will fail on XP and lower. + LOG(WARNING) << "MM device enumeration is not supported."; + return; + } + + AudioDeviceNames device_names; + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); + + for (AudioDeviceNames::iterator i = device_names.begin(); + i != device_names.end(); ++i) { + std::string converted_id = + GetDeviceIdFromPCMWaveInAudioInputStream(i->unique_id); + if (i == device_names.begin()) { + // The first in the list is the default device ID, which should not be + // changed when passed to PCMWaveInAudioInputStream. + EXPECT_EQ(i->unique_id, converted_id); + } else { + // MMDevice-style device IDs should be converted to WaveIn-style device + // IDs. + EXPECT_NE(i->unique_id, converted_id); } } +} + #endif // defined(OS_WIN) + +#if defined(USE_PULSEAUDIO) +// On Linux, there are two implementations available and both can +// sometimes be tested on a single system. These tests specifically +// test Pulseaudio. + +TEST_F(AudioManagerTest, EnumerateInputDevicesPulseaudio) { + if (!CanRunInputTest()) + return; + + audio_manager_.reset(AudioManagerPulse::Create()); + if (audio_manager_.get()) { + AudioDeviceNames device_names; + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); + } else { + LOG(WARNING) << "No pulseaudio on this system."; + } } +TEST_F(AudioManagerTest, EnumerateOutputDevicesPulseaudio) { + if (!CanRunOutputTest()) + return; + + audio_manager_.reset(AudioManagerPulse::Create()); + if (audio_manager_.get()) { + AudioDeviceNames device_names; + audio_manager_->GetAudioOutputDeviceNames(&device_names); + CheckDeviceNames(device_names); + } else { + LOG(WARNING) << "No pulseaudio on this system."; + } +} +#endif // defined(USE_PULSEAUDIO) + +#if defined(USE_ALSA) +// On Linux, there are two implementations available and both can +// sometimes be tested on a single system. These tests specifically +// test Alsa. + +TEST_F(AudioManagerTest, EnumerateInputDevicesAlsa) { + if (!CanRunInputTest()) + return; + + VLOG(2) << "Testing AudioManagerLinux."; + audio_manager_.reset(new AudioManagerLinux()); + AudioDeviceNames device_names; + audio_manager_->GetAudioInputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} + +TEST_F(AudioManagerTest, EnumerateOutputDevicesAlsa) { + if (!CanRunOutputTest()) + return; + + VLOG(2) << "Testing AudioManagerLinux."; + audio_manager_.reset(new AudioManagerLinux()); + AudioDeviceNames device_names; + audio_manager_->GetAudioOutputDeviceNames(&device_names); + CheckDeviceNames(device_names); +} +#endif // defined(USE_ALSA) + TEST_F(AudioManagerTest, GetDefaultOutputStreamParameters) { #if defined(OS_WIN) || defined(OS_MACOSX) - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - ASSERT_TRUE(audio_manager); - if (!audio_manager->HasAudioOutputDevices()) + if (!CanRunInputTest()) return; - AudioParameters params = audio_manager->GetDefaultOutputStreamParameters(); + AudioParameters params = audio_manager_->GetDefaultOutputStreamParameters(); EXPECT_TRUE(params.IsValid()); #endif // defined(OS_WIN) || defined(OS_MACOSX) } TEST_F(AudioManagerTest, GetAssociatedOutputDeviceID) { #if defined(OS_WIN) || defined(OS_MACOSX) - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - ASSERT_TRUE(audio_manager); - if (!audio_manager->HasAudioOutputDevices() || - !audio_manager->HasAudioInputDevices()) { + if (!CanRunInputTest() || !CanRunOutputTest()) return; - } AudioDeviceNames device_names; - audio_manager->GetAudioInputDeviceNames(&device_names); + audio_manager_->GetAudioInputDeviceNames(&device_names); bool found_an_associated_device = false; for (AudioDeviceNames::iterator it = device_names.begin(); it != device_names.end(); @@ -131,7 +331,7 @@ TEST_F(AudioManagerTest, GetAssociatedOutputDeviceID) { EXPECT_FALSE(it->unique_id.empty()); EXPECT_FALSE(it->device_name.empty()); std::string output_device_id( - audio_manager->GetAssociatedOutputDeviceID(it->unique_id)); + audio_manager_->GetAssociatedOutputDeviceID(it->unique_id)); if (!output_device_id.empty()) { VLOG(2) << it->unique_id << " matches with " << output_device_id; found_an_associated_device = true; diff --git a/media/audio/cras/audio_manager_cras.cc b/media/audio/cras/audio_manager_cras.cc index 276487557e..14a0c4e86a 100644 --- a/media/audio/cras/audio_manager_cras.cc +++ b/media/audio/cras/audio_manager_cras.cc @@ -16,14 +16,21 @@ namespace media { +static void AddDefaultDevice(AudioDeviceNames* device_names) { + DCHECK(device_names->empty()); + + // Cras will route audio from a proper physical device automatically. + device_names->push_back( + AudioDeviceName(AudioManagerBase::kDefaultDeviceName, + AudioManagerBase::kDefaultDeviceId)); +} + // Maximum number of output streams that can be open simultaneously. static const int kMaxOutputStreams = 50; // Default sample rate for input and output streams. static const int kDefaultSampleRate = 48000; -const char AudioManagerCras::kLoopbackDeviceId[] = "loopback"; - bool AudioManagerCras::HasAudioOutputDevices() { return true; } @@ -45,10 +52,13 @@ void AudioManagerCras::ShowAudioInputSettings() { } void AudioManagerCras::GetAudioInputDeviceNames( - media::AudioDeviceNames* device_names) { - DCHECK(device_names->empty()); - GetCrasAudioInputDevices(device_names); - return; + AudioDeviceNames* device_names) { + AddDefaultDevice(device_names); +} + +void AudioManagerCras::GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) { + AddDefaultDevice(device_names); } AudioParameters AudioManagerCras::GetInputStreamParameters( @@ -61,14 +71,6 @@ AudioParameters AudioManagerCras::GetInputStreamParameters( kDefaultSampleRate, 16, kDefaultInputBufferSize); } -void AudioManagerCras::GetCrasAudioInputDevices( - media::AudioDeviceNames* device_names) { - // Cras will route audio from a proper physical device automatically. - device_names->push_back( - AudioDeviceName(AudioManagerBase::kDefaultDeviceName, - AudioManagerBase::kDefaultDeviceId)); -} - AudioOutputStream* AudioManagerCras::MakeLinearOutputStream( const AudioParameters& params) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); diff --git a/media/audio/cras/audio_manager_cras.h b/media/audio/cras/audio_manager_cras.h index 41e1876ac8..3b0ef530e0 100644 --- a/media/audio/cras/audio_manager_cras.h +++ b/media/audio/cras/audio_manager_cras.h @@ -15,18 +15,16 @@ namespace media { class MEDIA_EXPORT AudioManagerCras : public AudioManagerBase { public: - // Unique ID of the "loopback" input device. This device captures post-mix, - // pre-DSP system audio. - static const char kLoopbackDeviceId[]; - AudioManagerCras(); // AudioManager implementation. virtual bool HasAudioOutputDevices() OVERRIDE; virtual bool HasAudioInputDevices() OVERRIDE; virtual void ShowAudioInputSettings() OVERRIDE; - virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; @@ -50,9 +48,6 @@ class MEDIA_EXPORT AudioManagerCras : public AudioManagerBase { const AudioParameters& input_params) OVERRIDE; private: - // Gets a list of available cras input devices. - void GetCrasAudioInputDevices(media::AudioDeviceNames* device_names); - // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream. AudioOutputStream* MakeOutputStream(const AudioParameters& params); diff --git a/media/audio/cras/cras_input.cc b/media/audio/cras/cras_input.cc index 363dc68227..fd574dc86e 100644 --- a/media/audio/cras/cras_input.cc +++ b/media/audio/cras/cras_input.cc @@ -26,9 +26,8 @@ CrasInputStream::CrasInputStream(const AudioParameters& params, params_(params), started_(false), stream_id_(0), - stream_direction_(device_id == AudioManagerCras::kLoopbackDeviceId - ? CRAS_STREAM_POST_MIX_PRE_DSP - : CRAS_STREAM_INPUT) { + stream_direction_(device_id == AudioManagerBase::kLoopbackInputDeviceId ? + CRAS_STREAM_POST_MIX_PRE_DSP : CRAS_STREAM_INPUT) { DCHECK(audio_manager_); } diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc index 7596c2fe25..708e4f2684 100644 --- a/media/audio/linux/audio_manager_linux.cc +++ b/media/audio/linux/audio_manager_linux.cc @@ -103,13 +103,13 @@ void AudioManagerLinux::ShowAudioInputSettings() { } void AudioManagerLinux::GetAudioInputDeviceNames( - media::AudioDeviceNames* device_names) { + AudioDeviceNames* device_names) { DCHECK(device_names->empty()); GetAlsaAudioDevices(kStreamCapture, device_names); } void AudioManagerLinux::GetAudioOutputDeviceNames( - media::AudioDeviceNames* device_names) { + AudioDeviceNames* device_names) { DCHECK(device_names->empty()); GetAlsaAudioDevices(kStreamPlayback, device_names); } diff --git a/media/audio/linux/audio_manager_linux.h b/media/audio/linux/audio_manager_linux.h index 2258e81eb9..ab284dfdce 100644 --- a/media/audio/linux/audio_manager_linux.h +++ b/media/audio/linux/audio_manager_linux.h @@ -25,10 +25,10 @@ class MEDIA_EXPORT AudioManagerLinux : public AudioManagerBase { virtual bool HasAudioOutputDevices() OVERRIDE; virtual bool HasAudioInputDevices() OVERRIDE; virtual void ShowAudioInputSettings() OVERRIDE; - virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; - virtual void GetAudioOutputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h index 9757315920..d162554b40 100644 --- a/media/audio/mac/audio_manager_mac.h +++ b/media/audio/mac/audio_manager_mac.h @@ -27,10 +27,10 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase { // Implementation of AudioManager. virtual bool HasAudioOutputDevices() OVERRIDE; virtual bool HasAudioInputDevices() OVERRIDE; - virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; - virtual void GetAudioOutputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; virtual std::string GetAssociatedOutputDeviceID( diff --git a/media/audio/mock_audio_manager.cc b/media/audio/mock_audio_manager.cc index 2ab2b708da..a164332a64 100644 --- a/media/audio/mock_audio_manager.cc +++ b/media/audio/mock_audio_manager.cc @@ -33,11 +33,11 @@ void MockAudioManager::ShowAudioInputSettings() { } void MockAudioManager::GetAudioInputDeviceNames( - media::AudioDeviceNames* device_names) { + AudioDeviceNames* device_names) { } void MockAudioManager::GetAudioOutputDeviceNames( - media::AudioDeviceNames* device_names) { + AudioDeviceNames* device_names) { } media::AudioOutputStream* MockAudioManager::MakeAudioOutputStream( diff --git a/media/audio/pulse/audio_manager_pulse.h b/media/audio/pulse/audio_manager_pulse.h index 8fc4310cba..3639663992 100644 --- a/media/audio/pulse/audio_manager_pulse.h +++ b/media/audio/pulse/audio_manager_pulse.h @@ -25,10 +25,10 @@ class MEDIA_EXPORT AudioManagerPulse : public AudioManagerBase { virtual bool HasAudioOutputDevices() OVERRIDE; virtual bool HasAudioInputDevices() OVERRIDE; virtual void ShowAudioInputSettings() OVERRIDE; - virtual void GetAudioInputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; - virtual void GetAudioOutputDeviceNames(media::AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; diff --git a/media/audio/shared_memory_util.cc b/media/audio/shared_memory_util.cc index b65df03e2e..523cdb9646 100644 --- a/media/audio/shared_memory_util.cc +++ b/media/audio/shared_memory_util.cc @@ -4,6 +4,8 @@ #include "media/audio/shared_memory_util.h" +#include <algorithm> + #include "base/atomicops.h" #include "base/logging.h" diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc index e081943910..a174ea2ea0 100644 --- a/media/audio/win/audio_low_latency_input_win.cc +++ b/media/audio/win/audio_low_latency_input_win.cc @@ -103,9 +103,8 @@ bool WASAPIAudioInputStream::Open() { // Verify that the selected audio endpoint supports the specified format // set during construction. - if (!DesiredFormatIsSupported()) { + if (!DesiredFormatIsSupported()) return false; - } // Initialize the audio stream between the client and the device using // shared mode and a lowest possible glitch-free latency. @@ -141,6 +140,9 @@ void WASAPIAudioInputStream::Start(AudioInputCallback* callback) { HRESULT hr = audio_client_->Start(); DLOG_IF(ERROR, FAILED(hr)) << "Failed to start input streaming."; + if (SUCCEEDED(hr) && audio_render_client_for_loopback_) + hr = audio_render_client_for_loopback_->Start(); + started_ = SUCCEEDED(hr); } @@ -276,6 +278,10 @@ HRESULT WASAPIAudioInputStream::GetMixFormat(const std::string& device_id, // Retrieve the default capture audio endpoint. hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, endpoint_device.Receive()); + } else if (device_id == AudioManagerBase::kLoopbackInputDeviceId) { + // Capture the default playback stream. + hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, + endpoint_device.Receive()); } else { // Retrieve a capture endpoint device that is specified by an endpoint // device-identification string. @@ -454,42 +460,44 @@ void WASAPIAudioInputStream::HandleError(HRESULT err) { HRESULT WASAPIAudioInputStream::SetCaptureDevice() { ScopedComPtr<IMMDeviceEnumerator> enumerator; - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), - NULL, - CLSCTX_INPROC_SERVER, - __uuidof(IMMDeviceEnumerator), - enumerator.ReceiveVoid()); - if (SUCCEEDED(hr)) { - // Retrieve the IMMDevice by using the specified role or the specified - // unique endpoint device-identification string. - // TODO(henrika): possibly add support for the eCommunications as well. - if (device_id_ == AudioManagerBase::kDefaultDeviceId) { - // Retrieve the default capture audio endpoint for the specified role. - // Note that, in Windows Vista, the MMDevice API supports device roles - // but the system-supplied user interface programs do not. - hr = enumerator->GetDefaultAudioEndpoint(eCapture, - eConsole, - endpoint_device_.Receive()); - } else { - // Retrieve a capture endpoint device that is specified by an endpoint - // device-identification string. - hr = enumerator->GetDevice(UTF8ToUTF16(device_id_).c_str(), - endpoint_device_.Receive()); - } + HRESULT hr = enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_INPROC_SERVER); + if (FAILED(hr)) + return hr; - if (FAILED(hr)) - return hr; + // Retrieve the IMMDevice by using the specified role or the specified + // unique endpoint device-identification string. + // TODO(henrika): possibly add support for the eCommunications as well. + if (device_id_ == AudioManagerBase::kDefaultDeviceId) { + // Retrieve the default capture audio endpoint for the specified role. + // Note that, in Windows Vista, the MMDevice API supports device roles + // but the system-supplied user interface programs do not. + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, + endpoint_device_.Receive()); + } else if (device_id_ == AudioManagerBase::kLoopbackInputDeviceId) { + // Capture the default playback stream. + hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, + endpoint_device_.Receive()); + } else { + // Retrieve a capture endpoint device that is specified by an endpoint + // device-identification string. + hr = enumerator->GetDevice(UTF8ToUTF16(device_id_).c_str(), + endpoint_device_.Receive()); + } - // Verify that the audio endpoint device is active, i.e., the audio - // adapter that connects to the endpoint device is present and enabled. - DWORD state = DEVICE_STATE_DISABLED; - hr = endpoint_device_->GetState(&state); - if (SUCCEEDED(hr)) { - if (!(state & DEVICE_STATE_ACTIVE)) { - DLOG(ERROR) << "Selected capture device is not active."; - hr = E_ACCESSDENIED; - } - } + if (FAILED(hr)) + return hr; + + // Verify that the audio endpoint device is active, i.e., the audio + // adapter that connects to the endpoint device is present and enabled. + DWORD state = DEVICE_STATE_DISABLED; + hr = endpoint_device_->GetState(&state); + if (FAILED(hr)) + return hr; + + if (!(state & DEVICE_STATE_ACTIVE)) { + DLOG(ERROR) << "Selected capture device is not active."; + hr = E_ACCESSDENIED; } return hr; @@ -565,16 +573,25 @@ bool WASAPIAudioInputStream::DesiredFormatIsSupported() { } HRESULT WASAPIAudioInputStream::InitializeAudioEngine() { + DWORD flags; + // Use event-driven mode only fo regular input devices. For loopback the + // EVENTCALLBACK flag is specified when intializing + // |audio_render_client_for_loopback_|. + if (device_id_ == AudioManagerBase::kLoopbackInputDeviceId) { + flags = AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_NOPERSIST; + } else { + flags = + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST; + } + // Initialize the audio stream between the client and the device. - // We connect indirectly through the audio engine by using shared mode - // and WASAPI is initialized in an event driven mode. + // We connect indirectly through the audio engine by using shared mode. // Note that, |hnsBufferDuration| is set of 0, which ensures that the // buffer is never smaller than the minimum buffer size needed to ensure // that glitches do not occur between the periodic processing passes. // This setting should lead to lowest possible latency. HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK | - AUDCLNT_STREAMFLAGS_NOPERSIST, + flags, 0, // hnsBufferDuration 0, &format_, @@ -590,6 +607,7 @@ HRESULT WASAPIAudioInputStream::InitializeAudioEngine() { hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_); if (FAILED(hr)) return hr; + DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_ << " [frames]"; @@ -618,9 +636,41 @@ HRESULT WASAPIAudioInputStream::InitializeAudioEngine() { } #endif - // Set the event handle that the audio engine will signal each time - // a buffer becomes ready to be processed by the client. - hr = audio_client_->SetEventHandle(audio_samples_ready_event_.Get()); + // Set the event handle that the audio engine will signal each time a buffer + // becomes ready to be processed by the client. + // + // In loopback case the capture device doesn't receive any events, so we + // need to create a separate playback client to get notifications. According + // to MSDN: + // + // A pull-mode capture client does not receive any events when a stream is + // initialized with event-driven buffering and is loopback-enabled. To + // work around this, initialize a render stream in event-driven mode. Each + // time the client receives an event for the render stream, it must signal + // the capture client to run the capture thread that reads the next set of + // samples from the capture endpoint buffer. + // + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd316551(v=vs.85).aspx + if (device_id_ == AudioManagerBase::kLoopbackInputDeviceId) { + hr = endpoint_device_->Activate( + __uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, + audio_render_client_for_loopback_.ReceiveVoid()); + if (FAILED(hr)) + return hr; + + hr = audio_render_client_for_loopback_->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, + 0, 0, &format_, NULL); + if (FAILED(hr)) + return hr; + + hr = audio_render_client_for_loopback_->SetEventHandle( + audio_samples_ready_event_.Get()); + } else { + hr = audio_client_->SetEventHandle(audio_samples_ready_event_.Get()); + } + if (FAILED(hr)) return hr; diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h index 4f9c7fb6c8..99e1604925 100644 --- a/media/audio/win/audio_low_latency_input_win.h +++ b/media/audio/win/audio_low_latency_input_win.h @@ -184,6 +184,14 @@ class MEDIA_EXPORT WASAPIAudioInputStream // an audio stream between an audio application and the audio engine. base::win::ScopedComPtr<IAudioClient> audio_client_; + // Loopback IAudioClient doesn't support event-driven mode, so a separate + // IAudioClient is needed to receive notifications when data is available in + // the buffer. For loopback input |audio_client_| is used to receive data, + // while |audio_render_client_for_loopback_| is used to get notifications + // when a new buffer is ready. See comment in InitializeAudioEngine() for + // details. + base::win::ScopedComPtr<IAudioClient> audio_render_client_for_loopback_; + // The IAudioCaptureClient interface enables a client to read input data // from a capture endpoint buffer. base::win::ScopedComPtr<IAudioCaptureClient> audio_capture_client_; diff --git a/media/audio/win/audio_low_latency_input_win_unittest.cc b/media/audio/win/audio_low_latency_input_win_unittest.cc index 40990ec13d..11fad25d3f 100644 --- a/media/audio/win/audio_low_latency_input_win_unittest.cc +++ b/media/audio/win/audio_low_latency_input_win_unittest.cc @@ -39,12 +39,53 @@ ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { public: MOCK_METHOD5(OnData, void(AudioInputStream* stream, - const uint8* src, uint32 size, - uint32 hardware_delay_bytes, double volume)); + const uint8* src, uint32 size, + uint32 hardware_delay_bytes, double volume)); MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); MOCK_METHOD1(OnError, void(AudioInputStream* stream)); }; +class FakeAudioInputCallback : public AudioInputStream::AudioInputCallback { + public: + FakeAudioInputCallback() + : closed_(false), + error_(false), + data_event_(false, false) { + } + + const std::vector<uint8>& received_data() const { return received_data_; } + bool closed() const { return closed_; } + bool error() const { return error_; } + + // Waits until OnData() is called on another thread. + void WaitForData() { + data_event_.Wait(); + } + + virtual void OnData(AudioInputStream* stream, + const uint8* src, uint32 size, + uint32 hardware_delay_bytes, double volume) OVERRIDE { + received_data_.insert(received_data_.end(), src, src + size); + data_event_.Signal(); + } + + virtual void OnClose(AudioInputStream* stream) OVERRIDE { + closed_ = true; + } + + virtual void OnError(AudioInputStream* stream) OVERRIDE { + error_ = true; + } + + private: + std::vector<uint8> received_data_; + base::WaitableEvent data_event_; + bool closed_; + bool error_; + + DISALLOW_COPY_AND_ASSIGN(FakeAudioInputCallback); +}; + // This audio sink implementation should be used for manual tests only since // the recorded data is stored on a raw binary data file. class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { @@ -188,6 +229,39 @@ static AudioInputStream* CreateDefaultAudioInputStream( return ais; } +class ScopedAudioInputStream { + public: + explicit ScopedAudioInputStream(AudioInputStream* stream) + : stream_(stream) {} + + ~ScopedAudioInputStream() { + if (stream_) + stream_->Close(); + } + + void Close() { + if (stream_) + stream_->Close(); + stream_ = NULL; + } + + AudioInputStream* operator->() { + return stream_; + } + + AudioInputStream* get() const { return stream_; } + + void Reset(AudioInputStream* new_stream) { + Close(); + stream_ = new_stream; + } + + private: + AudioInputStream* stream_; + + DISALLOW_COPY_AND_ASSIGN(ScopedAudioInputStream); +}; + // Verify that we can retrieve the current hardware/mixing sample rate // for all available input devices. TEST(WinAudioInputTest, WASAPIAudioInputStreamHardwareSampleRate) { @@ -217,8 +291,9 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamCreateAndClose) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) return; - AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); - ais->Close(); + ScopedAudioInputStream ais( + CreateDefaultAudioInputStream(audio_manager.get())); + ais.Close(); } // Test Open(), Close() calling sequence. @@ -226,9 +301,10 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenAndClose) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) return; - AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); + ScopedAudioInputStream ais( + CreateDefaultAudioInputStream(audio_manager.get())); EXPECT_TRUE(ais->Open()); - ais->Close(); + ais.Close(); } // Test Open(), Start(), Close() calling sequence. @@ -236,13 +312,14 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartAndClose) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) return; - AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); + ScopedAudioInputStream ais( + CreateDefaultAudioInputStream(audio_manager.get())); EXPECT_TRUE(ais->Open()); MockAudioInputCallback sink; ais->Start(&sink); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); } // Test Open(), Start(), Stop(), Close() calling sequence. @@ -250,14 +327,15 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamOpenStartStopAndClose) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) return; - AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); + ScopedAudioInputStream ais( + CreateDefaultAudioInputStream(audio_manager.get())); EXPECT_TRUE(ais->Open()); MockAudioInputCallback sink; ais->Start(&sink); ais->Stop(); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); } // Test some additional calling sequences. @@ -265,8 +343,10 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) { scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); if (!CanRunAudioTests(audio_manager.get())) return; - AudioInputStream* ais = CreateDefaultAudioInputStream(audio_manager.get()); - WASAPIAudioInputStream* wais = static_cast<WASAPIAudioInputStream*>(ais); + ScopedAudioInputStream ais( + CreateDefaultAudioInputStream(audio_manager.get())); + WASAPIAudioInputStream* wais = + static_cast<WASAPIAudioInputStream*>(ais.get()); // Open(), Open() should fail the second time. EXPECT_TRUE(ais->Open()); @@ -286,9 +366,9 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamMiscCallingSequences) { ais->Stop(); EXPECT_FALSE(wais->started()); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); } TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { @@ -304,7 +384,7 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { // Create default WASAPI input stream which records in stereo using // the shared mixing rate. The default buffer size is 10ms. AudioInputStreamWrapper aisw(audio_manager.get()); - AudioInputStream* ais = aisw.Create(); + ScopedAudioInputStream ais(aisw.Create()); EXPECT_TRUE(ais->Open()); MockAudioInputCallback sink; @@ -317,7 +397,7 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { // All should contain valid packets of the same size and a valid delay // estimate. EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) + ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); @@ -327,49 +407,78 @@ TEST(WinAudioInputTest, WASAPIAudioInputStreamTestPacketSizes) { // Store current packet size (to be used in the subsequent tests). int samples_per_packet_10ms = aisw.samples_per_packet(); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); // 20 ms packet size. count = 0; - ais = aisw.Create(2 * samples_per_packet_10ms); + ais.Reset(aisw.Create(2 * samples_per_packet_10ms)); EXPECT_TRUE(ais->Open()); bytes_per_packet = aisw.channels() * aisw.samples_per_packet() * (aisw.bits_per_sample() / 8); EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) + ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); loop.Run(); ais->Stop(); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); // 5 ms packet size. count = 0; - ais = aisw.Create(samples_per_packet_10ms / 2); + ais.Reset(aisw.Create(samples_per_packet_10ms / 2)); EXPECT_TRUE(ais->Open()); bytes_per_packet = aisw.channels() * aisw.samples_per_packet() * (aisw.bits_per_sample() / 8); EXPECT_CALL(sink, OnData( - ais, NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) + ais.get(), NotNull(), bytes_per_packet, Gt(bytes_per_packet), _)) .Times(AtLeast(10)) .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); ais->Start(&sink); loop.Run(); ais->Stop(); - EXPECT_CALL(sink, OnClose(ais)) + EXPECT_CALL(sink, OnClose(ais.get())) .Times(1); - ais->Close(); + ais.Close(); +} + +// Test that we can capture loopback stream. +TEST(WinAudioInputTest, WASAPIAudioInputStreamLoopback) { + scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); + if (!audio_manager->HasAudioOutputDevices() || !CoreAudioUtil::IsSupported()) + return; + + AudioParameters params = audio_manager->GetInputStreamParameters( + AudioManagerBase::kLoopbackInputDeviceId); + + AudioParameters output_params = + audio_manager->GetOutputStreamParameters(std::string()); + EXPECT_EQ(params.sample_rate(), output_params.sample_rate()); + EXPECT_EQ(params.channel_layout(), output_params.channel_layout()); + + ScopedAudioInputStream stream(audio_manager->MakeAudioInputStream( + params, AudioManagerBase::kLoopbackInputDeviceId)); + ASSERT_TRUE(stream->Open()); + FakeAudioInputCallback sink; + stream->Start(&sink); + ASSERT_FALSE(sink.error()); + + sink.WaitForData(); + stream.Close(); + + EXPECT_FALSE(sink.received_data().empty()); + EXPECT_TRUE(sink.closed()); + EXPECT_FALSE(sink.error()); } // This test is intended for manual tests and should only be enabled @@ -389,7 +498,7 @@ TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { const char* file_name = "out_stereo_10sec.pcm"; AudioInputStreamWrapper aisw(audio_manager.get()); - AudioInputStream* ais = aisw.Create(); + ScopedAudioInputStream ais(aisw.Create()); EXPECT_TRUE(ais->Open()); LOG(INFO) << ">> Sample rate: " << aisw.sample_rate() << " [Hz]"; @@ -399,7 +508,7 @@ TEST(WinAudioInputTest, DISABLED_WASAPIAudioInputStreamRecordToFile) { base::PlatformThread::Sleep(TestTimeouts::action_timeout()); ais->Stop(); LOG(INFO) << ">> Recording has stopped."; - ais->Close(); + ais.Close(); } } // namespace media diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc index e3a95883b2..0352e6677d 100644 --- a/media/audio/win/audio_manager_win.cc +++ b/media/audio/win/audio_manager_win.cc @@ -299,6 +299,11 @@ AudioParameters AudioManagerWin::GetInputStreamParameters( std::string AudioManagerWin::GetAssociatedOutputDeviceID( const std::string& input_device_id) { + if (!CoreAudioUtil::IsSupported()) { + NOTIMPLEMENTED() + << "GetAssociatedOutputDeviceID is not supported on this OS"; + return std::string(); + } return CoreAudioUtil::GetMatchingOutputDeviceID(input_device_id); } diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h index b3e8de9286..86e22badc5 100644 --- a/media/audio/win/audio_manager_win.h +++ b/media/audio/win/audio_manager_win.h @@ -25,10 +25,10 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase { virtual bool HasAudioInputDevices() OVERRIDE; virtual string16 GetAudioInputDeviceModel() OVERRIDE; virtual void ShowAudioInputSettings() OVERRIDE; - virtual void GetAudioInputDeviceNames(AudioDeviceNames* device_names) - OVERRIDE; - virtual void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) - OVERRIDE; + virtual void GetAudioInputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; + virtual void GetAudioOutputDeviceNames( + AudioDeviceNames* device_names) OVERRIDE; virtual AudioParameters GetInputStreamParameters( const std::string& device_id) OVERRIDE; virtual std::string GetAssociatedOutputDeviceID( @@ -62,9 +62,7 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase { }; // Allow unit test to modify the utilized enumeration API. - // TODO(joi): Collapse these tests into one. friend class AudioManagerTest; - friend class AudioInputDeviceTest; EnumerationType enumeration_type_; EnumerationType enumeration_type() { return enumeration_type_; } diff --git a/media/audio/win/device_enumeration_win.cc b/media/audio/win/device_enumeration_win.cc index 50d0b7aa0e..aa66afb12b 100644 --- a/media/audio/win/device_enumeration_win.cc +++ b/media/audio/win/device_enumeration_win.cc @@ -25,10 +25,8 @@ using base::win::ScopedCoMem; namespace media { -namespace { - -bool GetDeviceNamesWinImpl(EDataFlow data_flow, - AudioDeviceNames* device_names) { +static bool GetDeviceNamesWinImpl(EDataFlow data_flow, + AudioDeviceNames* device_names) { // It is assumed that this method is called from a COM thread, i.e., // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. ScopedComPtr<IMMDeviceEnumerator> enumerator; @@ -103,7 +101,7 @@ bool GetDeviceNamesWinImpl(EDataFlow data_flow, template <UINT (__stdcall *NumDevsFunc)(), typename CAPSSTRUCT, MMRESULT (__stdcall *DevCapsFunc)(UINT_PTR, CAPSSTRUCT*, UINT)> -bool GetDeviceNamesWinXPImpl(AudioDeviceNames* device_names) { +static bool GetDeviceNamesWinXPImpl(AudioDeviceNames* device_names) { // Retrieve the number of active waveform input devices. UINT number_of_active_devices = NumDevsFunc(); if (number_of_active_devices == 0) @@ -138,8 +136,6 @@ bool GetDeviceNamesWinXPImpl(AudioDeviceNames* device_names) { return true; } -} // namespace - bool GetInputDeviceNamesWin(AudioDeviceNames* device_names) { return GetDeviceNamesWinImpl(eCapture, device_names); } diff --git a/media/audio/win/wavein_input_win.h b/media/audio/win/wavein_input_win.h index 4b830e3480..df5ce4d129 100644 --- a/media/audio/win/wavein_input_win.h +++ b/media/audio/win/wavein_input_win.h @@ -56,7 +56,7 @@ class PCMWaveInAudioInputStream : public AudioInputStream { }; // Allow unit tests to query the device ID. - friend class AudioInputDeviceTest; + friend class AudioManagerTest; // Windows calls us back with the recorded audio data here. See msdn // documentation for 'waveInProc' for details about the parameters. |