// 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 #include "ppapi/c/ppb_audio.h" #include "ppapi/c/ppb_audio_config.h" #include "ppapi/cpp/module.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" #if defined(__native_client__) #include "native_client/src/untrusted/irt/irt.h" #include "ppapi/native_client/src/untrusted/irt_stub/thread_creator.h" #endif #define ARRAYSIZE_UNSAFE(a) \ ((sizeof(a) / sizeof(*(a))) / \ static_cast(!(sizeof(a) % sizeof(*(a))))) #if defined(__native_client__) namespace { void GetNaClIrtPpapiHook(struct nacl_irt_ppapihook* hooks) { nacl_interface_query(NACL_IRT_PPAPIHOOK_v0_1, hooks, sizeof(*hooks)); } struct PP_ThreadFunctions g_thread_funcs = {}; void ThreadFunctionsGetter(const struct PP_ThreadFunctions* thread_funcs) { g_thread_funcs = *thread_funcs; } // In order to check if the thread_create is called, CountingThreadCreate() // increments this variable. Callers can check if the function is actually // called by looking at this value. int g_num_thread_create_called = 0; int g_num_thread_join_called = 0; int CountingThreadCreate(uintptr_t* tid, void (*func)(void* thread_argument), void* thread_argument) { ++g_num_thread_create_called; return g_thread_funcs.thread_create(tid, func, thread_argument); } int CountingThreadJoin(uintptr_t tid) { ++g_num_thread_join_called; return g_thread_funcs.thread_join(tid); } // Sets NULL for PP_ThreadFunctions to emulate the situation that // ppapi_register_thread_creator() is not yet called. void SetNullThreadFunctions() { nacl_irt_ppapihook hooks; GetNaClIrtPpapiHook(&hooks); PP_ThreadFunctions thread_functions = {}; hooks.ppapi_register_thread_creator(&thread_functions); } void InjectCountingThreadFunctions() { // First of all, we extract the system default thread functions. // Internally, __nacl_register_thread_creator calls // hooks.ppapi_register_thread_creator with default PP_ThreadFunctions // instance. ThreadFunctionGetter stores it to g_thread_funcs. nacl_irt_ppapihook hooks = { NULL, ThreadFunctionsGetter }; __nacl_register_thread_creator(&hooks); // Here g_thread_funcs stores the thread functions. // Inject the CountingThreadCreate. PP_ThreadFunctions thread_functions = { CountingThreadCreate, CountingThreadJoin, }; GetNaClIrtPpapiHook(&hooks); hooks.ppapi_register_thread_creator(&thread_functions); } // Resets the PP_ThreadFunctions on exit from the scope. class ScopedThreadFunctionsResetter { public: ScopedThreadFunctionsResetter() {} ~ScopedThreadFunctionsResetter() { nacl_irt_ppapihook hooks; GetNaClIrtPpapiHook(&hooks); __nacl_register_thread_creator(&hooks); } }; } // namespace #endif // __native_client__ 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); #if defined(__native_client__) RUN_TEST(AudioThreadCreatorIsRequired, filter); RUN_TEST(AudioThreadCreatorIsCalled, filter); #endif } // 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(); } #if defined(__native_client__) // Tests the behavior of the thread_create functions. // For PPB_Audio_Shared to work properly, the user code must call // ppapi_register_thread_creator(). This test checks the error handling for the // case when user code doesn't call ppapi_register_thread_creator(). std::string TestAudio::TestAudioThreadCreatorIsRequired() { // We'll inject some thread functions in this test case. // Reset them at the end of this case. ScopedThreadFunctionsResetter thread_resetter; // Set the thread functions to NULLs to emulate the situation where // ppapi_register_thread_creator() is not called by user code. SetNullThreadFunctions(); 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; // StartPlayback() fails, because no thread creating function // is available. ASSERT_FALSE(audio_interface_->StartPlayback(audio)); // If any more audio callbacks are generated, // we should crash (which is good). audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); PASS(); } // Tests whether the thread functions passed from the user code are actually // called. std::string TestAudio::TestAudioThreadCreatorIsCalled() { // We'll inject some thread functions in this test case. // Reset them at the end of this case. ScopedThreadFunctionsResetter thread_resetter; // Inject the thread counting function. In the injected function, // when called, g_num_thread_create_called is incremented. g_num_thread_create_called = 0; g_num_thread_join_called = 0; InjectCountingThreadFunctions(); 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(); // Here, the injected thread_create is called, but thread_join is not yet. ASSERT_EQ(1, g_num_thread_create_called); ASSERT_EQ(0, g_num_thread_join_called); ASSERT_TRUE(audio_interface_->StopPlayback(audio)); test_done_ = true; // Here, the injected thread_join is called. ASSERT_EQ(1, g_num_thread_join_called); // If any more audio callbacks are generated, // we should crash (which is good). audio_callback_method_ = NULL; core_interface_->ReleaseResource(audio); PASS(); } #endif // 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); }