Home | History | Annotate | Download | only in tests
      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.
     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 
     95 TestMediaStreamAudioTrack::TestMediaStreamAudioTrack(TestingInstance* instance)
     96     : TestCase(instance),
     97       event_(instance_->pp_instance()) {
     98 }
     99 
    100 bool TestMediaStreamAudioTrack::Init() {
    101   return true;
    102 }
    103 
    104 TestMediaStreamAudioTrack::~TestMediaStreamAudioTrack() {
    105 }
    106 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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