1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4// 5// Tests PPB_MediaStreamAudioTrack interface. 6 7#include "ppapi/tests/test_media_stream_audio_track.h" 8 9// For MSVC. 10#define _USE_MATH_DEFINES 11#include <math.h> 12#include <stdint.h> 13 14#include <algorithm> 15 16#include "ppapi/c/private/ppb_testing_private.h" 17#include "ppapi/cpp/audio_buffer.h" 18#include "ppapi/cpp/completion_callback.h" 19#include "ppapi/cpp/instance.h" 20#include "ppapi/cpp/var.h" 21#include "ppapi/tests/test_utils.h" 22#include "ppapi/tests/testing_instance.h" 23 24REGISTER_TEST_CASE(MediaStreamAudioTrack); 25 26namespace { 27 28// Real constants defined in 29// content/renderer/pepper/pepper_media_stream_audio_track_host.cc. 30const int32_t kMaxNumberOfBuffers = 1000; 31const int32_t kMinDuration = 10; 32const int32_t kMaxDuration = 10000; 33const int32_t kTimes = 3; 34const char kJSCode[] = 35 "function gotStream(stream) {" 36 " test_stream = stream;" 37 " var track = stream.getAudioTracks()[0];" 38 " var plugin = document.getElementById('plugin');" 39 " plugin.postMessage(track);" 40 "}" 41 "var constraints = {" 42 " audio: true," 43 " video: false," 44 "};" 45 "navigator.getUserMedia = " 46 " navigator.getUserMedia || navigator.webkitGetUserMedia;" 47 "navigator.getUserMedia(constraints," 48 " gotStream, function() {});"; 49 50const char kSineJSCode[] = 51 // Create oscillators for the left and right channels. Use a sine wave, 52 // which is the easiest to calculate expected values. The oscillator output 53 // is low-pass filtered (as per spec) making comparison hard. 54 "var context = new AudioContext();" 55 "var l_osc = context.createOscillator();" 56 "l_osc.type = \"sine\";" 57 "l_osc.frequency.value = 25;" 58 "var r_osc = context.createOscillator();" 59 "r_osc.type = \"sine\";" 60 "r_osc.frequency.value = 100;" 61 // Combine the left and right channels. 62 "var merger = context.createChannelMerger(2);" 63 "merger.channelInterpretation = \"discrete\";" 64 "l_osc.connect(merger, 0, 0);" 65 "r_osc.connect(merger, 0, 1);" 66 "var dest_stream = context.createMediaStreamDestination();" 67 "merger.connect(dest_stream);" 68 // Dump the generated waveform to a MediaStream output. 69 "l_osc.start();" 70 "r_osc.start();" 71 "var track = dest_stream.stream.getAudioTracks()[0];" 72 "var plugin = document.getElementById('plugin');" 73 "plugin.postMessage(track);"; 74 75// Helper to check if the |sample_rate| is listed in PP_AudioBuffer_SampleRate 76// enum. 77bool IsSampleRateValid(PP_AudioBuffer_SampleRate sample_rate) { 78 switch (sample_rate) { 79 case PP_AUDIOBUFFER_SAMPLERATE_8000: 80 case PP_AUDIOBUFFER_SAMPLERATE_16000: 81 case PP_AUDIOBUFFER_SAMPLERATE_22050: 82 case PP_AUDIOBUFFER_SAMPLERATE_32000: 83 case PP_AUDIOBUFFER_SAMPLERATE_44100: 84 case PP_AUDIOBUFFER_SAMPLERATE_48000: 85 case PP_AUDIOBUFFER_SAMPLERATE_96000: 86 case PP_AUDIOBUFFER_SAMPLERATE_192000: 87 return true; 88 default: 89 return false; 90 } 91} 92 93} // namespace 94 95TestMediaStreamAudioTrack::TestMediaStreamAudioTrack(TestingInstance* instance) 96 : TestCase(instance), 97 event_(instance_->pp_instance()) { 98} 99 100bool TestMediaStreamAudioTrack::Init() { 101 return true; 102} 103 104TestMediaStreamAudioTrack::~TestMediaStreamAudioTrack() { 105} 106 107void TestMediaStreamAudioTrack::RunTests(const std::string& filter) { 108 RUN_TEST(Create, filter); 109 RUN_TEST(GetBuffer, filter); 110 RUN_TEST(Configure, filter); 111 RUN_TEST(ConfigureClose, filter); 112 RUN_TEST(VerifyWaveform, filter); 113} 114 115void TestMediaStreamAudioTrack::HandleMessage(const pp::Var& message) { 116 if (message.is_resource()) { 117 audio_track_ = pp::MediaStreamAudioTrack(message.AsResource()); 118 } 119 event_.Signal(); 120} 121 122std::string TestMediaStreamAudioTrack::TestCreate() { 123 // Create a track. 124 instance_->EvalScript(kJSCode); 125 event_.Wait(); 126 event_.Reset(); 127 128 ASSERT_FALSE(audio_track_.is_null()); 129 ASSERT_FALSE(audio_track_.HasEnded()); 130 ASSERT_FALSE(audio_track_.GetId().empty()); 131 132 // Close the track. 133 audio_track_.Close(); 134 ASSERT_TRUE(audio_track_.HasEnded()); 135 audio_track_ = pp::MediaStreamAudioTrack(); 136 PASS(); 137} 138 139std::string TestMediaStreamAudioTrack::TestGetBuffer() { 140 // Create a track. 141 instance_->EvalScript(kJSCode); 142 event_.Wait(); 143 event_.Reset(); 144 145 ASSERT_FALSE(audio_track_.is_null()); 146 ASSERT_FALSE(audio_track_.HasEnded()); 147 ASSERT_FALSE(audio_track_.GetId().empty()); 148 149 PP_TimeDelta timestamp = 0.0; 150 151 // Get |kTimes| buffers. 152 for (int i = 0; i < kTimes; ++i) { 153 TestCompletionCallbackWithOutput<pp::AudioBuffer> cc( 154 instance_->pp_instance(), false); 155 cc.WaitForResult(audio_track_.GetBuffer(cc.GetCallback())); 156 ASSERT_EQ(PP_OK, cc.result()); 157 pp::AudioBuffer buffer = cc.output(); 158 ASSERT_FALSE(buffer.is_null()); 159 ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate())); 160 ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS); 161 162 ASSERT_GE(buffer.GetTimestamp(), timestamp); 163 timestamp = buffer.GetTimestamp(); 164 165 ASSERT_GT(buffer.GetDataBufferSize(), 0U); 166 ASSERT_TRUE(buffer.GetDataBuffer() != NULL); 167 168 audio_track_.RecycleBuffer(buffer); 169 170 // A recycled buffer should be invalidated. 171 ASSERT_EQ(buffer.GetSampleRate(), PP_AUDIOBUFFER_SAMPLERATE_UNKNOWN); 172 ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_UNKNOWN); 173 ASSERT_EQ(buffer.GetDataBufferSize(), 0U); 174 ASSERT_TRUE(buffer.GetDataBuffer() == NULL); 175 } 176 177 // Close the track. 178 audio_track_.Close(); 179 ASSERT_TRUE(audio_track_.HasEnded()); 180 audio_track_ = pp::MediaStreamAudioTrack(); 181 PASS(); 182} 183 184std::string TestMediaStreamAudioTrack::CheckConfigure( 185 int32_t attrib_list[], int32_t expected_result) { 186 TestCompletionCallback cc_configure(instance_->pp_instance(), false); 187 cc_configure.WaitForResult( 188 audio_track_.Configure(attrib_list, cc_configure.GetCallback())); 189 ASSERT_EQ(expected_result, cc_configure.result()); 190 PASS(); 191} 192 193std::string TestMediaStreamAudioTrack::CheckGetBuffer( 194 int times, int expected_duration) { 195 PP_TimeDelta timestamp = 0.0; 196 for (int j = 0; j < times; ++j) { 197 TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer( 198 instance_->pp_instance(), false); 199 cc_get_buffer.WaitForResult( 200 audio_track_.GetBuffer(cc_get_buffer.GetCallback())); 201 ASSERT_EQ(PP_OK, cc_get_buffer.result()); 202 pp::AudioBuffer buffer = cc_get_buffer.output(); 203 ASSERT_FALSE(buffer.is_null()); 204 ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate())); 205 ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS); 206 207 ASSERT_GE(buffer.GetTimestamp(), timestamp); 208 timestamp = buffer.GetTimestamp(); 209 210 ASSERT_TRUE(buffer.GetDataBuffer() != NULL); 211 if (expected_duration > 0) { 212 uint32_t buffer_size = buffer.GetDataBufferSize(); 213 uint32_t channels = buffer.GetNumberOfChannels(); 214 uint32_t sample_rate = buffer.GetSampleRate(); 215 uint32_t bytes_per_frame = channels * 2; 216 int32_t duration = expected_duration; 217 ASSERT_EQ(buffer_size % bytes_per_frame, 0U); 218 ASSERT_EQ(buffer_size, 219 (duration * sample_rate * bytes_per_frame) / 1000); 220 } else { 221 ASSERT_GT(buffer.GetDataBufferSize(), 0U); 222 } 223 224 audio_track_.RecycleBuffer(buffer); 225 } 226 PASS(); 227} 228 229std::string TestMediaStreamAudioTrack::TestConfigure() { 230 // Create a track. 231 instance_->EvalScript(kJSCode); 232 event_.Wait(); 233 event_.Reset(); 234 235 ASSERT_FALSE(audio_track_.is_null()); 236 ASSERT_FALSE(audio_track_.HasEnded()); 237 ASSERT_FALSE(audio_track_.GetId().empty()); 238 239 // Perform a |Configure()| with no attributes. This ends up making an IPC 240 // call, but the host implementation has a fast-path when there are no changes 241 // to the configuration. This test is intended to hit that fast-path and make 242 // sure it works correctly. 243 { 244 int32_t attrib_list[] = { 245 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 246 }; 247 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK)); 248 } 249 250 // Configure number of buffers. 251 struct { 252 int32_t buffers; 253 int32_t expect_result; 254 } buffers[] = { 255 { 8, PP_OK }, 256 { 100, PP_OK }, 257 { kMaxNumberOfBuffers, PP_OK }, 258 { -1, PP_ERROR_BADARGUMENT }, 259 { kMaxNumberOfBuffers + 1, PP_OK }, // Clipped to max value. 260 { 0, PP_OK }, // Use default. 261 }; 262 for (size_t i = 0; i < sizeof(buffers) / sizeof(buffers[0]); ++i) { 263 int32_t attrib_list[] = { 264 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, buffers[i].buffers, 265 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 266 }; 267 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, 268 buffers[i].expect_result)); 269 // Get some buffers. This should also succeed when configure fails. 270 ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, -1)); 271 } 272 273 // Configure buffer duration. 274 struct { 275 int32_t duration; 276 int32_t expect_result; 277 } durations[] = { 278 { kMinDuration, PP_OK }, 279 { 123, PP_OK }, 280 { kMinDuration - 1, PP_ERROR_BADARGUMENT }, 281 { kMaxDuration + 1, PP_ERROR_BADARGUMENT }, 282 }; 283 for (size_t i = 0; i < sizeof(durations) / sizeof(durations[0]); ++i) { 284 int32_t attrib_list[] = { 285 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, durations[i].duration, 286 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 287 }; 288 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, 289 durations[i].expect_result)); 290 291 // Get some buffers. This always works, but the buffer size will vary. 292 int duration = 293 durations[i].expect_result == PP_OK ? durations[i].duration : -1; 294 ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, duration)); 295 } 296 // Test kMaxDuration separately since each GetBuffer will take 10 seconds. 297 { 298 int32_t attrib_list[] = { 299 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMaxDuration, 300 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 301 }; 302 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK)); 303 } 304 305 // Reset the duration to prevent the next part from taking 10 seconds. 306 { 307 int32_t attrib_list[] = { 308 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMinDuration, 309 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 310 }; 311 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK)); 312 } 313 314 // Configure should fail while plugin holds buffers. 315 { 316 TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer( 317 instance_->pp_instance(), false); 318 cc_get_buffer.WaitForResult( 319 audio_track_.GetBuffer(cc_get_buffer.GetCallback())); 320 ASSERT_EQ(PP_OK, cc_get_buffer.result()); 321 pp::AudioBuffer buffer = cc_get_buffer.output(); 322 int32_t attrib_list[] = { 323 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, 0, 324 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 325 }; 326 TestCompletionCallback cc_configure(instance_->pp_instance(), false); 327 cc_configure.WaitForResult( 328 audio_track_.Configure(attrib_list, cc_configure.GetCallback())); 329 ASSERT_EQ(PP_ERROR_INPROGRESS, cc_configure.result()); 330 audio_track_.RecycleBuffer(buffer); 331 } 332 333 // Close the track. 334 audio_track_.Close(); 335 ASSERT_TRUE(audio_track_.HasEnded()); 336 audio_track_ = pp::MediaStreamAudioTrack(); 337 PASS(); 338} 339 340std::string TestMediaStreamAudioTrack::TestConfigureClose() { 341 // Create a track. 342 instance_->EvalScript(kJSCode); 343 event_.Wait(); 344 event_.Reset(); 345 346 ASSERT_FALSE(audio_track_.is_null()); 347 ASSERT_FALSE(audio_track_.HasEnded()); 348 ASSERT_FALSE(audio_track_.GetId().empty()); 349 350 // Configure the audio track and close it immediately. The Configure() call 351 // should complete. 352 int32_t attrib_list[] = { 353 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, 10, 354 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 355 }; 356 TestCompletionCallback cc_configure(instance_->pp_instance(), false); 357 int32_t result = audio_track_.Configure(attrib_list, 358 cc_configure.GetCallback()); 359 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 360 audio_track_.Close(); 361 cc_configure.WaitForResult(result); 362 result = cc_configure.result(); 363 // Unfortunately, we can't control whether the configure succeeds or is 364 // aborted. 365 ASSERT_TRUE(result == PP_OK || result == PP_ERROR_ABORTED); 366 367 PASS(); 368} 369 370uint32_t CalculateWaveStartingTime(int16_t sample, int16_t next_sample, 371 uint32_t period) { 372 int16_t slope = next_sample - sample; 373 double angle = asin(sample / (double)INT16_MAX); 374 if (slope < 0) { 375 angle = M_PI - angle; 376 } 377 if (angle < 0) { 378 angle += 2 * M_PI; 379 } 380 return round(angle * period / (2 * M_PI)); 381} 382 383std::string TestMediaStreamAudioTrack::TestVerifyWaveform() { 384 // Create a track. 385 instance_->EvalScript(kSineJSCode); 386 event_.Wait(); 387 event_.Reset(); 388 389 ASSERT_FALSE(audio_track_.is_null()); 390 ASSERT_FALSE(audio_track_.HasEnded()); 391 ASSERT_FALSE(audio_track_.GetId().empty()); 392 393 // Use a weird buffer length and number of buffers. 394 const int32_t kBufferSize = 13; 395 const int32_t kNumBuffers = 3; 396 397 const uint32_t kChannels = 2; 398 const uint32_t kFreqLeft = 25; 399 const uint32_t kFreqRight = 100; 400 401 int32_t attrib_list[] = { 402 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kBufferSize, 403 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, kNumBuffers, 404 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE, 405 }; 406 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK)); 407 408 // Get kNumBuffers buffers and verify they conform to the expected waveform. 409 PP_TimeDelta timestamp = 0.0; 410 int sample_time = 0; 411 uint32_t left_start = 0; 412 uint32_t right_start = 0; 413 for (int j = 0; j < kNumBuffers; ++j) { 414 TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer( 415 instance_->pp_instance(), false); 416 cc_get_buffer.WaitForResult( 417 audio_track_.GetBuffer(cc_get_buffer.GetCallback())); 418 ASSERT_EQ(PP_OK, cc_get_buffer.result()); 419 pp::AudioBuffer buffer = cc_get_buffer.output(); 420 ASSERT_FALSE(buffer.is_null()); 421 ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate())); 422 ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS); 423 ASSERT_EQ(buffer.GetNumberOfChannels(), kChannels); 424 ASSERT_GE(buffer.GetTimestamp(), timestamp); 425 timestamp = buffer.GetTimestamp(); 426 427 uint32_t buffer_size = buffer.GetDataBufferSize(); 428 uint32_t sample_rate = buffer.GetSampleRate(); 429 uint32_t num_samples = buffer.GetNumberOfSamples(); 430 uint32_t bytes_per_frame = kChannels * 2; 431 ASSERT_EQ(num_samples, (kChannels * kBufferSize * sample_rate) / 1000); 432 ASSERT_EQ(buffer_size % bytes_per_frame, 0U); 433 ASSERT_EQ(buffer_size, num_samples * 2); 434 435 // Period of sine wave, in samples. 436 uint32_t left_period = sample_rate / kFreqLeft; 437 uint32_t right_period = sample_rate / kFreqRight; 438 439 int16_t* data_buffer = static_cast<int16_t*>(buffer.GetDataBuffer()); 440 ASSERT_TRUE(data_buffer != NULL); 441 442 if (j == 0) { 443 // The generated wave doesn't necessarily start at 0, so compensate for 444 // this. 445 left_start = CalculateWaveStartingTime(data_buffer[0], data_buffer[2], 446 left_period); 447 right_start = CalculateWaveStartingTime(data_buffer[1], data_buffer[3], 448 right_period); 449 } 450 451 for (uint32_t sample = 0; sample < num_samples; 452 sample += 2, sample_time++) { 453 int16_t left = data_buffer[sample]; 454 int16_t right = data_buffer[sample + 1]; 455 double angle = (2.0 * M_PI * ((sample_time + left_start) % left_period)) / 456 left_period; 457 int16_t expected = INT16_MAX * sin(angle); 458 // Account for off-by-one errors due to rounding. 459 ASSERT_GE(left, std::max<int16_t>(expected, INT16_MIN + 1) - 1); 460 ASSERT_LE(left, std::min<int16_t>(expected, INT16_MAX - 1) + 1); 461 462 angle = (2 * M_PI * ((sample_time + right_start) % right_period)) / 463 right_period; 464 expected = INT16_MAX * sin(angle); 465 ASSERT_GE(right, std::max<int16_t>(expected, INT16_MIN + 1) - 1); 466 ASSERT_LE(right, std::min<int16_t>(expected, INT16_MAX - 1) + 1); 467 } 468 469 audio_track_.RecycleBuffer(buffer); 470 } 471 472 PASS(); 473} 474