| Index: ppapi/tests/test_media_stream_audio_track.cc
|
| diff --git a/ppapi/tests/test_media_stream_audio_track.cc b/ppapi/tests/test_media_stream_audio_track.cc
|
| index 020cf0819be9e3c4549c01f53278d133647497c3..6509f2a16c9b92dc30cde45384e3447a96edabe7 100644
|
| --- a/ppapi/tests/test_media_stream_audio_track.cc
|
| +++ b/ppapi/tests/test_media_stream_audio_track.cc
|
| @@ -6,6 +6,13 @@
|
|
|
| #include "ppapi/tests/test_media_stream_audio_track.h"
|
|
|
| +// For MSVC.
|
| +#define _USE_MATH_DEFINES
|
| +#include <math.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"
|
| @@ -40,6 +47,31 @@ const char kJSCode[] =
|
| "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) {
|
| @@ -77,6 +109,7 @@ void TestMediaStreamAudioTrack::RunTests(const std::string& filter) {
|
| RUN_TEST(GetBuffer, filter);
|
| RUN_TEST(Configure, filter);
|
| RUN_TEST(ConfigureClose, filter);
|
| + RUN_TEST(VerifyWaveform, filter);
|
| }
|
|
|
| void TestMediaStreamAudioTrack::HandleMessage(const pp::Var& message) {
|
| @@ -174,8 +207,6 @@ std::string TestMediaStreamAudioTrack::CheckGetBuffer(
|
| ASSERT_GE(buffer.GetTimestamp(), timestamp);
|
| timestamp = buffer.GetTimestamp();
|
|
|
| - // TODO(amistry): Figure out how to inject a predictable audio pattern, such
|
| - // as a sawtooth, and check the buffer data to make sure it's correct.
|
| ASSERT_TRUE(buffer.GetDataBuffer() != NULL);
|
| if (expected_duration > 0) {
|
| uint32_t buffer_size = buffer.GetDataBufferSize();
|
| @@ -335,3 +366,108 @@ std::string TestMediaStreamAudioTrack::TestConfigureClose() {
|
|
|
| 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();
|
| +}
|
|
|