blob: a4eae16f5dec4818dd0d82b9ed341ed600603dfa [file] [log] [blame]
// Copyright 2023 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/player/decoded_audio_internal.h"
#include <cmath>
#include <utility>
#include "starboard/common/log.h"
#include "starboard/shared/starboard/media/media_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace {
using ::starboard::shared::starboard::media::GetBytesPerSample;
constexpr int kChannels = 2;
constexpr SbTime kTimestamp = kSbTimeSecond;
constexpr int kSizeInBytes = 4192;
constexpr int kSampleRate = 22050;
constexpr double kPi = 3.1415926535897932384626;
constexpr SbMediaAudioSampleType kSampleTypes[] = {
kSbMediaAudioSampleTypeInt16Deprecated, kSbMediaAudioSampleTypeFloat32};
constexpr SbMediaAudioFrameStorageType kStorageTypes[] = {
kSbMediaAudioFrameStorageTypeInterleaved,
kSbMediaAudioFrameStorageTypePlanar};
// The following two functions fill `data` with audio samples of a sine wave
// starting from `initial_angle`. `stride` is the number of samples per group.
// For example:
// 1. When `stride` is one, all samples filled with be continuous.
// 2. When `stride` is 2, filling `data` and `data + 1` fills an interleaved
// stereo audio buffer with samples.
void Fill(int16_t* data,
double initial_angle,
double angle_step,
int count,
int stride) {
SB_DCHECK(data);
auto current_angel = initial_angle;
for (int i = 0; i < count; ++i) {
*data = static_cast<int16_t>(sin(current_angel) * 32767);
current_angel += angle_step;
data += stride;
}
}
void Fill(float* data,
double initial_angle,
double angle_step,
int count,
int stride) {
SB_DCHECK(data);
auto current_angel = initial_angle;
for (int i = 0; i < count; ++i) {
*data = static_cast<float>(sin(current_angel));
current_angel += angle_step;
data += stride;
}
}
// The following two functions verify `data` with audio samples against a sine
// wave starting from `initial_angle`. `stride` is the number of samples per
// group. For example:
// 1. When `stride` is one, all samples will be treated as continuous.
// 2. When `stride` is 2, verifying against `data` and `data + 1` verifies an
// interleaved stereo audio buffer.
void Verify(const int16_t* data,
double initial_angle,
double angle_step,
int count,
int stride) {
SB_DCHECK(data);
auto current_angel = initial_angle;
for (int i = 0; i < count; ++i) {
const auto current_value = static_cast<int16_t>(sin(current_angel) * 32767);
// Using `ASSERT_NEAR()` to allow for small value drifting due to
// conversions between sample types.
ASSERT_NEAR(static_cast<double>(*data), static_cast<double>(current_value),
2.0 / 32767);
current_angel += angle_step;
data += stride;
}
}
void Verify(const float* data,
double initial_angle,
double angle_step,
int count,
int stride) {
SB_DCHECK(data);
auto current_angel = initial_angle;
for (int i = 0; i < count; ++i) {
const auto current_value = static_cast<float>(sin(current_angel));
// Using `ASSERT_NEAR()` to allow for small value drifting due to
// conversions between sample types.
ASSERT_NEAR(static_cast<double>(*data), static_cast<double>(current_value),
2.0 / 32767);
current_angel += angle_step;
data += stride;
}
}
// Fill `decoded_audio` with sine wave samples, with phase shift of Pi/2 on each
// channel.
void Fill(scoped_refptr<DecodedAudio>* decoded_audio) {
SB_DCHECK(decoded_audio);
SB_DCHECK(*decoded_audio);
bool is_int16 =
(*decoded_audio)->sample_type() == kSbMediaAudioSampleTypeInt16Deprecated;
bool is_interleaved = (*decoded_audio)->storage_type() ==
kSbMediaAudioFrameStorageTypeInterleaved;
for (int i = 0; i < (*decoded_audio)->channels(); ++i) {
if (is_int16 && is_interleaved) {
Fill((*decoded_audio)->data_as_int16() + i, kPi / 2 * i, kPi / 180,
(*decoded_audio)->frames(), (*decoded_audio)->channels());
} else if (!is_int16 && is_interleaved) {
Fill((*decoded_audio)->data_as_float32() + i, kPi / 2 * i, kPi / 180,
(*decoded_audio)->frames(), (*decoded_audio)->channels());
} else if (is_int16 && !is_interleaved) {
Fill((*decoded_audio)->data_as_int16() + (*decoded_audio)->frames() * i,
kPi / 2 * i, kPi / 180, (*decoded_audio)->frames(), 1);
} else if (!is_int16 && !is_interleaved) {
Fill((*decoded_audio)->data_as_float32() + (*decoded_audio)->frames() * i,
kPi / 2 * i, kPi / 180, (*decoded_audio)->frames(), 1);
}
}
}
// verify `decoded_audio` against sine wave samples, with phase shift of Pi/2 on
// each channel.
void Verify(const scoped_refptr<DecodedAudio>& decoded_audio) {
SB_DCHECK(decoded_audio);
bool is_int16 =
decoded_audio->sample_type() == kSbMediaAudioSampleTypeInt16Deprecated;
bool is_interleaved =
decoded_audio->storage_type() == kSbMediaAudioFrameStorageTypeInterleaved;
for (int i = 0; i < decoded_audio->channels(); ++i) {
if (is_int16 && is_interleaved) {
ASSERT_NO_FATAL_FAILURE(
Verify(decoded_audio->data_as_int16() + i, kPi / 2 * i, kPi / 180,
decoded_audio->frames(), decoded_audio->channels()));
} else if (!is_int16 && is_interleaved) {
ASSERT_NO_FATAL_FAILURE(
Verify(decoded_audio->data_as_float32() + i, kPi / 2 * i, kPi / 180,
decoded_audio->frames(), decoded_audio->channels()));
} else if (is_int16 && !is_interleaved) {
ASSERT_NO_FATAL_FAILURE(
Verify(decoded_audio->data_as_int16() + decoded_audio->frames() * i,
kPi / 2 * i, kPi / 180, decoded_audio->frames(), 1));
} else if (!is_int16 && !is_interleaved) {
ASSERT_NO_FATAL_FAILURE(
Verify(decoded_audio->data_as_float32() + decoded_audio->frames() * i,
kPi / 2 * i, kPi / 180, decoded_audio->frames(), 1));
}
}
}
TEST(DecodedAudioTest, DefaultCtor) {
scoped_refptr<DecodedAudio> decoded_audio(new DecodedAudio);
EXPECT_TRUE(decoded_audio->is_end_of_stream());
}
TEST(DecodedAudioTest, CtorWithSize) {
for (auto sample_type : kSampleTypes) {
for (auto storage_type : kStorageTypes) {
scoped_refptr<DecodedAudio> decoded_audio(new DecodedAudio(
kChannels, sample_type, storage_type, kTimestamp, kSizeInBytes));
EXPECT_FALSE(decoded_audio->is_end_of_stream());
EXPECT_EQ(decoded_audio->channels(), kChannels);
EXPECT_EQ(decoded_audio->sample_type(), sample_type);
EXPECT_EQ(decoded_audio->storage_type(), storage_type);
EXPECT_EQ(decoded_audio->size_in_bytes(), kSizeInBytes);
EXPECT_EQ(decoded_audio->frames(),
kSizeInBytes / GetBytesPerSample(decoded_audio->sample_type()) /
kChannels);
Fill(&decoded_audio);
Verify(decoded_audio);
}
}
}
TEST(DecodedAudioTest, AdjustForSeekTime) {
for (int channels = 1; channels <= 6; ++channels) {
for (auto sample_type : kSampleTypes) {
scoped_refptr<DecodedAudio> original_decoded_audio(new DecodedAudio(
kChannels, sample_type, kSbMediaAudioFrameStorageTypeInterleaved,
kTimestamp, kSizeInBytes));
Fill(&original_decoded_audio);
scoped_refptr<DecodedAudio> adjusted_decoded_audio =
original_decoded_audio->Clone();
// Adjust to the beginning of `adjusted_decoded_audio` should be a no-op.
adjusted_decoded_audio->AdjustForSeekTime(
kSampleRate, adjusted_decoded_audio->timestamp());
ASSERT_EQ(*original_decoded_audio, *adjusted_decoded_audio);
// Adjust to an invalid timestamp before the time range of
// `adjusted_decoded_audio`, it's a no-op.
adjusted_decoded_audio->AdjustForSeekTime(
kSampleRate, adjusted_decoded_audio->timestamp() - kSbTimeSecond / 2);
ASSERT_EQ(*original_decoded_audio, *adjusted_decoded_audio);
// Adjust to an invalid timestamp after the time range of
// `adjusted_decoded_audio`, it's also a no-op.
adjusted_decoded_audio->AdjustForSeekTime(
kSampleRate,
adjusted_decoded_audio->timestamp() + kSbTimeSecond * 100);
ASSERT_EQ(*original_decoded_audio, *adjusted_decoded_audio);
const SbTime duration = media::AudioFramesToDuration(
adjusted_decoded_audio->frames(), kSampleRate);
const SbTime duration_of_one_frame =
media::AudioFramesToDuration(1, kSampleRate) + 1;
for (int i = 1; i < 10; ++i) {
adjusted_decoded_audio = original_decoded_audio->Clone();
// Adjust to the middle of `adjusted_decoded_audio`.
SbTime seek_time =
adjusted_decoded_audio->timestamp() + duration * i / 10;
adjusted_decoded_audio->AdjustForSeekTime(kSampleRate, seek_time);
ASSERT_NEAR(adjusted_decoded_audio->frames(),
original_decoded_audio->frames() * (10 - i) / 10, 1);
ASSERT_LE(adjusted_decoded_audio->timestamp(), seek_time);
ASSERT_NEAR(adjusted_decoded_audio->timestamp(), seek_time,
duration_of_one_frame);
auto offset_in_bytes = original_decoded_audio->size_in_bytes() -
adjusted_decoded_audio->size_in_bytes();
ASSERT_TRUE(memcmp(adjusted_decoded_audio->data(),
original_decoded_audio->data() + offset_in_bytes,
adjusted_decoded_audio->size_in_bytes()) == 0);
}
}
}
}
TEST(DecodedAudioTest, AdjustForDiscardedDurations) {
for (int channels = 1; channels <= 6; ++channels) {
for (auto sample_type : kSampleTypes) {
scoped_refptr<DecodedAudio> original_decoded_audio(new DecodedAudio(
kChannels, sample_type, kSbMediaAudioFrameStorageTypeInterleaved,
kTimestamp, kSizeInBytes));
Fill(&original_decoded_audio);
scoped_refptr<DecodedAudio> adjusted_decoded_audio =
original_decoded_audio->Clone();
adjusted_decoded_audio->AdjustForDiscardedDurations(kSampleRate, 0, 0);
ASSERT_EQ(*original_decoded_audio, *adjusted_decoded_audio);
auto duration_of_decoded_audio = media::AudioFramesToDuration(
original_decoded_audio->frames(), kSampleRate);
auto quarter_duration = duration_of_decoded_audio / 4;
auto duration_of_one_frame =
media::AudioFramesToDuration(1, kSampleRate) + 1;
adjusted_decoded_audio->AdjustForDiscardedDurations(
kSampleRate, quarter_duration, quarter_duration);
ASSERT_NEAR(adjusted_decoded_audio->frames(),
original_decoded_audio->frames() / 2, 2);
ASSERT_NEAR(adjusted_decoded_audio->timestamp(),
original_decoded_audio->timestamp() + quarter_duration,
duration_of_one_frame * 2);
adjusted_decoded_audio = original_decoded_audio->Clone();
// Adjust more frames than it has from front
adjusted_decoded_audio->AdjustForDiscardedDurations(
kSampleRate, duration_of_decoded_audio * 2, 0);
ASSERT_EQ(adjusted_decoded_audio->frames(), 0);
ASSERT_NEAR(
adjusted_decoded_audio->timestamp(),
original_decoded_audio->timestamp() + duration_of_decoded_audio,
duration_of_one_frame * 2);
adjusted_decoded_audio = original_decoded_audio->Clone();
// Adjust more frames than it has from back
adjusted_decoded_audio->AdjustForDiscardedDurations(
kSampleRate, 0, duration_of_decoded_audio * 2);
ASSERT_EQ(adjusted_decoded_audio->frames(), 0);
ASSERT_EQ(adjusted_decoded_audio->timestamp(),
original_decoded_audio->timestamp());
}
}
}
TEST(DecodedAudioTest, SwitchFormatTo) {
for (auto original_sample_type : kSampleTypes) {
for (auto original_storage_type : kStorageTypes) {
scoped_refptr<DecodedAudio> original_decoded_audio(
new DecodedAudio(kChannels, original_sample_type,
original_storage_type, kTimestamp, kSizeInBytes));
Fill(&original_decoded_audio);
for (auto new_sample_type : kSampleTypes) {
for (auto new_storage_type : kStorageTypes) {
if (!original_decoded_audio->IsFormat(new_sample_type,
new_storage_type)) {
scoped_refptr<DecodedAudio> new_decoded_audio =
original_decoded_audio->SwitchFormatTo(new_sample_type,
new_storage_type);
EXPECT_FALSE(new_decoded_audio->is_end_of_stream());
EXPECT_EQ(new_decoded_audio->channels(),
original_decoded_audio->channels());
EXPECT_EQ(new_decoded_audio->timestamp(),
original_decoded_audio->timestamp());
EXPECT_EQ(new_decoded_audio->frames(),
original_decoded_audio->frames());
EXPECT_TRUE(
new_decoded_audio->IsFormat(new_sample_type, new_storage_type));
ASSERT_NO_FATAL_FAILURE(Verify(new_decoded_audio));
}
}
}
}
}
}
TEST(DecodedAudioTest, Clone) {
for (auto sample_type : kSampleTypes) {
scoped_refptr<DecodedAudio> decoded_audio(new DecodedAudio(
kChannels, sample_type, kSbMediaAudioFrameStorageTypeInterleaved,
kTimestamp, kSizeInBytes));
Fill(&decoded_audio);
auto copy = decoded_audio->Clone();
ASSERT_EQ(*copy, *decoded_audio);
ASSERT_GT(decoded_audio->size_in_bytes(), 0);
decoded_audio->data()[0] = ~decoded_audio->data()[0];
ASSERT_NE(*copy, *decoded_audio);
}
}
} // namespace
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard