blob: 97310038e7bfc39fcb47ec5c434a60336ff04e38 [file] [log] [blame]
// Copyright 2017 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/media/media_util.h"
#include <algorithm>
#include <cctype>
#include "starboard/character.h"
#include "starboard/common/log.h"
#include "starboard/common/media.h"
#include "starboard/common/string.h"
#include "starboard/log.h"
#include "starboard/shared/starboard/media/codec_util.h"
#include "starboard/shared/starboard/media/mime_type.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace media {
namespace {
const int64_t kDefaultBitRate = 0;
const int64_t kDefaultAudioChannels = 2;
template <typename StreamInfo>
void Assign(const StreamInfo& source, AudioStreamInfo* dest) {
SB_DCHECK(dest);
if (source.audio_specific_config_size > 0) {
SB_DCHECK(source.audio_specific_config);
}
dest->codec = source.codec;
if (dest->codec == kSbMediaAudioCodecNone) {
dest->mime.clear();
dest->audio_specific_config.clear();
return;
}
SB_DCHECK(source.mime);
dest->mime = source.mime;
dest->number_of_channels = source.number_of_channels;
dest->samples_per_second = source.samples_per_second;
dest->bits_per_sample = source.bits_per_sample;
auto config = static_cast<const uint8_t*>(source.audio_specific_config);
dest->audio_specific_config.assign(
config, config + source.audio_specific_config_size);
}
template <typename StreamInfo>
void Assign(const StreamInfo& source, VideoStreamInfo* dest) {
SB_DCHECK(dest);
dest->codec = source.codec;
if (dest->codec == kSbMediaVideoCodecNone) {
dest->mime.clear();
dest->max_video_capabilities.clear();
return;
}
SB_DCHECK(source.mime);
SB_DCHECK(source.max_video_capabilities);
dest->mime = source.mime;
dest->max_video_capabilities = source.max_video_capabilities;
dest->frame_width = source.frame_width;
dest->frame_height = source.frame_height;
dest->color_metadata = source.color_metadata;
}
template <typename StreamInfo>
void Assign(const AudioStreamInfo& source, StreamInfo* dest) {
SB_DCHECK(dest);
*dest = {};
dest->codec = source.codec;
dest->mime = source.mime.c_str();
dest->number_of_channels = source.number_of_channels;
dest->samples_per_second = source.samples_per_second;
dest->bits_per_sample = source.bits_per_sample;
if (!source.audio_specific_config.empty()) {
dest->audio_specific_config = source.audio_specific_config.data();
dest->audio_specific_config_size =
static_cast<uint16_t>(source.audio_specific_config.size());
}
}
template <typename StreamInfo>
void Assign(const VideoStreamInfo& source, StreamInfo* dest) {
SB_DCHECK(dest);
*dest = {};
dest->codec = source.codec;
dest->mime = source.mime.c_str();
dest->max_video_capabilities = source.max_video_capabilities.c_str();
dest->frame_width = source.frame_width;
dest->frame_height = source.frame_height;
dest->color_metadata = source.color_metadata;
}
} // namespace
AudioStreamInfo& AudioStreamInfo::operator=(
const SbMediaAudioStreamInfo& that) {
Assign(that, this);
return *this;
}
AudioStreamInfo& AudioStreamInfo::operator=(
const CobaltExtensionEnhancedAudioMediaAudioStreamInfo& that) {
Assign(that, this);
return *this;
}
void AudioStreamInfo::ConvertTo(
SbMediaAudioStreamInfo* audio_stream_info) const {
Assign(*this, audio_stream_info);
#if SB_API_VERSION < SB_MEDIA_ENHANCED_AUDIO_API_VERSION
SB_DCHECK(audio_stream_info);
audio_stream_info->format_tag = 0xff;
audio_stream_info->block_alignment = 4;
audio_stream_info->average_bytes_per_second =
audio_stream_info->samples_per_second *
audio_stream_info->number_of_channels *
audio_stream_info->bits_per_sample / 8;
#endif // SB_API_VERSION < SB_MEDIA_ENHANCED_AUDIO_API_VERSION
}
void AudioStreamInfo::ConvertTo(
CobaltExtensionEnhancedAudioMediaAudioStreamInfo* audio_stream_info) const {
Assign(*this, audio_stream_info);
}
bool operator==(const AudioStreamInfo& left, const AudioStreamInfo& right) {
if (left.codec == kSbMediaAudioCodecNone &&
right.codec == kSbMediaAudioCodecNone) {
return true;
}
return left.codec == right.codec && left.mime == right.mime &&
left.number_of_channels == right.number_of_channels &&
left.samples_per_second == right.samples_per_second &&
left.bits_per_sample == right.bits_per_sample &&
left.audio_specific_config == right.audio_specific_config;
}
bool operator!=(const AudioStreamInfo& left, const AudioStreamInfo& right) {
return !(left == right);
}
AudioSampleInfo& AudioSampleInfo::operator=(
const SbMediaAudioSampleInfo& that) {
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info = that.stream_info;
discarded_duration_from_front = that.discarded_duration_from_front;
discarded_duration_from_back = that.discarded_duration_from_back;
#else // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info = that;
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
return *this;
}
AudioSampleInfo& AudioSampleInfo::operator=(
const CobaltExtensionEnhancedAudioMediaAudioSampleInfo& that) {
stream_info = that.stream_info;
discarded_duration_from_front = that.discarded_duration_from_front;
discarded_duration_from_back = that.discarded_duration_from_back;
return *this;
}
void AudioSampleInfo::ConvertTo(
SbMediaAudioSampleInfo* audio_sample_info) const {
SB_DCHECK(audio_sample_info);
*audio_sample_info = {};
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info.ConvertTo(&audio_sample_info->stream_info);
audio_sample_info->discarded_duration_from_front =
discarded_duration_from_front;
audio_sample_info->discarded_duration_from_back =
discarded_duration_from_back;
#else // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info.ConvertTo(audio_sample_info);
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
}
void AudioSampleInfo::ConvertTo(
CobaltExtensionEnhancedAudioMediaAudioSampleInfo* audio_sample_info) const {
SB_DCHECK(audio_sample_info);
*audio_sample_info = {};
stream_info.ConvertTo(&audio_sample_info->stream_info);
audio_sample_info->discarded_duration_from_front =
discarded_duration_from_front;
audio_sample_info->discarded_duration_from_back =
discarded_duration_from_back;
}
VideoStreamInfo& VideoStreamInfo::operator=(
const SbMediaVideoStreamInfo& that) {
Assign(that, this);
return *this;
}
VideoStreamInfo& VideoStreamInfo::operator=(
const CobaltExtensionEnhancedAudioMediaVideoStreamInfo& that) {
Assign(that, this);
return *this;
}
void VideoStreamInfo::ConvertTo(
SbMediaVideoStreamInfo* video_stream_info) const {
Assign(*this, video_stream_info);
}
void VideoStreamInfo::ConvertTo(
CobaltExtensionEnhancedAudioMediaVideoStreamInfo* video_stream_info) const {
Assign(*this, video_stream_info);
}
bool operator==(const VideoStreamInfo& left, const VideoStreamInfo& right) {
if (left.codec == kSbMediaVideoCodecNone &&
right.codec == kSbMediaVideoCodecNone) {
return true;
}
return left.codec == right.codec && left.mime == right.mime &&
left.max_video_capabilities == right.max_video_capabilities &&
left.frame_width == right.frame_width &&
left.frame_height == right.frame_height &&
left.color_metadata == right.color_metadata;
}
bool operator!=(const VideoStreamInfo& left, const VideoStreamInfo& right) {
return !(left == right);
}
VideoSampleInfo& VideoSampleInfo::operator=(
const SbMediaVideoSampleInfo& that) {
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info = that.stream_info;
#else // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info = that;
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
is_key_frame = that.is_key_frame;
return *this;
}
VideoSampleInfo& VideoSampleInfo::operator=(
const CobaltExtensionEnhancedAudioMediaVideoSampleInfo& that) {
stream_info = that.stream_info;
is_key_frame = that.is_key_frame;
return *this;
}
void VideoSampleInfo::ConvertTo(
SbMediaVideoSampleInfo* video_sample_info) const {
SB_DCHECK(video_sample_info);
*video_sample_info = {};
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info.ConvertTo(&video_sample_info->stream_info);
#else // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
stream_info.ConvertTo(video_sample_info);
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
video_sample_info->is_key_frame = is_key_frame;
}
void VideoSampleInfo::ConvertTo(
CobaltExtensionEnhancedAudioMediaVideoSampleInfo* video_sample_info) const {
SB_DCHECK(video_sample_info);
*video_sample_info = {};
stream_info.ConvertTo(&video_sample_info->stream_info);
video_sample_info->is_key_frame = is_key_frame;
}
std::ostream& operator<<(std::ostream& os, const VideoSampleInfo& sample_info) {
const auto& stream_info = sample_info.stream_info;
if (stream_info.codec == kSbMediaVideoCodecNone) {
return os << "codec: " << GetMediaVideoCodecName(stream_info.codec);
}
os << "codec: " << GetMediaVideoCodecName(stream_info.codec) << ", ";
os << "mime: " << stream_info.mime
<< ", max video capabilities: " << stream_info.max_video_capabilities
<< ", ";
if (sample_info.is_key_frame) {
os << "key frame, ";
}
os << stream_info.frame_width << 'x' << stream_info.frame_height << ' ';
os << '(' << stream_info.color_metadata << ')';
return os;
}
bool IsSDRVideo(int bit_depth,
SbMediaPrimaryId primary_id,
SbMediaTransferId transfer_id,
SbMediaMatrixId matrix_id) {
if (bit_depth != 8) {
return false;
}
if (primary_id != kSbMediaPrimaryIdBt709 &&
primary_id != kSbMediaPrimaryIdUnspecified &&
primary_id != kSbMediaPrimaryIdSmpte170M) {
return false;
}
if (transfer_id != kSbMediaTransferIdBt709 &&
transfer_id != kSbMediaTransferIdUnspecified &&
transfer_id != kSbMediaTransferIdSmpte170M) {
return false;
}
if (matrix_id != kSbMediaMatrixIdBt709 &&
matrix_id != kSbMediaMatrixIdUnspecified &&
matrix_id != kSbMediaMatrixIdSmpte170M) {
return false;
}
return true;
}
bool IsSDRVideo(const char* mime) {
SB_DCHECK(mime);
if (!mime) {
SB_LOG(WARNING) << mime << " is empty, assuming sdr video.";
return true;
}
MimeType mime_type(mime);
if (!mime_type.is_valid()) {
SB_LOG(WARNING) << mime << " is not a valid mime type, assuming sdr video.";
return true;
}
const std::vector<std::string> codecs = mime_type.GetCodecs();
if (codecs.empty()) {
SB_LOG(WARNING) << mime << " contains no codecs, assuming sdr video.";
return true;
}
if (codecs.size() > 1) {
SB_LOG(WARNING) << mime
<< " contains more than one codecs, assuming sdr video.";
return true;
}
SbMediaVideoCodec video_codec;
int profile = -1;
int level = -1;
int bit_depth = 8;
SbMediaPrimaryId primary_id = kSbMediaPrimaryIdUnspecified;
SbMediaTransferId transfer_id = kSbMediaTransferIdUnspecified;
SbMediaMatrixId matrix_id = kSbMediaMatrixIdUnspecified;
if (!ParseVideoCodec(codecs[0].c_str(), &video_codec, &profile, &level,
&bit_depth, &primary_id, &transfer_id, &matrix_id)) {
SB_LOG(WARNING) << "ParseVideoCodec() failed on mime: " << mime
<< ", assuming sdr video.";
return true;
}
SB_DCHECK(video_codec != kSbMediaVideoCodecNone);
// TODO: Consider to consolidate the two IsSDRVideo() implementations by
// calling IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id).
return bit_depth == 8;
}
int GetBytesPerSample(SbMediaAudioSampleType sample_type) {
switch (sample_type) {
case kSbMediaAudioSampleTypeInt16Deprecated:
return 2;
case kSbMediaAudioSampleTypeFloat32:
return 4;
}
SB_NOTREACHED();
return 4;
}
std::string GetStringRepresentation(const uint8_t* data, const int size) {
std::string result;
for (int i = 0; i < size; ++i) {
if (std::isspace(data[i])) {
result += ' ';
} else if (std::isprint(data[i])) {
result += data[i];
} else {
result += '?';
}
}
return result;
}
std::string GetMixedRepresentation(const uint8_t* data,
const int size,
const int bytes_per_line) {
std::string result;
for (int i = 0; i < size; i += bytes_per_line) {
if (i + bytes_per_line <= size) {
result += HexEncode(data + i, bytes_per_line);
result += " | ";
result += GetStringRepresentation(data + i, bytes_per_line);
result += '\n';
} else {
int bytes_left = size - i;
result += HexEncode(data + i, bytes_left);
result += std::string((bytes_per_line - bytes_left) * 2, ' ');
result += " | ";
result += GetStringRepresentation(data + i, bytes_left);
result += std::string(bytes_per_line - bytes_left, ' ');
result += '\n';
}
}
return result;
}
bool IsAudioSampleInfoSubstantiallyDifferent(const AudioStreamInfo& left,
const AudioStreamInfo& right) {
return left.codec != right.codec ||
left.samples_per_second != right.samples_per_second ||
left.number_of_channels != right.number_of_channels ||
left.audio_specific_config != right.audio_specific_config;
}
int AudioDurationToFrames(SbTime duration, int samples_per_second) {
SB_DCHECK(samples_per_second > 0)
<< "samples_per_second has to be greater than 0";
// The same as `frames = (duration / kSbTimeSecond) * samples_per_second`,
// switch order to avoid precision loss due to integer division.
return duration * samples_per_second / kSbTimeSecond;
}
SbTime AudioFramesToDuration(int frames, int samples_per_second) {
SB_DCHECK(samples_per_second > 0)
<< "samples_per_second has to be greater than 0";
return frames * kSbTimeSecond / std::max(samples_per_second, 1);
}
} // namespace media
} // namespace starboard
} // namespace shared
} // namespace starboard
bool operator==(const SbMediaColorMetadata& metadata_1,
const SbMediaColorMetadata& metadata_2) {
return memcmp(&metadata_1, &metadata_2, sizeof(SbMediaColorMetadata)) == 0;
}
bool operator==(const SbMediaVideoSampleInfo& sample_info_1,
const SbMediaVideoSampleInfo& sample_info_2) {
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
const SbMediaVideoStreamInfo& stream_info_1 = sample_info_1.stream_info;
const SbMediaVideoStreamInfo& stream_info_2 = sample_info_2.stream_info;
#else // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
const SbMediaVideoStreamInfo& stream_info_1 = sample_info_1;
const SbMediaVideoStreamInfo& stream_info_2 = sample_info_2;
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
if (stream_info_1.codec != stream_info_2.codec) {
return false;
}
if (stream_info_1.codec == kSbMediaVideoCodecNone) {
return true;
}
if (strcmp(stream_info_1.mime, stream_info_2.mime) != 0) {
return false;
}
if (strcmp(stream_info_1.max_video_capabilities,
stream_info_2.max_video_capabilities) != 0) {
return false;
}
if (sample_info_1.is_key_frame != sample_info_2.is_key_frame) {
return false;
}
if (stream_info_1.frame_width != stream_info_2.frame_width) {
return false;
}
if (stream_info_1.frame_height != stream_info_2.frame_height) {
return false;
}
return stream_info_1.color_metadata == stream_info_2.color_metadata;
}
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
bool operator==(const SbMediaVideoStreamInfo& stream_info_1,
const SbMediaVideoStreamInfo& stream_info_2) {
if (stream_info_1.codec != stream_info_2.codec) {
return false;
}
if (stream_info_1.codec == kSbMediaVideoCodecNone) {
return true;
}
if (strcmp(stream_info_1.mime, stream_info_2.mime) != 0) {
return false;
}
if (strcmp(stream_info_1.max_video_capabilities,
stream_info_2.max_video_capabilities) != 0) {
return false;
}
if (stream_info_1.frame_width != stream_info_2.frame_width) {
return false;
}
if (stream_info_1.frame_height != stream_info_2.frame_height) {
return false;
}
return stream_info_1.color_metadata == stream_info_2.color_metadata;
}
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
bool operator!=(const SbMediaColorMetadata& metadata_1,
const SbMediaColorMetadata& metadata_2) {
return !(metadata_1 == metadata_2);
}
bool operator!=(const SbMediaVideoSampleInfo& sample_info_1,
const SbMediaVideoSampleInfo& sample_info_2) {
return !(sample_info_1 == sample_info_2);
}
#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
bool operator!=(const SbMediaVideoStreamInfo& stream_info_1,
const SbMediaVideoStreamInfo& stream_info_2) {
return !(stream_info_1 == stream_info_2);
}
#endif // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION