123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- //
- // Tests PPB_MediaStreamAudioTrack interface.
- #include "ppapi/tests/test_media_stream_audio_track.h"
- // For MSVC.
- #define _USE_MATH_DEFINES
- #include <math.h>
- #include <stddef.h>
- #include <stdint.h>
- #include <algorithm>
- #include "ppapi/c/private/ppb_testing_private.h"
- #include "ppapi/cpp/audio_buffer.h"
- #include "ppapi/cpp/completion_callback.h"
- #include "ppapi/cpp/instance.h"
- #include "ppapi/cpp/var.h"
- #include "ppapi/tests/test_utils.h"
- #include "ppapi/tests/testing_instance.h"
- REGISTER_TEST_CASE(MediaStreamAudioTrack);
- namespace {
- // Real constants defined in
- // content/renderer/pepper/pepper_media_stream_audio_track_host.cc.
- const int32_t kMaxNumberOfBuffers = 1000;
- const int32_t kMinDuration = 10;
- const int32_t kMaxDuration = 10000;
- const int32_t kTimes = 3;
- const char kJSCode[] =
- "function gotStream(stream) {"
- " test_stream = stream;"
- " var track = stream.getAudioTracks()[0];"
- " var plugin = document.getElementById('plugin');"
- " plugin.postMessage(track);"
- "}"
- "var constraints = {"
- " audio: true,"
- " video: false,"
- "};"
- "navigator.getUserMedia = "
- " navigator.getUserMedia || navigator.webkitGetUserMedia;"
- "navigator.getUserMedia(constraints,"
- " gotStream, function() {});";
- const char kSineJSCode[] =
- // Create oscillators for the left and right channels. Use a sine wave,
- // which is the easiest to calculate expected values. The oscillator output
- // is low-pass filtered (as per spec) making comparison hard.
- "var context = new AudioContext();"
- "var l_osc = context.createOscillator();"
- "l_osc.type = \"sine\";"
- "l_osc.frequency.value = 25;"
- "var r_osc = context.createOscillator();"
- "r_osc.type = \"sine\";"
- "r_osc.frequency.value = 100;"
- // Combine the left and right channels.
- "var merger = context.createChannelMerger(2);"
- "merger.channelInterpretation = \"discrete\";"
- "l_osc.connect(merger, 0, 0);"
- "r_osc.connect(merger, 0, 1);"
- "var dest_stream = context.createMediaStreamDestination();"
- "merger.connect(dest_stream);"
- // Dump the generated waveform to a MediaStream output.
- "l_osc.start();"
- "r_osc.start();"
- "var track = dest_stream.stream.getAudioTracks()[0];"
- "var plugin = document.getElementById('plugin');"
- "plugin.postMessage(track);";
- // Helper to check if the |sample_rate| is listed in PP_AudioBuffer_SampleRate
- // enum.
- bool IsSampleRateValid(PP_AudioBuffer_SampleRate sample_rate) {
- switch (sample_rate) {
- case PP_AUDIOBUFFER_SAMPLERATE_8000:
- case PP_AUDIOBUFFER_SAMPLERATE_16000:
- case PP_AUDIOBUFFER_SAMPLERATE_22050:
- case PP_AUDIOBUFFER_SAMPLERATE_32000:
- case PP_AUDIOBUFFER_SAMPLERATE_44100:
- case PP_AUDIOBUFFER_SAMPLERATE_48000:
- case PP_AUDIOBUFFER_SAMPLERATE_96000:
- case PP_AUDIOBUFFER_SAMPLERATE_192000:
- return true;
- default:
- return false;
- }
- }
- } // namespace
- TestMediaStreamAudioTrack::TestMediaStreamAudioTrack(TestingInstance* instance)
- : TestCase(instance),
- event_(instance_->pp_instance()) {
- }
- bool TestMediaStreamAudioTrack::Init() {
- return true;
- }
- TestMediaStreamAudioTrack::~TestMediaStreamAudioTrack() {
- }
- void TestMediaStreamAudioTrack::RunTests(const std::string& filter) {
- RUN_TEST(Create, filter);
- RUN_TEST(GetBuffer, filter);
- RUN_TEST(Configure, filter);
- RUN_TEST(ConfigureClose, filter);
- RUN_TEST(VerifyWaveform, filter);
- }
- void TestMediaStreamAudioTrack::HandleMessage(const pp::Var& message) {
- if (message.is_resource()) {
- audio_track_ = pp::MediaStreamAudioTrack(message.AsResource());
- }
- event_.Signal();
- }
- std::string TestMediaStreamAudioTrack::TestCreate() {
- // Create a track.
- instance_->EvalScript(kJSCode);
- event_.Wait();
- event_.Reset();
- ASSERT_FALSE(audio_track_.is_null());
- ASSERT_FALSE(audio_track_.HasEnded());
- ASSERT_FALSE(audio_track_.GetId().empty());
- // Close the track.
- audio_track_.Close();
- ASSERT_TRUE(audio_track_.HasEnded());
- audio_track_ = pp::MediaStreamAudioTrack();
- PASS();
- }
- std::string TestMediaStreamAudioTrack::TestGetBuffer() {
- // Create a track.
- instance_->EvalScript(kJSCode);
- event_.Wait();
- event_.Reset();
- ASSERT_FALSE(audio_track_.is_null());
- ASSERT_FALSE(audio_track_.HasEnded());
- ASSERT_FALSE(audio_track_.GetId().empty());
- PP_TimeDelta timestamp = 0.0;
- // Get |kTimes| buffers.
- for (int i = 0; i < kTimes; ++i) {
- TestCompletionCallbackWithOutput<pp::AudioBuffer> cc(
- instance_->pp_instance(), false);
- cc.WaitForResult(audio_track_.GetBuffer(cc.GetCallback()));
- ASSERT_EQ(PP_OK, cc.result());
- pp::AudioBuffer buffer = cc.output();
- ASSERT_FALSE(buffer.is_null());
- ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate()));
- ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS);
- ASSERT_GE(buffer.GetTimestamp(), timestamp);
- timestamp = buffer.GetTimestamp();
- ASSERT_GT(buffer.GetDataBufferSize(), 0U);
- ASSERT_TRUE(buffer.GetDataBuffer() != NULL);
- audio_track_.RecycleBuffer(buffer);
- // A recycled buffer should be invalidated.
- ASSERT_EQ(buffer.GetSampleRate(), PP_AUDIOBUFFER_SAMPLERATE_UNKNOWN);
- ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_UNKNOWN);
- ASSERT_EQ(buffer.GetDataBufferSize(), 0U);
- ASSERT_TRUE(buffer.GetDataBuffer() == NULL);
- }
- // Close the track.
- audio_track_.Close();
- ASSERT_TRUE(audio_track_.HasEnded());
- audio_track_ = pp::MediaStreamAudioTrack();
- PASS();
- }
- std::string TestMediaStreamAudioTrack::CheckConfigure(
- int32_t attrib_list[], int32_t expected_result) {
- TestCompletionCallback cc_configure(instance_->pp_instance(), false);
- cc_configure.WaitForResult(
- audio_track_.Configure(attrib_list, cc_configure.GetCallback()));
- ASSERT_EQ(expected_result, cc_configure.result());
- PASS();
- }
- std::string TestMediaStreamAudioTrack::CheckGetBuffer(
- int times, int expected_duration) {
- PP_TimeDelta timestamp = 0.0;
- for (int j = 0; j < times; ++j) {
- TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer(
- instance_->pp_instance(), false);
- cc_get_buffer.WaitForResult(
- audio_track_.GetBuffer(cc_get_buffer.GetCallback()));
- ASSERT_EQ(PP_OK, cc_get_buffer.result());
- pp::AudioBuffer buffer = cc_get_buffer.output();
- ASSERT_FALSE(buffer.is_null());
- ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate()));
- ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS);
- ASSERT_GE(buffer.GetTimestamp(), timestamp);
- timestamp = buffer.GetTimestamp();
- ASSERT_TRUE(buffer.GetDataBuffer() != NULL);
- if (expected_duration > 0) {
- uint32_t buffer_size = buffer.GetDataBufferSize();
- uint32_t channels = buffer.GetNumberOfChannels();
- uint32_t sample_rate = buffer.GetSampleRate();
- uint32_t bytes_per_frame = channels * 2;
- int32_t duration = expected_duration;
- ASSERT_EQ(buffer_size % bytes_per_frame, 0U);
- ASSERT_EQ(buffer_size,
- (duration * sample_rate * bytes_per_frame) / 1000);
- } else {
- ASSERT_GT(buffer.GetDataBufferSize(), 0U);
- }
- audio_track_.RecycleBuffer(buffer);
- }
- PASS();
- }
- std::string TestMediaStreamAudioTrack::TestConfigure() {
- // Create a track.
- instance_->EvalScript(kJSCode);
- event_.Wait();
- event_.Reset();
- ASSERT_FALSE(audio_track_.is_null());
- ASSERT_FALSE(audio_track_.HasEnded());
- ASSERT_FALSE(audio_track_.GetId().empty());
- // Perform a |Configure()| with no attributes. This ends up making an IPC
- // call, but the host implementation has a fast-path when there are no changes
- // to the configuration. This test is intended to hit that fast-path and make
- // sure it works correctly.
- {
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
- }
- // Configure number of buffers.
- struct {
- int32_t buffers;
- int32_t expect_result;
- } buffers[] = {
- { 8, PP_OK },
- { 100, PP_OK },
- { kMaxNumberOfBuffers, PP_OK },
- { -1, PP_ERROR_BADARGUMENT },
- { kMaxNumberOfBuffers + 1, PP_OK }, // Clipped to max value.
- { 0, PP_OK }, // Use default.
- };
- for (size_t i = 0; i < sizeof(buffers) / sizeof(buffers[0]); ++i) {
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, buffers[i].buffers,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list,
- buffers[i].expect_result));
- // Get some buffers. This should also succeed when configure fails.
- ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, -1));
- }
- // Configure buffer duration.
- struct {
- int32_t duration;
- int32_t expect_result;
- } durations[] = {
- { kMinDuration, PP_OK },
- { 123, PP_OK },
- { kMinDuration - 1, PP_ERROR_BADARGUMENT },
- { kMaxDuration + 1, PP_ERROR_BADARGUMENT },
- };
- for (size_t i = 0; i < sizeof(durations) / sizeof(durations[0]); ++i) {
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, durations[i].duration,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list,
- durations[i].expect_result));
- // Get some buffers. This always works, but the buffer size will vary.
- int duration =
- durations[i].expect_result == PP_OK ? durations[i].duration : -1;
- ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, duration));
- }
- // Test kMaxDuration separately since each GetBuffer will take 10 seconds.
- {
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMaxDuration,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
- }
- // Reset the duration to prevent the next part from taking 10 seconds.
- {
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMinDuration,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
- }
- // Configure should fail while plugin holds buffers.
- {
- TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer(
- instance_->pp_instance(), false);
- cc_get_buffer.WaitForResult(
- audio_track_.GetBuffer(cc_get_buffer.GetCallback()));
- ASSERT_EQ(PP_OK, cc_get_buffer.result());
- pp::AudioBuffer buffer = cc_get_buffer.output();
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, 0,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- TestCompletionCallback cc_configure(instance_->pp_instance(), false);
- cc_configure.WaitForResult(
- audio_track_.Configure(attrib_list, cc_configure.GetCallback()));
- ASSERT_EQ(PP_ERROR_INPROGRESS, cc_configure.result());
- audio_track_.RecycleBuffer(buffer);
- }
- // Close the track.
- audio_track_.Close();
- ASSERT_TRUE(audio_track_.HasEnded());
- audio_track_ = pp::MediaStreamAudioTrack();
- PASS();
- }
- std::string TestMediaStreamAudioTrack::TestConfigureClose() {
- // Create a track.
- instance_->EvalScript(kJSCode);
- event_.Wait();
- event_.Reset();
- ASSERT_FALSE(audio_track_.is_null());
- ASSERT_FALSE(audio_track_.HasEnded());
- ASSERT_FALSE(audio_track_.GetId().empty());
- // Configure the audio track and close it immediately. The Configure() call
- // should complete.
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, 10,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- TestCompletionCallback cc_configure(instance_->pp_instance(), false);
- int32_t result = audio_track_.Configure(attrib_list,
- cc_configure.GetCallback());
- ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
- audio_track_.Close();
- cc_configure.WaitForResult(result);
- result = cc_configure.result();
- // Unfortunately, we can't control whether the configure succeeds or is
- // aborted.
- ASSERT_TRUE(result == PP_OK || result == PP_ERROR_ABORTED);
- PASS();
- }
- uint32_t CalculateWaveStartingTime(int16_t sample, int16_t next_sample,
- uint32_t period) {
- int16_t slope = next_sample - sample;
- double angle = asin(sample / (double)INT16_MAX);
- if (slope < 0) {
- angle = M_PI - angle;
- }
- if (angle < 0) {
- angle += 2 * M_PI;
- }
- return round(angle * period / (2 * M_PI));
- }
- std::string TestMediaStreamAudioTrack::TestVerifyWaveform() {
- // Create a track.
- instance_->EvalScript(kSineJSCode);
- event_.Wait();
- event_.Reset();
- ASSERT_FALSE(audio_track_.is_null());
- ASSERT_FALSE(audio_track_.HasEnded());
- ASSERT_FALSE(audio_track_.GetId().empty());
- // Use a weird buffer length and number of buffers.
- const int32_t kBufferSize = 13;
- const int32_t kNumBuffers = 3;
- const uint32_t kChannels = 2;
- const uint32_t kFreqLeft = 25;
- const uint32_t kFreqRight = 100;
- int32_t attrib_list[] = {
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kBufferSize,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_BUFFERS, kNumBuffers,
- PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
- };
- ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
- // Get kNumBuffers buffers and verify they conform to the expected waveform.
- PP_TimeDelta timestamp = 0.0;
- int sample_time = 0;
- uint32_t left_start = 0;
- uint32_t right_start = 0;
- for (int j = 0; j < kNumBuffers; ++j) {
- TestCompletionCallbackWithOutput<pp::AudioBuffer> cc_get_buffer(
- instance_->pp_instance(), false);
- cc_get_buffer.WaitForResult(
- audio_track_.GetBuffer(cc_get_buffer.GetCallback()));
- ASSERT_EQ(PP_OK, cc_get_buffer.result());
- pp::AudioBuffer buffer = cc_get_buffer.output();
- ASSERT_FALSE(buffer.is_null());
- ASSERT_TRUE(IsSampleRateValid(buffer.GetSampleRate()));
- ASSERT_EQ(buffer.GetSampleSize(), PP_AUDIOBUFFER_SAMPLESIZE_16_BITS);
- ASSERT_EQ(buffer.GetNumberOfChannels(), kChannels);
- ASSERT_GE(buffer.GetTimestamp(), timestamp);
- timestamp = buffer.GetTimestamp();
- uint32_t buffer_size = buffer.GetDataBufferSize();
- uint32_t sample_rate = buffer.GetSampleRate();
- uint32_t num_samples = buffer.GetNumberOfSamples();
- uint32_t bytes_per_frame = kChannels * 2;
- ASSERT_EQ(num_samples, (kChannels * kBufferSize * sample_rate) / 1000);
- ASSERT_EQ(buffer_size % bytes_per_frame, 0U);
- ASSERT_EQ(buffer_size, num_samples * 2);
- // Period of sine wave, in samples.
- uint32_t left_period = sample_rate / kFreqLeft;
- uint32_t right_period = sample_rate / kFreqRight;
- int16_t* data_buffer = static_cast<int16_t*>(buffer.GetDataBuffer());
- ASSERT_TRUE(data_buffer != NULL);
- if (j == 0) {
- // The generated wave doesn't necessarily start at 0, so compensate for
- // this.
- left_start = CalculateWaveStartingTime(data_buffer[0], data_buffer[2],
- left_period);
- right_start = CalculateWaveStartingTime(data_buffer[1], data_buffer[3],
- right_period);
- }
- for (uint32_t sample = 0; sample < num_samples;
- sample += 2, sample_time++) {
- int16_t left = data_buffer[sample];
- int16_t right = data_buffer[sample + 1];
- double angle = (2.0 * M_PI * ((sample_time + left_start) % left_period)) /
- left_period;
- int16_t expected = INT16_MAX * sin(angle);
- // Account for off-by-one errors due to rounding.
- ASSERT_GE(left, std::max<int16_t>(expected, INT16_MIN + 1) - 1);
- ASSERT_LE(left, std::min<int16_t>(expected, INT16_MAX - 1) + 1);
- angle = (2 * M_PI * ((sample_time + right_start) % right_period)) /
- right_period;
- expected = INT16_MAX * sin(angle);
- ASSERT_GE(right, std::max<int16_t>(expected, INT16_MIN + 1) - 1);
- ASSERT_LE(right, std::min<int16_t>(expected, INT16_MAX - 1) + 1);
- }
- audio_track_.RecycleBuffer(buffer);
- }
- PASS();
- }
|