#include "ppapi/tests/test_media_stream_audio_track.h"
#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 {
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[] =
"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;"
"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);"
"l_osc.start();"
"r_osc.start();"
"var track = dest_stream.stream.getAudioTracks()[0];"
"var plugin = document.getElementById('plugin');"
"plugin.postMessage(track);";
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;
}
}
}
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() {
instance_->EvalScript(kJSCode);
event_.Wait();
event_.Reset();
ASSERT_FALSE(audio_track_.is_null());
ASSERT_FALSE(audio_track_.HasEnded());
ASSERT_FALSE(audio_track_.GetId().empty());
audio_track_.Close();
ASSERT_TRUE(audio_track_.HasEnded());
audio_track_ = pp::MediaStreamAudioTrack();
PASS();
}
std::string TestMediaStreamAudioTrack::TestGetBuffer() {
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;
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);
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);
}
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() {
instance_->EvalScript(kJSCode);
event_.Wait();
event_.Reset();
ASSERT_FALSE(audio_track_.is_null());
ASSERT_FALSE(audio_track_.HasEnded());
ASSERT_FALSE(audio_track_.GetId().empty());
{
int32_t attrib_list[] = {
PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
};
ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
}
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 },
{ 0, PP_OK },
};
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));
ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, -1));
}
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));
int duration =
durations[i].expect_result == PP_OK ? durations[i].duration : -1;
ASSERT_SUBTEST_SUCCESS(CheckGetBuffer(kTimes, duration));
}
{
int32_t attrib_list[] = {
PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMaxDuration,
PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
};
ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
}
{
int32_t attrib_list[] = {
PP_MEDIASTREAMAUDIOTRACK_ATTRIB_DURATION, kMinDuration,
PP_MEDIASTREAMAUDIOTRACK_ATTRIB_NONE,
};
ASSERT_SUBTEST_SUCCESS(CheckConfigure(attrib_list, PP_OK));
}
{
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);
}
audio_track_.Close();
ASSERT_TRUE(audio_track_.HasEnded());
audio_track_ = pp::MediaStreamAudioTrack();
PASS();
}
std::string TestMediaStreamAudioTrack::TestConfigureClose() {
instance_->EvalScript(kJSCode);
event_.Wait();
event_.Reset();
ASSERT_FALSE(audio_track_.is_null());
ASSERT_FALSE(audio_track_.HasEnded());
ASSERT_FALSE(audio_track_.GetId().empty());
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();
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() {
instance_->EvalScript(kSineJSCode);
event_.Wait();
event_.Reset();
ASSERT_FALSE(audio_track_.is_null());
ASSERT_FALSE(audio_track_.HasEnded());
ASSERT_FALSE(audio_track_.GetId().empty());
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));
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);
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) {
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);
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();
}