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.
5 // Tests PPB_MediaStreamAudioTrack interface.
7 #include "ppapi/tests/test_media_stream_audio_track.h"
10 #define _USE_MATH_DEFINES
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"
24 REGISTER_TEST_CASE(MediaStreamAudioTrack
);
28 // Real constants defined in
29 // content/renderer/pepper/pepper_media_stream_audio_track_host.cc.
30 const int32_t kMaxNumberOfBuffers
= 1000;
31 const int32_t kMinDuration
= 10;
32 const int32_t kMaxDuration
= 10000;
33 const int32_t kTimes
= 3;
34 const 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);"
45 "navigator.getUserMedia = "
46 " navigator.getUserMedia || navigator.webkitGetUserMedia;"
47 "navigator.getUserMedia(constraints,"
48 " gotStream, function() {});";
50 const 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.
71 "var track = dest_stream.stream.getAudioTracks()[0];"
72 "var plugin = document.getElementById('plugin');"
73 "plugin.postMessage(track);";
75 // Helper to check if the |sample_rate| is listed in PP_AudioBuffer_SampleRate
77 bool 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
:
95 TestMediaStreamAudioTrack::TestMediaStreamAudioTrack(TestingInstance
* instance
)
97 event_(instance_
->pp_instance()) {
100 bool TestMediaStreamAudioTrack::Init() {
104 TestMediaStreamAudioTrack::~TestMediaStreamAudioTrack() {
107 void 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
);
115 void TestMediaStreamAudioTrack::HandleMessage(const pp::Var
& message
) {
116 if (message
.is_resource()) {
117 audio_track_
= pp::MediaStreamAudioTrack(message
.AsResource());
122 std::string
TestMediaStreamAudioTrack::TestCreate() {
124 instance_
->EvalScript(kJSCode
);
128 ASSERT_FALSE(audio_track_
.is_null());
129 ASSERT_FALSE(audio_track_
.HasEnded());
130 ASSERT_FALSE(audio_track_
.GetId().empty());
133 audio_track_
.Close();
134 ASSERT_TRUE(audio_track_
.HasEnded());
135 audio_track_
= pp::MediaStreamAudioTrack();
139 std::string
TestMediaStreamAudioTrack::TestGetBuffer() {
141 instance_
->EvalScript(kJSCode
);
145 ASSERT_FALSE(audio_track_
.is_null());
146 ASSERT_FALSE(audio_track_
.HasEnded());
147 ASSERT_FALSE(audio_track_
.GetId().empty());
149 PP_TimeDelta timestamp
= 0.0;
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
);
162 ASSERT_GE(buffer
.GetTimestamp(), timestamp
);
163 timestamp
= buffer
.GetTimestamp();
165 ASSERT_GT(buffer
.GetDataBufferSize(), 0U);
166 ASSERT_TRUE(buffer
.GetDataBuffer() != NULL
);
168 audio_track_
.RecycleBuffer(buffer
);
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
);
178 audio_track_
.Close();
179 ASSERT_TRUE(audio_track_
.HasEnded());
180 audio_track_
= pp::MediaStreamAudioTrack();
184 std::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());
193 std::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
);
207 ASSERT_GE(buffer
.GetTimestamp(), timestamp
);
208 timestamp
= buffer
.GetTimestamp();
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);
221 ASSERT_GT(buffer
.GetDataBufferSize(), 0U);
224 audio_track_
.RecycleBuffer(buffer
);
229 std::string
TestMediaStreamAudioTrack::TestConfigure() {
231 instance_
->EvalScript(kJSCode
);
235 ASSERT_FALSE(audio_track_
.is_null());
236 ASSERT_FALSE(audio_track_
.HasEnded());
237 ASSERT_FALSE(audio_track_
.GetId().empty());
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.
244 int32_t attrib_list
[] = {
245 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE
,
247 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list
, PP_OK
));
250 // Configure number of buffers.
253 int32_t expect_result
;
257 { kMaxNumberOfBuffers
, PP_OK
},
258 { -1, PP_ERROR_BADARGUMENT
},
259 { kMaxNumberOfBuffers
+ 1, PP_OK
}, // Clipped to max value.
260 { 0, PP_OK
}, // Use default.
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
,
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));
273 // Configure buffer duration.
276 int32_t expect_result
;
278 { kMinDuration
, PP_OK
},
280 { kMinDuration
- 1, PP_ERROR_BADARGUMENT
},
281 { kMaxDuration
+ 1, PP_ERROR_BADARGUMENT
},
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
,
288 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list
,
289 durations
[i
].expect_result
));
291 // Get some buffers. This always works, but the buffer size will vary.
293 durations
[i
].expect_result
== PP_OK
? durations
[i
].duration
: -1;
294 ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes
, duration
));
296 // Test kMaxDuration separately since each GetBuffer will take 10 seconds.
298 int32_t attrib_list
[] = {
299 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION
, kMaxDuration
,
300 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE
,
302 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list
, PP_OK
));
305 // Reset the duration to prevent the next part from taking 10 seconds.
307 int32_t attrib_list
[] = {
308 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION
, kMinDuration
,
309 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE
,
311 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list
, PP_OK
));
314 // Configure should fail while plugin holds buffers.
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
,
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
);
334 audio_track_
.Close();
335 ASSERT_TRUE(audio_track_
.HasEnded());
336 audio_track_
= pp::MediaStreamAudioTrack();
340 std::string
TestMediaStreamAudioTrack::TestConfigureClose() {
342 instance_
->EvalScript(kJSCode
);
346 ASSERT_FALSE(audio_track_
.is_null());
347 ASSERT_FALSE(audio_track_
.HasEnded());
348 ASSERT_FALSE(audio_track_
.GetId().empty());
350 // Configure the audio track and close it immediately. The Configure() call
352 int32_t attrib_list
[] = {
353 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS
, 10,
354 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE
,
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
365 ASSERT_TRUE(result
== PP_OK
|| result
== PP_ERROR_ABORTED
);
370 uint32_t CalculateWaveStartingTime(int16_t sample
, int16_t next_sample
,
372 int16_t slope
= next_sample
- sample
;
373 double angle
= asin(sample
/ (double)INT16_MAX
);
375 angle
= M_PI
- angle
;
380 return round(angle
* period
/ (2 * M_PI
));
383 std::string
TestMediaStreamAudioTrack::TestVerifyWaveform() {
385 instance_
->EvalScript(kSineJSCode
);
389 ASSERT_FALSE(audio_track_
.is_null());
390 ASSERT_FALSE(audio_track_
.HasEnded());
391 ASSERT_FALSE(audio_track_
.GetId().empty());
393 // Use a weird buffer length and number of buffers.
394 const int32_t kBufferSize
= 13;
395 const int32_t kNumBuffers
= 3;
397 const uint32_t kChannels
= 2;
398 const uint32_t kFreqLeft
= 25;
399 const uint32_t kFreqRight
= 100;
401 int32_t attrib_list
[] = {
402 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION
, kBufferSize
,
403 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS
, kNumBuffers
,
404 PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE
,
406 ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list
, PP_OK
));
408 // Get kNumBuffers buffers and verify they conform to the expected waveform.
409 PP_TimeDelta timestamp
= 0.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();
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);
435 // Period of sine wave, in samples.
436 uint32_t left_period
= sample_rate
/ kFreqLeft
;
437 uint32_t right_period
= sample_rate
/ kFreqRight
;
439 int16_t* data_buffer
= static_cast<int16_t*>(buffer
.GetDataBuffer());
440 ASSERT_TRUE(data_buffer
!= NULL
);
443 // The generated wave doesn't necessarily start at 0, so compensate for
445 left_start
= CalculateWaveStartingTime(data_buffer
[0], data_buffer
[2],
447 right_start
= CalculateWaveStartingTime(data_buffer
[1], data_buffer
[3],
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
)) /
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);
462 angle
= (2 * M_PI
* ((sample_time
+ right_start
) % 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);
469 audio_track_
.RecycleBuffer(buffer
);