| // 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 <windows.h> |
| #include <mmsystem.h> |
| |
| #include "base/basictypes.h" |
| #include "base/base_paths.h" |
| #include "base/file_util.h" |
| #include "base/memory/aligned_memory.h" |
| #include "base/path_service.h" |
| #include "base/sync_socket.h" |
| #include "base/win/scoped_com_initializer.h" |
| #include "base/win/windows_version.h" |
| #include "media/base/limits.h" |
| #include "media/audio/audio_io.h" |
| #include "media/audio/audio_util.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/simple_sources.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::DoAll; |
| using ::testing::Field; |
| using ::testing::Invoke; |
| using ::testing::InSequence; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| |
| using base::win::ScopedCOMInitializer; |
| |
| namespace media { |
| |
| static const wchar_t kAudioFile1_16b_m_16K[] |
| = L"media\\test\\data\\sweep02_16b_mono_16KHz.raw"; |
| |
| // This class allows to find out if the callbacks are occurring as |
| // expected and if any error has been reported. |
| class TestSourceBasic : public AudioOutputStream::AudioSourceCallback { |
| public: |
| explicit TestSourceBasic() |
| : callback_count_(0), |
| had_error_(0) { |
| } |
| // AudioSourceCallback::OnMoreData implementation: |
| virtual int OnMoreData(AudioBus* audio_bus, |
| AudioBuffersState buffers_state) { |
| ++callback_count_; |
| // Touch the channel memory value to make sure memory is good. |
| audio_bus->Zero(); |
| return audio_bus->frames(); |
| } |
| virtual int OnMoreIOData(AudioBus* source, |
| AudioBus* dest, |
| AudioBuffersState buffers_state) { |
| NOTREACHED(); |
| return 0; |
| } |
| // AudioSourceCallback::OnError implementation: |
| virtual void OnError(AudioOutputStream* stream, int code) { |
| ++had_error_; |
| } |
| // Returns how many times OnMoreData() has been called. |
| int callback_count() const { |
| return callback_count_; |
| } |
| // Returns how many times the OnError callback was called. |
| int had_error() const { |
| return had_error_; |
| } |
| |
| void set_error(bool error) { |
| had_error_ += error ? 1 : 0; |
| } |
| |
| private: |
| int callback_count_; |
| int had_error_; |
| }; |
| |
| const int kMaxNumBuffers = 3; |
| // Specializes TestSourceBasic to simulate a source that blocks for some time |
| // in the OnMoreData callback. |
| class TestSourceLaggy : public TestSourceBasic { |
| public: |
| TestSourceLaggy(int laggy_after_buffer, int lag_in_ms) |
| : laggy_after_buffer_(laggy_after_buffer), lag_in_ms_(lag_in_ms) { |
| } |
| virtual int OnMoreData(AudioBus* audio_bus, |
| AudioBuffersState buffers_state) { |
| // Call the base, which increments the callback_count_. |
| TestSourceBasic::OnMoreData(audio_bus, buffers_state); |
| if (callback_count() > kMaxNumBuffers) { |
| ::Sleep(lag_in_ms_); |
| } |
| return audio_bus->frames(); |
| } |
| private: |
| int laggy_after_buffer_; |
| int lag_in_ms_; |
| }; |
| |
| class MockAudioSource : public AudioOutputStream::AudioSourceCallback { |
| public: |
| MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus, |
| AudioBuffersState buffers_state)); |
| MOCK_METHOD3(OnMoreIOData, int(AudioBus* source, |
| AudioBus* dest, |
| AudioBuffersState buffers_state)); |
| MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); |
| |
| static int ClearData(AudioBus* audio_bus, AudioBuffersState buffers_state) { |
| audio_bus->Zero(); |
| return audio_bus->frames(); |
| } |
| }; |
| |
| // Helper class to memory map an entire file. The mapping is read-only. Don't |
| // use for gigabyte-sized files. Attempts to write to this memory generate |
| // memory access violations. |
| class ReadOnlyMappedFile { |
| public: |
| explicit ReadOnlyMappedFile(const wchar_t* file_name) |
| : fmap_(NULL), start_(NULL), size_(0) { |
| HANDLE file = ::CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| if (INVALID_HANDLE_VALUE == file) |
| return; |
| fmap_ = ::CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL); |
| ::CloseHandle(file); |
| if (!fmap_) |
| return; |
| start_ = reinterpret_cast<char*>(::MapViewOfFile(fmap_, FILE_MAP_READ, |
| 0, 0, 0)); |
| if (!start_) |
| return; |
| MEMORY_BASIC_INFORMATION mbi = {0}; |
| ::VirtualQuery(start_, &mbi, sizeof(mbi)); |
| size_ = mbi.RegionSize; |
| } |
| ~ReadOnlyMappedFile() { |
| if (start_) { |
| ::UnmapViewOfFile(start_); |
| ::CloseHandle(fmap_); |
| } |
| } |
| // Returns true if the file was successfully mapped. |
| bool is_valid() const { |
| return ((start_ > 0) && (size_ > 0)); |
| } |
| // Returns the size in bytes of the mapped memory. |
| uint32 size() const { |
| return size_; |
| } |
| // Returns the memory backing the file. |
| const void* GetChunkAt(uint32 offset) { |
| return &start_[offset]; |
| } |
| |
| private: |
| HANDLE fmap_; |
| char* start_; |
| uint32 size_; |
| }; |
| |
| // =========================================================================== |
| // Validation of AudioManager::AUDIO_PCM_LINEAR |
| // |
| // NOTE: |
| // The tests can fail on the build bots when somebody connects to them via |
| // remote-desktop and the rdp client installs an audio device that fails to open |
| // at some point, possibly when the connection goes idle. |
| |
| // Test that can it be created and closed. |
| TEST(WinAudioTest, PCMWaveStreamGetAndClose) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, |
| 8000, 16, 256)); |
| ASSERT_TRUE(NULL != oas); |
| oas->Close(); |
| } |
| |
| // Test that can it be cannot be created with invalid parameters. |
| TEST(WinAudioTest, SanityOnMakeParams) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| AudioParameters::Format fmt = AudioParameters::AUDIO_PCM_LINEAR; |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_UNSUPPORTED, 8000, 16, 256))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 1024 * 1024, 16, 256))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, 8000, 80, 256))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_UNSUPPORTED, 8000, 16, 256))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_STEREO, -8000, 16, 256))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16, -100))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16, 0))); |
| EXPECT_TRUE(NULL == audio_man->MakeAudioOutputStream( |
| AudioParameters(fmt, CHANNEL_LAYOUT_MONO, 8000, 16, |
| media::limits::kMaxSamplesPerPacket + 1))); |
| } |
| |
| // Test that it can be opened and closed. |
| TEST(WinAudioTest, PCMWaveStreamOpenAndClose) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, |
| 8000, 16, 256)); |
| ASSERT_TRUE(NULL != oas); |
| EXPECT_TRUE(oas->Open()); |
| oas->Close(); |
| } |
| |
| // Test that it has a maximum packet size. |
| TEST(WinAudioTest, PCMWaveStreamOpenLimit) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, |
| 8000, 16, 1024 * 1024 * 1024)); |
| EXPECT_TRUE(NULL == oas); |
| if (oas) |
| oas->Close(); |
| } |
| |
| // Test potential deadlock situation if the source is slow or blocks for some |
| // time. The actual EXPECT_GT are mostly meaningless and the real test is that |
| // the test completes in reasonable time. |
| TEST(WinAudioTest, PCMWaveSlowSource) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| 16000, 16, 256)); |
| ASSERT_TRUE(NULL != oas); |
| TestSourceLaggy test_laggy(2, 90); |
| EXPECT_TRUE(oas->Open()); |
| // The test parameters cause a callback every 32 ms and the source is |
| // sleeping for 90 ms, so it is guaranteed that we run out of ready buffers. |
| oas->Start(&test_laggy); |
| ::Sleep(500); |
| EXPECT_GT(test_laggy.callback_count(), 2); |
| EXPECT_FALSE(test_laggy.had_error()); |
| oas->Stop(); |
| ::Sleep(500); |
| oas->Close(); |
| } |
| |
| // Test another potential deadlock situation if the thread that calls Start() |
| // gets paused. This test is best when run over RDP with audio enabled. See |
| // bug 19276 for more details. |
| TEST(WinAudioTest, PCMWaveStreamPlaySlowLoop) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| AudioParameters::kAudioCDSampleRate, 16, samples_100_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate); |
| |
| EXPECT_TRUE(oas->Open()); |
| oas->SetVolume(1.0); |
| |
| for (int ix = 0; ix != 5; ++ix) { |
| oas->Start(&source); |
| ::Sleep(10); |
| oas->Stop(); |
| } |
| oas->Close(); |
| } |
| |
| |
| // This test produces actual audio for .5 seconds on the default wave |
| // device at 44.1K s/sec. Parameters have been chosen carefully so you should |
| // not hear pops or noises while the sound is playing. |
| TEST(WinAudioTest, PCMWaveStreamPlay200HzTone44Kss) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| AudioParameters::kAudioCDSampleRate, 16, samples_100_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate); |
| |
| EXPECT_TRUE(oas->Open()); |
| oas->SetVolume(1.0); |
| oas->Start(&source); |
| ::Sleep(500); |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| // This test produces actual audio for for .5 seconds on the default wave |
| // device at 22K s/sec. Parameters have been chosen carefully so you should |
| // not hear pops or noises while the sound is playing. The audio also should |
| // sound with a lower volume than PCMWaveStreamPlay200HzTone44Kss. |
| TEST(WinAudioTest, PCMWaveStreamPlay200HzTone22Kss) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 20; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| AudioParameters::kAudioCDSampleRate / 2, 16, |
| samples_100_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate/2); |
| |
| EXPECT_TRUE(oas->Open()); |
| |
| oas->SetVolume(0.5); |
| oas->Start(&source); |
| ::Sleep(500); |
| |
| // Test that the volume is within the set limits. |
| double volume = 0.0; |
| oas->GetVolume(&volume); |
| EXPECT_LT(volume, 0.51); |
| EXPECT_GT(volume, 0.49); |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| // Uses a restricted source to play ~2 seconds of audio for about 5 seconds. We |
| // try hard to generate situation where the two threads are accessing the |
| // object roughly at the same time. |
| TEST(WinAudioTest, PushSourceFile16KHz) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| static const int kSampleRate = 16000; |
| SineWaveAudioSource source(1, 200.0, kSampleRate); |
| // Compute buffer size for 100ms of audio. |
| const uint32 kSamples100ms = (kSampleRate / 1000) * 100; |
| // Restrict SineWaveAudioSource to 100ms of samples. |
| source.CapSamples(kSamples100ms); |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| kSampleRate, 16, kSamples100ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| EXPECT_TRUE(oas->Open()); |
| |
| oas->SetVolume(1.0); |
| oas->Start(&source); |
| |
| // We buffer and play at the same time, buffering happens every ~10ms and the |
| // consuming of the buffer happens every ~100ms. We do 100 buffers which |
| // effectively wrap around the file more than once. |
| for (uint32 ix = 0; ix != 100; ++ix) { |
| ::Sleep(10); |
| source.Reset(); |
| } |
| |
| // Play a little bit more of the file. |
| ::Sleep(500); |
| |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| // This test is to make sure an AudioOutputStream can be started after it was |
| // stopped. You will here two .5 seconds wave signal separated by 0.5 seconds |
| // of silence. |
| TEST(WinAudioTest, PCMWaveStreamPlayTwice200HzTone44Kss) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| AudioParameters::kAudioCDSampleRate, 16, samples_100_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate); |
| EXPECT_TRUE(oas->Open()); |
| oas->SetVolume(1.0); |
| |
| // Play the wave for .5 seconds. |
| oas->Start(&source); |
| ::Sleep(500); |
| oas->Stop(); |
| |
| // Sleep to give silence after stopping the AudioOutputStream. |
| ::Sleep(250); |
| |
| // Start again and play for .5 seconds. |
| oas->Start(&source); |
| ::Sleep(500); |
| oas->Stop(); |
| |
| oas->Close(); |
| } |
| |
| // With the low latency mode, WASAPI is utilized by default for Vista and |
| // higher and Wave is used for XP and lower. It is possible to utilize a |
| // smaller buffer size for WASAPI than for Wave. |
| TEST(WinAudioTest, PCMWaveStreamPlay200HzToneLowLatency) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| // The WASAPI API requires a correct COM environment. |
| ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); |
| |
| // Use 10 ms buffer size for WASAPI and 50 ms buffer size for Wave. |
| // Take the existing native sample rate into account. |
| int sample_rate = static_cast<int>(media::GetAudioHardwareSampleRate()); |
| uint32 samples_10_ms = sample_rate / 100; |
| int n = 1; |
| (base::win::GetVersion() <= base::win::VERSION_XP) ? n = 5 : n = 1; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| CHANNEL_LAYOUT_MONO, sample_rate, |
| 16, n * samples_10_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| SineWaveAudioSource source(1, 200, sample_rate); |
| |
| bool opened = oas->Open(); |
| if (!opened) { |
| // It was not possible to open this audio device in mono. |
| // No point in continuing the test so let's break here. |
| LOG(WARNING) << "Mono is not supported. Skipping test."; |
| oas->Close(); |
| return; |
| } |
| oas->SetVolume(1.0); |
| |
| // Play the wave for .8 seconds. |
| oas->Start(&source); |
| ::Sleep(800); |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| // Check that the pending bytes value is correct what the stream starts. |
| TEST(WinAudioTest, PCMWaveStreamPendingBytes) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| uint32 samples_100_ms = AudioParameters::kAudioCDSampleRate / 10; |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, |
| AudioParameters::kAudioCDSampleRate, 16, samples_100_ms)); |
| ASSERT_TRUE(NULL != oas); |
| |
| NiceMock<MockAudioSource> source; |
| EXPECT_TRUE(oas->Open()); |
| |
| uint32 bytes_100_ms = samples_100_ms * 2; |
| |
| // Audio output stream has either a double or triple buffer scheme. |
| // We expect the amount of pending bytes will reaching up to 2 times of |
| // |bytes_100_ms| depending on number of buffers used. |
| // From that it would decrease as we are playing the data but not providing |
| // new one. And then we will try to provide zero data so the amount of |
| // pending bytes will go down and eventually read zero. |
| InSequence s; |
| |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, 0))) |
| .WillOnce(Invoke(MockAudioSource::ClearData)); |
| switch (NumberOfWaveOutBuffers()) { |
| case 2: |
| break; // Calls are the same as at end of 3-buffer scheme. |
| case 3: |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, |
| bytes_100_ms))) |
| .WillOnce(Invoke(MockAudioSource::ClearData)); |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, |
| 2 * bytes_100_ms))) |
| .WillOnce(Invoke(MockAudioSource::ClearData)); |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, |
| 2 * bytes_100_ms))) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(0)); |
| break; |
| default: |
| ASSERT_TRUE(false) |
| << "Unexpected number of buffers: " << NumberOfWaveOutBuffers(); |
| } |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, |
| bytes_100_ms))) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(0)); |
| EXPECT_CALL(source, OnMoreData(NotNull(), |
| Field(&AudioBuffersState::pending_bytes, 0))) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(0)); |
| |
| oas->Start(&source); |
| ::Sleep(500); |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| // Simple source that uses a SyncSocket to retrieve the audio data |
| // from a potentially remote thread. |
| class SyncSocketSource : public AudioOutputStream::AudioSourceCallback { |
| public: |
| SyncSocketSource(base::SyncSocket* socket, const AudioParameters& params) |
| : socket_(socket) { |
| // Setup AudioBus wrapping data we'll receive over the sync socket. |
| data_size_ = AudioBus::CalculateMemorySize(params); |
| data_.reset(static_cast<float*>( |
| base::AlignedAlloc(data_size_, AudioBus::kChannelAlignment))); |
| audio_bus_ = AudioBus::WrapMemory(params, data_.get()); |
| } |
| ~SyncSocketSource() {} |
| |
| // AudioSourceCallback::OnMoreData implementation: |
| virtual int OnMoreData(AudioBus* audio_bus, |
| AudioBuffersState buffers_state) { |
| socket_->Send(&buffers_state, sizeof(buffers_state)); |
| uint32 size = socket_->Receive(data_.get(), data_size_); |
| DCHECK_EQ(static_cast<size_t>(size) % sizeof(*audio_bus_->channel(0)), 0U); |
| audio_bus_->CopyTo(audio_bus); |
| return audio_bus_->frames(); |
| } |
| virtual int OnMoreIOData(AudioBus* source, |
| AudioBus* dest, |
| AudioBuffersState buffers_state) { |
| NOTREACHED(); |
| return 0; |
| } |
| // AudioSourceCallback::OnError implementation: |
| virtual void OnError(AudioOutputStream* stream, int code) { |
| } |
| |
| private: |
| base::SyncSocket* socket_; |
| int data_size_; |
| scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data_; |
| scoped_ptr<AudioBus> audio_bus_; |
| }; |
| |
| struct SyncThreadContext { |
| base::SyncSocket* socket; |
| int sample_rate; |
| int channels; |
| int frames; |
| double sine_freq; |
| uint32 packet_size_bytes; |
| }; |
| |
| // This thread provides the data that the SyncSocketSource above needs |
| // using the other end of a SyncSocket. The protocol is as follows: |
| // |
| // SyncSocketSource ---send 4 bytes ------------> SyncSocketThread |
| // <--- audio packet ---------- |
| // |
| DWORD __stdcall SyncSocketThread(void* context) { |
| SyncThreadContext& ctx = *(reinterpret_cast<SyncThreadContext*>(context)); |
| |
| // Setup AudioBus wrapping data we'll pass over the sync socket. |
| scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> data(static_cast<float*>( |
| base::AlignedAlloc(ctx.packet_size_bytes, AudioBus::kChannelAlignment))); |
| scoped_ptr<AudioBus> audio_bus = AudioBus::WrapMemory( |
| ctx.channels, ctx.frames, data.get()); |
| |
| SineWaveAudioSource sine(1, ctx.sine_freq, ctx.sample_rate); |
| const int kTwoSecFrames = ctx.sample_rate * 2; |
| |
| AudioBuffersState buffers_state; |
| int times = 0; |
| for (int ix = 0; ix < kTwoSecFrames; ix += ctx.frames) { |
| if (ctx.socket->Receive(&buffers_state, sizeof(buffers_state)) == 0) |
| break; |
| if ((times > 0) && (buffers_state.pending_bytes < 1000)) __debugbreak(); |
| sine.OnMoreData(audio_bus.get(), buffers_state); |
| ctx.socket->Send(data.get(), ctx.packet_size_bytes); |
| ++times; |
| } |
| |
| return 0; |
| } |
| |
| // Test the basic operation of AudioOutputStream used with a SyncSocket. |
| // The emphasis is to verify that it is possible to feed data to the audio |
| // layer using a source based on SyncSocket. In a real situation we would |
| // go for the low-latency version in combination with SyncSocket, but to keep |
| // the test more simple, AUDIO_PCM_LINEAR is utilized instead. The main |
| // principle of the test still remains and we avoid the additional complexity |
| // related to the two different audio-layers for AUDIO_PCM_LOW_LATENCY. |
| // In this test you should hear a continuous 200Hz tone for 2 seconds. |
| TEST(WinAudioTest, SyncSocketBasic) { |
| scoped_ptr<AudioManager> audio_man(AudioManager::Create()); |
| if (!audio_man->HasAudioOutputDevices()) { |
| LOG(WARNING) << "No output device detected."; |
| return; |
| } |
| |
| static const int sample_rate = AudioParameters::kAudioCDSampleRate; |
| static const uint32 kSamples20ms = sample_rate / 50; |
| AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, |
| CHANNEL_LAYOUT_MONO, sample_rate, 16, kSamples20ms); |
| |
| |
| AudioOutputStream* oas = audio_man->MakeAudioOutputStream(params); |
| ASSERT_TRUE(NULL != oas); |
| |
| ASSERT_TRUE(oas->Open()); |
| |
| base::SyncSocket sockets[2]; |
| ASSERT_TRUE(base::SyncSocket::CreatePair(&sockets[0], &sockets[1])); |
| |
| SyncSocketSource source(&sockets[0], params); |
| |
| SyncThreadContext thread_context; |
| thread_context.sample_rate = params.sample_rate(); |
| thread_context.sine_freq = 200.0; |
| thread_context.packet_size_bytes = AudioBus::CalculateMemorySize(params); |
| thread_context.frames = params.frames_per_buffer(); |
| thread_context.channels = params.channels(); |
| thread_context.socket = &sockets[1]; |
| |
| HANDLE thread = ::CreateThread(NULL, 0, SyncSocketThread, |
| &thread_context, 0, NULL); |
| |
| oas->Start(&source); |
| |
| ::WaitForSingleObject(thread, INFINITE); |
| ::CloseHandle(thread); |
| |
| oas->Stop(); |
| oas->Close(); |
| } |
| |
| } // namespace media |