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