| // 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 <stddef.h> |
| #include <string.h> |
| |
| #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<size_t>(!(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<const PPB_Audio_1_1*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_INTERFACE_1_1)); |
| audio_interface_1_0_ = static_cast<const PPB_Audio_1_0*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_INTERFACE_1_0)); |
| audio_config_interface_ = static_cast<const PPB_AudioConfig*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_AUDIO_CONFIG_INTERFACE)); |
| core_interface_ = static_cast<const PPB_Core*>( |
| 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<volatile unsigned*>(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<TestAudio*>(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); |
| } |