blob: 518338f57ee0b446674e0f1df2777b8a0214be8e [file] [log] [blame]
// Copyright 2022 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/mime_util.h"
#include <cstring>
#include <string>
#include <vector>
#include "starboard/common/log.h"
#include "starboard/common/media.h"
#include "starboard/log.h"
#include "starboard/shared/starboard/media/bitrate_supportability_cache.h"
#include "starboard/shared/starboard/media/key_system_supportability_cache.h"
#include "starboard/shared/starboard/media/media_support_internal.h"
#include "starboard/shared/starboard/media/mime_supportability_cache.h"
#include "starboard/shared/starboard/media/mime_type.h"
#include "starboard/shared/starboard/media/parsed_mime_info.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace media {
namespace {
// RemoveAttributeFromMime() will return a new mime string with the specified
// attribute removed. If |attribute_string| is not null, the removed attribute
// string will be returned via |attribute_string|. Following are some examples:
// mime: "video/webm; codecs=\"vp9\"; bitrate=300000"
// attribute_name: "bitrate"
// return: "video/webm; codecs=\"vp9\""
// attribute_string: "bitrate=300000"
//
// mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709"
// attribute_name: "bitrate"
// return: "video/webm; codecs=\"vp9\"; eotf=bt709"
// attribute_string: "bitrate=300000"
//
// mime: "bitrate=300000"
// attribute_name: "bitrate"
// return: ""
// attribute_string: "bitrate=300000"
std::string RemoveAttributeFromMime(const char* mime,
const char* attribute_name,
std::string* attribute_string) {
size_t name_length = strlen(attribute_name);
if (name_length == 0) {
return mime;
}
std::string mime_without_attribute;
const char* start_pos = strstr(mime, attribute_name);
while (start_pos) {
if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) &&
(start_pos[name_length] &&
(start_pos[name_length] == '=' || isspace(start_pos[name_length])))) {
break;
}
start_pos += name_length;
start_pos = strstr(start_pos, attribute_name);
}
if (!start_pos) {
// Target attribute is not found.
return std::string(mime);
}
const char* end_pos = strstr(start_pos, ";");
if (end_pos) {
// There may be other attribute after target attribute.
if (attribute_string) {
// Returned |attribute_string| will not have a trailing ';'.
attribute_string->assign(start_pos, end_pos - start_pos);
}
end_pos++;
// Remove leading spaces.
while (*end_pos && isspace(*end_pos)) {
end_pos++;
}
if (*end_pos) {
// Append the string after target attribute.
mime_without_attribute = std::string(mime, start_pos - mime);
mime_without_attribute.append(end_pos);
} else {
// Target attribute is the last one. Remove trailing spaces.
size_t mime_length = start_pos - mime;
while (mime_length > 0 && (isspace(mime[mime_length - 1]))) {
mime_length--;
}
mime_without_attribute = std::string(mime, mime_length);
}
} else {
// It can't find a trailing ';'. The target attribute must be the last one.
size_t mime_length = start_pos - mime;
// Remove trailing spaces.
while (mime_length > 0 && (isspace(mime[mime_length - 1]))) {
mime_length--;
}
// Remove the trailing ';'.
if (mime_length > 0 && mime[mime_length - 1] == ';') {
mime_length--;
}
mime_without_attribute = std::string(mime, mime_length);
if (attribute_string) {
*attribute_string = std::string(start_pos);
}
}
return mime_without_attribute;
}
// Use SbMediaGetAudioConfiguration() to check if the platform can support
// |channels|.
bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) {
int count = SbMediaGetAudioOutputCount();
for (int output_index = 0; output_index < count; ++output_index) {
SbMediaAudioConfiguration configuration;
if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
continue;
}
if (configuration.coding_type == coding_type &&
configuration.number_of_channels >= channels) {
return true;
}
}
return false;
}
bool IsSupportedKeySystem(SbMediaAudioCodec codec, const char* key_system) {
SB_DCHECK(key_system);
// KeySystemSupportabilityCache() should always return supported for empty
// |key_system|, so here it should always be non empty.
SB_DCHECK(strlen(key_system) > 0);
return SbMediaIsSupported(kSbMediaVideoCodecNone, codec, key_system);
}
bool IsSupportedKeySystem(SbMediaVideoCodec codec, const char* key_system) {
SB_DCHECK(key_system);
// KeySystemSupportabilityCache() should always return supported for empty
// |key_system|, so here it should always be non empty.
SB_DCHECK(strlen(key_system) > 0);
return SbMediaIsSupported(codec, kSbMediaAudioCodecNone, key_system);
}
bool IsSupportedAudioCodec(const ParsedMimeInfo& mime_info) {
SB_DCHECK(mime_info.is_valid());
SB_DCHECK(mime_info.mime_type().is_valid());
SB_DCHECK(mime_info.has_audio_info());
const MimeType& mime_type = mime_info.mime_type();
const ParsedMimeInfo::AudioCodecInfo& audio_info = mime_info.audio_info();
switch (audio_info.codec) {
case kSbMediaAudioCodecNone:
SB_NOTREACHED();
return false;
case kSbMediaAudioCodecAac:
case kSbMediaAudioCodecAc3:
case kSbMediaAudioCodecEac3:
if (mime_type.subtype() != "mp4") {
return false;
}
break;
case kSbMediaAudioCodecOpus:
case kSbMediaAudioCodecVorbis:
if (mime_type.subtype() != "webm") {
return false;
}
break;
#if SB_API_VERSION >= 14
case kSbMediaAudioCodecMp3:
case kSbMediaAudioCodecFlac:
case kSbMediaAudioCodecPcm:
return false;
#endif // SB_API_VERSION >= 14
}
if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm,
audio_info.channels)) {
return false;
}
return SbMediaIsAudioSupported(audio_info.codec, &mime_type,
audio_info.bitrate);
}
bool IsSupportedVideoCodec(const ParsedMimeInfo& mime_info) {
SB_DCHECK(mime_info.is_valid());
SB_DCHECK(mime_info.mime_type().is_valid());
SB_DCHECK(mime_info.has_video_info());
const MimeType& mime_type = mime_info.mime_type();
const ParsedMimeInfo::VideoCodecInfo& video_info = mime_info.video_info();
switch (video_info.codec) {
case kSbMediaVideoCodecNone:
SB_NOTREACHED();
return false;
case kSbMediaVideoCodecH264:
case kSbMediaVideoCodecH265:
if (mime_type.subtype() != "mp4") {
return false;
}
break;
case kSbMediaVideoCodecMpeg2:
case kSbMediaVideoCodecTheora:
return false; // No associated container in YT.
case kSbMediaVideoCodecVc1:
case kSbMediaVideoCodecAv1:
if (mime_type.subtype() != "mp4") {
return false;
}
break;
case kSbMediaVideoCodecVp8:
if (mime_type.subtype() != "webm") {
return false;
}
break;
case kSbMediaVideoCodecVp9:
if (mime_type.subtype() != "mp4" && mime_type.subtype() != "webm") {
return false;
}
break;
}
std::string cryptoblockformat =
mime_type.GetParamStringValue("cryptoblockformat", "");
if (!cryptoblockformat.empty()) {
if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") {
return false;
}
}
return SbMediaIsVideoSupported(
video_info.codec, &mime_type, video_info.profile, video_info.level,
video_info.bit_depth, video_info.primary_id, video_info.transfer_id,
video_info.matrix_id, video_info.frame_width, video_info.frame_height,
video_info.bitrate, video_info.fps,
video_info.decode_to_texture_required);
}
bool ValidateAndParseBitrate(const std::string& bitrate_string, int* bitrate) {
SB_DCHECK(!bitrate_string.empty());
MimeType::Param param;
if (!MimeType::ParseParamString(bitrate_string, &param)) {
return false;
}
if (param.type != MimeType::kParamTypeInteger) {
return false;
}
if (bitrate) {
*bitrate = param.int_value;
}
return true;
}
} // namespace
SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime,
const char* key_system) {
SB_DCHECK(mime);
SB_DCHECK(key_system);
// Remove bitrate from mime string and read bitrate if presents.
std::string bitrate_string;
std::string mime_without_bitrate =
RemoveAttributeFromMime(mime, "bitrate", &bitrate_string);
int bitrate = 0;
if (!bitrate_string.empty()) {
if (!ValidateAndParseBitrate(bitrate_string, &bitrate)) {
return kSbMediaSupportTypeNotSupported;
}
}
if (bitrate < 0) {
// Reject invalid bitrate.
return kSbMediaSupportTypeNotSupported;
}
// Get cached parsed mime infos and supportability. If it is not found in the
// cache, MimeSupportabilityCache would parse the mime string and return a
// ParsedMimeInfo.
ParsedMimeInfo mime_info;
Supportability mime_supportability =
MimeSupportabilityCache::GetInstance()->GetMimeSupportability(
mime_without_bitrate, &mime_info);
// Overwrite the bitrate.
mime_info.SetBitrate(bitrate);
if (mime_info.disable_cache()) {
// Disable all caches if required.
mime_supportability = kSupportabilityUnknown;
MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false);
KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false);
BitrateSupportabilityCache::GetInstance()->SetCacheEnabled(false);
}
// Reject mime if cached result is not supported.
if (mime_supportability == kSupportabilityNotSupported) {
return kSbMediaSupportTypeNotSupported;
}
// Reject mime if parsed mime info is invalid.
if (!mime_info.is_valid()) {
return kSbMediaSupportTypeNotSupported;
}
const MimeType& mime_type = mime_info.mime_type();
const std::vector<std::string>& codecs = mime_type.GetCodecs();
// Quick check for mp4 format.
if (codecs.size() == 0) {
// This happens when the H5 player is either querying for progressive
// playback support, or probing for generic mp4 support without specific
// codecs.
if (mime_type.subtype() == "mp4") {
return kSbMediaSupportTypeMaybe;
} else {
return kSbMediaSupportTypeNotSupported;
}
}
// Reject mime if it doesn't have any valid codec info.
if (!mime_info.has_audio_info() && !mime_info.has_video_info()) {
return kSbMediaSupportTypeNotSupported;
}
// Get cached key system supportability. Note that we check if audio or video
// codec supports key system separately.
if (mime_info.has_audio_info()) {
Supportability key_system_supportability =
KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability(
mime_info.audio_info().codec, key_system);
if (key_system_supportability == kSupportabilityUnknown) {
key_system_supportability =
IsSupportedKeySystem(mime_info.audio_info().codec, key_system)
? kSupportabilitySupported
: kSupportabilityNotSupported;
KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability(
mime_info.audio_info().codec, key_system, key_system_supportability);
}
// Reject mime if audio codec doesn't support the key system.
if (key_system_supportability == kSupportabilityNotSupported) {
return kSbMediaSupportTypeNotSupported;
}
}
if (mime_info.has_video_info()) {
Supportability key_system_supportability =
KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability(
mime_info.video_info().codec, key_system);
if (key_system_supportability == kSupportabilityUnknown) {
key_system_supportability =
IsSupportedKeySystem(mime_info.video_info().codec, key_system)
? kSupportabilitySupported
: kSupportabilityNotSupported;
KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability(
mime_info.video_info().codec, key_system, key_system_supportability);
}
// Reject mime if video codec doesn't the key system.
if (key_system_supportability == kSupportabilityNotSupported) {
return kSbMediaSupportTypeNotSupported;
}
}
// Get cached bitrate supportability.
Supportability bitrate_supportability =
BitrateSupportabilityCache::GetInstance()->GetBitrateSupportability(
mime_info);
// Reject mime if bitrate is not supported.
if (bitrate_supportability == kSupportabilityNotSupported) {
return kSbMediaSupportTypeNotSupported;
}
// Return supported if mime and bitrate are all supported.
if (mime_supportability == kSupportabilitySupported &&
bitrate_supportability == kSupportabilitySupported) {
return kSbMediaSupportTypeProbably;
}
// At this point, either mime or bitrate supportability must be unknown.
// Call platform functions to check if they are supported.
SB_DCHECK(mime_supportability == kSupportabilityUnknown ||
bitrate_supportability == kSupportabilityUnknown);
if (mime_info.has_audio_info() && !IsSupportedAudioCodec(mime_info)) {
mime_supportability = kSupportabilityNotSupported;
} else if (mime_info.has_video_info() && !IsSupportedVideoCodec(mime_info)) {
mime_supportability = kSupportabilityNotSupported;
} else {
mime_supportability = kSupportabilitySupported;
}
// Cache mime supportability when bitrate supportability is known.
if (bitrate_supportability == kSupportabilitySupported) {
MimeSupportabilityCache::GetInstance()->CacheMimeSupportability(
mime_without_bitrate, mime_supportability);
}
SB_DCHECK(mime_supportability != kSupportabilityUnknown);
return mime_supportability == kSupportabilitySupported
? kSbMediaSupportTypeProbably
: kSbMediaSupportTypeNotSupported;
}
} // namespace media
} // namespace starboard
} // namespace shared
} // namespace starboard