// 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 "ppapi/tests/test_audio.h" #include #include "ppapi/c/ppb_audio_config.h" #include "ppapi/c/ppb_audio.h" #include "ppapi/cpp/module.h" #include "ppapi/tests/testing_instance.h" #include "ppapi/tests/test_utils.h" #define ARRAYSIZE_UNSAFE(a) \ ((sizeof(a) / sizeof(*(a))) / \ static_cast(!(sizeof(a) % sizeof(*(a))))) REGISTER_TEST_CASE(Audio); TestAudio::TestAudio(TestingInstance* instance) : TestCase(instance), audio_callback_method_(NULL), audio_callback_event_(instance->pp_instance()), test_done_(false), audio_interface_(NULL), audio_interface_1_0_(NULL), audio_config_interface_(NULL), core_interface_(NULL) { } TestAudio::~TestAudio() { } bool TestAudio::Init() { audio_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_INTERFACE_1_1)); audio_interface_1_0_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_INTERFACE_1_0)); audio_config_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_CONFIG_INTERFACE)); core_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE)); return audio_interface_ && audio_interface_1_0_ && audio_config_interface_ && core_interface_; } void TestAudio::RunTests(const std::string& filter) { RUN_TEST(Creation, filter); RUN_TEST(DestroyNoStop, filter); RUN_TEST(Failures, filter); RUN_TEST(AudioCallback1, filter); RUN_TEST(AudioCallback2, filter); RUN_TEST(AudioCallback3, filter); RUN_TEST(AudioCallback4, filter); } // Test creating audio resources for all guaranteed sample rates and various // frame counts. std::string TestAudio::TestCreation() { static const PP_AudioSampleRate kSampleRates[] = { PP_AUDIOSAMPLERATE_44100, PP_AUDIOSAMPLERATE_48000 }; static const uint32_t kRequestFrameCounts[] = { PP_AUDIOMINSAMPLEFRAMECOUNT, PP_AUDIOMAXSAMPLEFRAMECOUNT, // Include some "okay-looking" frame counts; check their validity below. PP_AUDIOSAMPLERATE_44100 / 100, // 10ms @ 44.1kHz PP_AUDIOSAMPLERATE_48000 / 100, // 10ms @ 48kHz 2 * PP_AUDIOSAMPLERATE_44100 / 100, // 20ms @ 44.1kHz 2 * PP_AUDIOSAMPLERATE_48000 / 100, // 20ms @ 48kHz 1024, 2048, 4096 }; PP_AudioSampleRate sample_rate = audio_config_interface_->RecommendSampleRate( instance_->pp_instance()); ASSERT_TRUE(sample_rate == PP_AUDIOSAMPLERATE_NONE || sample_rate == PP_AUDIOSAMPLERATE_44100 || sample_rate == PP_AUDIOSAMPLERATE_48000); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSampleRates); i++) { PP_AudioSampleRate sample_rate = kSampleRates[i]; for (size_t j = 0; j < ARRAYSIZE_UNSAFE(kRequestFrameCounts); j++) { // Make a config, create the audio resource, and release the config. uint32_t request_frame_count = kRequestFrameCounts[j]; uint32_t frame_count = audio_config_interface_->RecommendSampleFrameCount( instance_->pp_instance(), sample_rate, request_frame_count); PP_Resource ac = audio_config_interface_->CreateStereo16Bit( instance_->pp_instance(), sample_rate, frame_count); ASSERT_TRUE(ac); PP_Resource audio = audio_interface_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline, this); core_interface_->ReleaseResource(ac); ac = 0; ASSERT_TRUE(audio); ASSERT_TRUE(audio_interface_->IsAudio(audio)); // Check that the config returned for |audio| matches what we gave it. ac = audio_interface_->GetCurrentConfig(audio); ASSERT_TRUE(ac); ASSERT_TRUE(audio_config_interface_->IsAudioConfig(ac)); ASSERT_EQ(sample_rate, audio_config_interface_->GetSampleRate(ac)); ASSERT_EQ(frame_count, audio_config_interface_->GetSampleFrameCount(ac)); core_interface_->ReleaseResource(ac); ac = 0; // Start and stop audio playback. The documentation indicates that // |StartPlayback()| and |StopPlayback()| may fail, but gives no // indication as to why ... so check that they succeed. audio_callback_method_ = &TestAudio::AudioCallbackTrivial; ASSERT_TRUE(audio_interface_->StartPlayback(audio)); ASSERT_TRUE(audio_interface_->StopPlayback(audio)); audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); } } PASS(); } // Test that releasing the resource without calling |StopPlayback()| "works". std::string TestAudio::TestDestroyNoStop() { PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 2048); ASSERT_TRUE(ac); audio_callback_method_ = NULL; PP_Resource audio = audio_interface_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline, this); core_interface_->ReleaseResource(ac); ac = 0; ASSERT_TRUE(audio); ASSERT_TRUE(audio_interface_->IsAudio(audio)); // Start playback and release the resource. audio_callback_method_ = &TestAudio::AudioCallbackTrivial; ASSERT_TRUE(audio_interface_->StartPlayback(audio)); core_interface_->ReleaseResource(audio); audio_callback_method_ = NULL; PASS(); } std::string TestAudio::TestFailures() { // Test invalid parameters to |Create()|. // We want a valid config for some of our tests of |Create()|. PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 2048); ASSERT_TRUE(ac); // Failure cases should never lead to the callback being called. audio_callback_method_ = NULL; // Invalid instance -> failure. PP_Resource audio = audio_interface_->Create( 0, ac, AudioCallbackTrampoline, this); ASSERT_EQ(0, audio); // Invalid config -> failure. audio = audio_interface_->Create( instance_->pp_instance(), 0, AudioCallbackTrampoline, this); ASSERT_EQ(0, audio); // Null callback -> failure. audio = audio_interface_->Create( instance_->pp_instance(), ac, NULL, NULL); ASSERT_EQ(0, audio); core_interface_->ReleaseResource(ac); ac = 0; // Test the other functions with an invalid audio resource. ASSERT_FALSE(audio_interface_->IsAudio(0)); ASSERT_EQ(0, audio_interface_->GetCurrentConfig(0)); ASSERT_FALSE(audio_interface_->StartPlayback(0)); ASSERT_FALSE(audio_interface_->StopPlayback(0)); PASS(); } // NOTE: |TestAudioCallbackN| assumes that the audio callback is called at least // once. If the audio stream does not start up correctly or is interrupted this // may not be the case and these tests will fail. However, in order to properly // test the audio callbacks, we must have a configuration where audio can // successfully play, so we assume this is the case on bots. // This test starts playback and verifies that: // 1) the audio callback is actually called; // 2) that |StopPlayback()| waits for the audio callback to finish. std::string TestAudio::TestAudioCallback1() { PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 1024); ASSERT_TRUE(ac); audio_callback_method_ = NULL; PP_Resource audio = audio_interface_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline, this); core_interface_->ReleaseResource(ac); ac = 0; audio_callback_event_.Reset(); test_done_ = false; audio_callback_method_ = &TestAudio::AudioCallbackTest; ASSERT_TRUE(audio_interface_->StartPlayback(audio)); // Wait for the audio callback to be called. audio_callback_event_.Wait(); ASSERT_TRUE(audio_interface_->StopPlayback(audio)); test_done_ = true; // If any more audio callbacks are generated, we should crash (which is good). audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); PASS(); } // This is the same as |TestAudioCallback1()|, except that instead of calling // |StopPlayback()|, it just releases the resource. std::string TestAudio::TestAudioCallback2() { PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 1024); ASSERT_TRUE(ac); audio_callback_method_ = NULL; PP_Resource audio = audio_interface_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline, this); core_interface_->ReleaseResource(ac); ac = 0; audio_callback_event_.Reset(); test_done_ = false; audio_callback_method_ = &TestAudio::AudioCallbackTest; ASSERT_TRUE(audio_interface_->StartPlayback(audio)); // Wait for the audio callback to be called. audio_callback_event_.Wait(); core_interface_->ReleaseResource(audio); test_done_ = true; // If any more audio callbacks are generated, we should crash (which is good). audio_callback_method_ = NULL; PASS(); } // This is the same as |TestAudioCallback1()|, except that it attempts a second // round of |StartPlayback| and |StopPlayback| to make sure the callback // function still responds when using the same audio resource. std::string TestAudio::TestAudioCallback3() { PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 1024); ASSERT_TRUE(ac); audio_callback_method_ = NULL; PP_Resource audio = audio_interface_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline, this); core_interface_->ReleaseResource(ac); ac = 0; audio_callback_event_.Reset(); test_done_ = false; audio_callback_method_ = &TestAudio::AudioCallbackTest; ASSERT_TRUE(audio_interface_->StartPlayback(audio)); // Wait for the audio callback to be called. audio_callback_event_.Wait(); ASSERT_TRUE(audio_interface_->StopPlayback(audio)); // Repeat one more |StartPlayback| & |StopPlayback| cycle, and verify again // that the callback function was invoked. audio_callback_event_.Reset(); ASSERT_TRUE(audio_interface_->StartPlayback(audio)); // Wait for the audio callback to be called. audio_callback_event_.Wait(); ASSERT_TRUE(audio_interface_->StopPlayback(audio)); test_done_ = true; // If any more audio callbacks are generated, we should crash (which is good). audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); PASS(); } // This is the same as |TestAudioCallback1()|, except that it uses // PPB_Audio_1_0. std::string TestAudio::TestAudioCallback4() { PP_Resource ac = CreateAudioConfig(PP_AUDIOSAMPLERATE_44100, 1024); ASSERT_TRUE(ac); audio_callback_method_ = NULL; PP_Resource audio = audio_interface_1_0_->Create( instance_->pp_instance(), ac, AudioCallbackTrampoline1_0, this); core_interface_->ReleaseResource(ac); ac = 0; audio_callback_event_.Reset(); test_done_ = false; audio_callback_method_ = &TestAudio::AudioCallbackTest; ASSERT_TRUE(audio_interface_1_0_->StartPlayback(audio)); // Wait for the audio callback to be called. audio_callback_event_.Wait(); ASSERT_TRUE(audio_interface_1_0_->StopPlayback(audio)); test_done_ = true; // If any more audio callbacks are generated, we should crash (which is good). audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); PASS(); } // TODO(raymes): Test that actually playback happens correctly, etc. static void Crash() { *static_cast(NULL) = 0xdeadbeef; } // static void TestAudio::AudioCallbackTrampoline(void* sample_buffer, uint32_t buffer_size_in_bytes, PP_TimeDelta latency, void* user_data) { TestAudio* thiz = static_cast(user_data); // Crash if on the main thread. if (thiz->core_interface_->IsMainThread()) Crash(); AudioCallbackMethod method = thiz->audio_callback_method_; (thiz->*method)(sample_buffer, buffer_size_in_bytes, latency); } // static void TestAudio::AudioCallbackTrampoline1_0(void* sample_buffer, uint32_t buffer_size_in_bytes, void* user_data) { AudioCallbackTrampoline(sample_buffer, buffer_size_in_bytes, 0.0, user_data); } void TestAudio::AudioCallbackTrivial(void* sample_buffer, uint32_t buffer_size_in_bytes, PP_TimeDelta latency) { if (latency < 0) Crash(); memset(sample_buffer, 0, buffer_size_in_bytes); } void TestAudio::AudioCallbackTest(void* sample_buffer, uint32_t buffer_size_in_bytes, PP_TimeDelta latency) { if (test_done_ || latency < 0) Crash(); memset(sample_buffer, 0, buffer_size_in_bytes); audio_callback_event_.Signal(); } PP_Resource TestAudio::CreateAudioConfig( PP_AudioSampleRate sample_rate, uint32_t requested_sample_frame_count) { uint32_t frame_count = audio_config_interface_->RecommendSampleFrameCount( instance_->pp_instance(), sample_rate, requested_sample_frame_count); return audio_config_interface_->CreateStereo16Bit( instance_->pp_instance(), sample_rate, frame_count); }