blob: 737dc206bb32f183fd453ac81cc89b2d45e63697 [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 "cobalt/media/base/decoder_buffer_cache.h"
#include <algorithm>
#include "base/time/time.h"
#include "cobalt/media/decoder_buffer_allocator.h"
#include "starboard/media.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/chromium//media/base/decoder_buffer.h"
namespace cobalt {
namespace media {
namespace {
using ::media::DecoderBuffer;
using ::media::DemuxerStream;
constexpr size_t kMaxSamplesPerRead = 8;
constexpr size_t kBufferArraySize = 8;
constexpr size_t kBufferSize = 8;
constexpr uint8_t kBufferDataArray[kBufferArraySize][kBufferSize] = {
{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01},
{0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02},
{0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03},
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04},
{0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05},
{0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06},
{0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07},
{0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08},
};
const size_t kVideoKeyFrameFrequency = 4;
const size_t kDurationPerFrame = 16;
bool operator==(const SbMediaAudioStreamInfo& left,
const SbMediaAudioStreamInfo& right) {
return !(left != right);
}
bool operator==(const SbMediaVideoStreamInfo& left,
const SbMediaVideoStreamInfo& right) {
return !(left != right);
}
SbMediaAudioStreamInfo GetAudioStreamInfo() {
SbMediaAudioStreamInfo stream_info;
stream_info.codec = kSbMediaAudioCodecAac;
stream_info.mime = "";
stream_info.number_of_channels = 2;
stream_info.samples_per_second = 44100;
stream_info.audio_specific_config_size = 0;
return stream_info;
}
SbMediaVideoStreamInfo GetVideoStreamInfo() {
SbMediaVideoStreamInfo stream_info;
stream_info.codec = kSbMediaVideoCodecH264;
stream_info.mime = "";
stream_info.max_video_capabilities = "";
stream_info.frame_width = 1920;
stream_info.frame_height = 1080;
return stream_info;
}
DecoderBufferCache::DecoderBuffers GetAudioSourceBuffers() {
DecoderBufferCache::DecoderBuffers source_buffers;
for (size_t i = 0; i < kBufferArraySize; i++) {
auto decoder_buffer =
media::DecoderBuffer::CopyFrom(kBufferDataArray[i], kBufferSize);
decoder_buffer->set_is_key_frame(true);
decoder_buffer->set_timestamp(
base::TimeDelta::FromMilliseconds(kDurationPerFrame * i));
source_buffers.push_back(decoder_buffer);
}
return source_buffers;
}
DecoderBufferCache::DecoderBuffers GetVideoSourceBuffers() {
DecoderBufferCache::DecoderBuffers source_buffers;
for (size_t i = 0; i < kBufferArraySize; i++) {
auto decoder_buffer =
media::DecoderBuffer::CopyFrom(kBufferDataArray[i], kBufferSize);
if (i % kVideoKeyFrameFrequency == 0) {
decoder_buffer->set_is_key_frame(true);
decoder_buffer->set_timestamp(
base::TimeDelta::FromMilliseconds(kDurationPerFrame * i));
} else {
// Intended to reverse the timestamp orders.
decoder_buffer->set_timestamp(base::TimeDelta::FromMilliseconds(
kDurationPerFrame *
(i + kVideoKeyFrameFrequency - 2 * (i % kVideoKeyFrameFrequency))));
}
source_buffers.push_back(decoder_buffer);
}
return source_buffers;
}
bool CompareOutputBuffers(const DecoderBufferCache::DecoderBuffers& source,
size_t start_index,
const DecoderBufferCache::DecoderBuffers& output) {
if (start_index + output.size() > source.size()) {
return false;
}
for (size_t i = 0; i < output.size(); i++) {
if (source[start_index + i] != output[i]) {
return false;
}
}
return true;
}
template <typename StreamInfo>
void ReadAndCompareOutputs(DecoderBufferCache* cache, size_t buffers_to_read,
const DecoderBufferCache::DecoderBuffers& source,
size_t start_index,
const StreamInfo& source_stream_info) {
StreamInfo output_stream_info;
DecoderBufferCache::DecoderBuffers output_buffers;
while (buffers_to_read > 0) {
output_buffers.clear();
cache->ReadBuffers(&output_buffers,
std::min(kMaxSamplesPerRead, buffers_to_read),
&output_stream_info);
ASSERT_TRUE(source_stream_info == output_stream_info);
ASSERT_GT(output_buffers.size(), 0);
ASSERT_TRUE(CompareOutputBuffers(source, start_index, output_buffers));
start_index += output_buffers.size();
buffers_to_read -= output_buffers.size();
}
}
size_t FindKeyFrameIndexBeforeTime(
const DecoderBufferCache::DecoderBuffers& source,
const base::TimeDelta& time) {
SB_DCHECK(!source.empty());
SB_DCHECK(source[0]->is_key_frame());
size_t previous_key_frame_index = 0;
for (size_t i = 1; i < source.size(); i++) {
if (source[i]->is_key_frame()) {
if (source[i]->timestamp() <= time) {
previous_key_frame_index = i;
} else {
return previous_key_frame_index;
}
}
}
return previous_key_frame_index;
}
} // namespace
TEST(DecoderBufferCacheTest, SunnyDay) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info = GetAudioStreamInfo();
SbMediaVideoStreamInfo source_video_stream_info = GetVideoStreamInfo();
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
cache.AddBuffers(source_audio_buffers, source_audio_stream_info);
cache.AddBuffers(source_video_buffers, source_video_stream_info);
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_audio_buffers.size(),
source_audio_buffers, 0, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_video_buffers.size(),
source_video_buffers, 0, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
cache.ClearAll();
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
}
TEST(DecoderBufferCacheTest, EmptyCache) {
DecoderBufferCache cache;
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
DecoderBufferCache::DecoderBuffers buffers;
SbMediaAudioStreamInfo audio_stream_info;
SbMediaVideoStreamInfo video_stream_info;
cache.ReadBuffers(&buffers, kMaxSamplesPerRead, &audio_stream_info);
ASSERT_TRUE(buffers.empty());
cache.ReadBuffers(&buffers, kMaxSamplesPerRead, &video_stream_info);
ASSERT_TRUE(buffers.empty());
cache.ClearSegmentsBeforeMediaTime(base::TimeDelta());
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
cache.ReadBuffers(&buffers, kMaxSamplesPerRead, &audio_stream_info);
ASSERT_TRUE(buffers.empty());
cache.ReadBuffers(&buffers, kMaxSamplesPerRead, &video_stream_info);
ASSERT_TRUE(buffers.empty());
}
TEST(DecoderBufferCacheTest, ClearSegmentsWithZero) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info = GetAudioStreamInfo();
SbMediaVideoStreamInfo source_video_stream_info = GetVideoStreamInfo();
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
cache.AddBuffers(source_audio_buffers, source_audio_stream_info);
cache.AddBuffers(source_video_buffers, source_video_stream_info);
// Cache should not clear anything.
cache.ClearSegmentsBeforeMediaTime(base::TimeDelta::FromMilliseconds(0));
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_audio_buffers.size(),
source_audio_buffers, 0, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_video_buffers.size(),
source_video_buffers, 0, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
}
TEST(DecoderBufferCacheTest, ClearSegmentsAndResume) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info = GetAudioStreamInfo();
SbMediaVideoStreamInfo source_video_stream_info = GetVideoStreamInfo();
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
cache.AddBuffers(source_audio_buffers, source_audio_stream_info);
cache.AddBuffers(source_video_buffers, source_video_stream_info);
base::TimeDelta clear_time =
base::TimeDelta::FromMilliseconds(kDurationPerFrame);
cache.ClearSegmentsBeforeMediaTime(clear_time);
size_t start_index =
FindKeyFrameIndexBeforeTime(source_audio_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers.size() - start_index, source_audio_buffers,
start_index, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
start_index = FindKeyFrameIndexBeforeTime(source_video_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers.size() - start_index, source_video_buffers,
start_index, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
cache.StartResuming();
start_index = FindKeyFrameIndexBeforeTime(source_audio_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers.size() - start_index, source_audio_buffers,
start_index, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
start_index = FindKeyFrameIndexBeforeTime(source_video_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers.size() - start_index, source_video_buffers,
start_index, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
clear_time = base::TimeDelta::FromMilliseconds(kDurationPerFrame *
(kVideoKeyFrameFrequency + 1));
cache.ClearSegmentsBeforeMediaTime(clear_time);
cache.StartResuming();
start_index = FindKeyFrameIndexBeforeTime(source_audio_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers.size() - start_index, source_audio_buffers,
start_index, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
start_index = FindKeyFrameIndexBeforeTime(source_video_buffers, clear_time);
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers.size() - start_index, source_video_buffers,
start_index, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
}
TEST(DecoderBufferCacheTest, MultipleStreamInfos) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info_1 = GetAudioStreamInfo();
SbMediaVideoStreamInfo source_video_stream_info_1 = GetVideoStreamInfo();
SbMediaAudioStreamInfo source_audio_stream_info_2 = GetAudioStreamInfo();
source_audio_stream_info_2.samples_per_second *= 2;
SbMediaVideoStreamInfo source_video_stream_info_2 = GetVideoStreamInfo();
source_video_stream_info_2.frame_width *= 2;
source_video_stream_info_2.frame_height *= 2;
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
DecoderBufferCache::DecoderBuffers source_audio_buffers_1(
source_audio_buffers.begin(),
source_audio_buffers.begin() + kVideoKeyFrameFrequency);
DecoderBufferCache::DecoderBuffers source_video_buffers_1(
source_video_buffers.begin(),
source_video_buffers.begin() + kVideoKeyFrameFrequency);
DecoderBufferCache::DecoderBuffers source_audio_buffers_2(
source_audio_buffers.begin() + kVideoKeyFrameFrequency,
source_audio_buffers.end());
DecoderBufferCache::DecoderBuffers source_video_buffers_2(
source_video_buffers.begin() + kVideoKeyFrameFrequency,
source_video_buffers.end());
cache.AddBuffers(source_audio_buffers_1, source_audio_stream_info_1);
cache.AddBuffers(source_video_buffers_1, source_video_stream_info_1);
cache.AddBuffers(source_audio_buffers_2, source_audio_stream_info_2);
cache.AddBuffers(source_video_buffers_2, source_video_stream_info_2);
DecoderBufferCache::DecoderBuffers output_buffers;
SbMediaAudioStreamInfo output_audio_stream_info;
SbMediaVideoStreamInfo output_video_stream_info;
// The output buffers in one read call should have same configuration.
cache.ReadBuffers(&output_buffers, kMaxSamplesPerRead,
&output_audio_stream_info);
ASSERT_TRUE(source_audio_stream_info_1 == output_audio_stream_info);
ASSERT_EQ(output_buffers.size(), source_audio_buffers_1.size());
output_buffers.clear();
cache.ReadBuffers(&output_buffers, kMaxSamplesPerRead,
&output_video_stream_info);
ASSERT_TRUE(source_video_stream_info_1 == output_video_stream_info);
ASSERT_EQ(output_buffers.size(), source_video_buffers_1.size());
cache.StartResuming();
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers_1.size(), source_audio_buffers_1, 0,
source_audio_stream_info_1));
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers_1.size(), source_video_buffers_1, 0,
source_video_stream_info_1));
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers_2.size(), source_audio_buffers_2, 0,
source_audio_stream_info_2));
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers_2.size(), source_video_buffers_2, 0,
source_video_stream_info_2));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
base::TimeDelta clear_time = base::TimeDelta::FromMilliseconds(
kDurationPerFrame * (kVideoKeyFrameFrequency));
cache.ClearSegmentsBeforeMediaTime(clear_time);
cache.StartResuming();
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_audio_buffers_2.size(), source_audio_buffers_2, 0,
source_audio_stream_info_2));
ASSERT_NO_FATAL_FAILURE(ReadAndCompareOutputs(
&cache, source_video_buffers_2.size(), source_video_buffers_2, 0,
source_video_stream_info_2));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
}
TEST(DecoderBufferCacheTest, AudioOnly) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info = GetAudioStreamInfo();
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
cache.AddBuffers(source_audio_buffers, source_audio_stream_info);
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_audio_buffers.size(),
source_audio_buffers, 0, source_audio_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
}
TEST(DecoderBufferCacheTest, VideoOnly) {
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaVideoStreamInfo source_video_stream_info = GetVideoStreamInfo();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
cache.AddBuffers(source_video_buffers, source_video_stream_info);
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::AUDIO));
ASSERT_NO_FATAL_FAILURE(
ReadAndCompareOutputs(&cache, source_video_buffers.size(),
source_video_buffers, 0, source_video_stream_info));
ASSERT_FALSE(cache.HasMoreBuffers(DemuxerStream::VIDEO));
}
TEST(DecoderBufferCacheTest, StreamInfosDeepCopied) {
const char* kAudioMime = "audio/mp4; codecs=\"mp4a.40.2\"";
const char* kAudioSpecificConfig = "0000";
const char* kVideoMime = "video/mp4; codecs=\"avc1.42001E\"";
const char* kMaxVideoCapabilities = "width=1920; height=1080; framerate=15";
DecoderBufferAllocator allocator;
DecoderBufferCache cache;
SbMediaAudioStreamInfo source_audio_stream_info = GetAudioStreamInfo();
source_audio_stream_info.mime = kAudioMime;
source_audio_stream_info.audio_specific_config_size =
strlen(kAudioSpecificConfig);
source_audio_stream_info.audio_specific_config = kAudioSpecificConfig;
SbMediaVideoStreamInfo source_video_stream_info = GetVideoStreamInfo();
source_video_stream_info.mime = kVideoMime;
source_video_stream_info.max_video_capabilities = kMaxVideoCapabilities;
DecoderBufferCache::DecoderBuffers source_audio_buffers =
GetAudioSourceBuffers();
DecoderBufferCache::DecoderBuffers source_video_buffers =
GetVideoSourceBuffers();
cache.AddBuffers(source_audio_buffers, source_audio_stream_info);
cache.AddBuffers(source_video_buffers, source_video_stream_info);
DecoderBufferCache::DecoderBuffers output_buffers;
SbMediaAudioStreamInfo output_audio_stream_info;
SbMediaVideoStreamInfo output_video_stream_info;
output_buffers.clear();
cache.ReadBuffers(&output_buffers, kMaxSamplesPerRead,
&output_audio_stream_info);
ASSERT_TRUE(source_audio_stream_info == output_audio_stream_info);
ASSERT_NE(source_audio_stream_info.mime, output_audio_stream_info.mime);
ASSERT_NE(source_audio_stream_info.audio_specific_config,
output_audio_stream_info.audio_specific_config);
output_buffers.clear();
cache.ReadBuffers(&output_buffers, kMaxSamplesPerRead,
&output_video_stream_info);
ASSERT_TRUE(source_video_stream_info == output_video_stream_info);
ASSERT_NE(source_video_stream_info.mime, output_video_stream_info.mime);
ASSERT_NE(source_video_stream_info.max_video_capabilities,
output_video_stream_info.max_video_capabilities);
}
} // namespace media
} // namespace cobalt