• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
24 REGISTER_TEST_CASE(MediaStreamAudioTrack);
25 
26 namespace {
27 
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);"
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 
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.
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.
IsSampleRateValid(PP_AudioBuffer_SampleRate sample_rate)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:
87       return true;
88     default:
89       return false;
90   }
91 }
92 
93 }  // namespace
94 
TestMediaStreamAudioTrack(TestingInstance * instance)95 TestMediaStreamAudioTrack::TestMediaStreamAudioTrack(TestingInstance* instance)
96     : TestCase(instance),
97       event_(instance_->pp_instance()) {
98 }
99 
Init()100 bool TestMediaStreamAudioTrack::Init() {
101   return true;
102 }
103 
~TestMediaStreamAudioTrack()104 TestMediaStreamAudioTrack::~TestMediaStreamAudioTrack() {
105 }
106 
RunTests(const std::string & filter)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);
113 }
114 
HandleMessage(const pp::Var & message)115 void TestMediaStreamAudioTrack::HandleMessage(const pp::Var& message) {
116   if (message.is_resource()) {
117     audio_track_ = pp::MediaStreamAudioTrack(message.AsResource());
118   }
119   event_.Signal();
120 }
121 
TestCreate()122 std::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 
TestGetBuffer()139 std::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 
CheckConfigure(int32_t attrib_list[],int32_t expected_result)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());
190   PASS();
191 }
192 
CheckGetBuffer(int times,int expected_duration)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);
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 
TestConfigure()229 std::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 
TestConfigureClose()340 std::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 
CalculateWaveStartingTime(int16_t sample,int16_t next_sample,uint32_t period)370 uint32_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 
TestVerifyWaveform()383 std::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