blob: 9d6c3f2de19c7f0bbf5344ac5f24f5932e3fbaa6 [file] [log] [blame]
// Copyright 2018 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/sandbox/format_guesstimator.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/path_service.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "cobalt/math/size.h"
#include "cobalt/media/base/audio_codecs.h"
#include "cobalt/media/base/audio_decoder_config.h"
#include "cobalt/media/base/demuxer_stream.h"
#include "cobalt/media/base/media_tracks.h"
#include "cobalt/media/base/video_codecs.h"
#include "cobalt/media/base/video_decoder_config.h"
#include "cobalt/media/filters/chunk_demuxer.h"
#include "cobalt/media/sandbox/web_media_player_helper.h"
#include "cobalt/render_tree/image.h"
#include "net/base/filename_util.h"
#include "net/base/url_util.h"
#include "starboard/common/string.h"
#include "starboard/file.h"
#include "starboard/memory.h"
#include "starboard/types.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
// The possible mime type configurations that are supported by cobalt.
const std::vector<std::string> kSupportedMimeTypes = {
"audio/mp4; codecs=\"ac-3\"",
"audio/mp4; codecs=\"ec-3\"",
"audio/mp4; codecs=\"mp4a.40.2\"",
"audio/webm; codecs=\"opus\"",
"video/mp4; codecs=\"av01.0.05M.08\"",
"video/mp4; codecs=\"avc1.640028, mp4a.40.2\"",
"video/mp4; codecs=\"avc1.640028\"",
"video/mp4; codecs=\"hvc1.1.6.H150.90\"",
"video/webm; codecs=\"vp9\"",
};
// Can be called as:
// IsFormat("https://example.com/audio.mp4", ".mp4")
// IsFormat("cobalt/demos/video.webm", ".webm")
bool IsFormat(const std::string& path_or_url, const std::string& format) {
DCHECK(!format.empty() && format[0] == '.') << "Invalid format: " << format;
return path_or_url.size() > format.size() &&
path_or_url.substr(path_or_url.size() - format.size()) == format;
}
base::FilePath ResolvePath(const std::string& path) {
base::FilePath result(path);
if (!result.IsAbsolute()) {
base::FilePath content_path;
base::PathService::Get(base::DIR_TEST_DATA, &content_path);
result = content_path.Append(result);
}
if (SbFileCanOpen(result.value().c_str(), kSbFileOpenOnly | kSbFileRead)) {
return result;
}
return base::FilePath();
}
// Read the first 256kb of the local file specified by |path|. If the size
// of the file is less than 256kb, the function reads all of its content.
std::vector<uint8_t> ReadHeader(const base::FilePath& path) {
// Size of the input file to be read into memory for checking the validity
// of ChunkDemuxer::AppendData() calls.
const int64_t kHeaderSize = 256 * 1024; // 256kb
starboard::ScopedFile file(path.value().c_str(),
kSbFileOpenOnly | kSbFileRead);
int64_t bytes_to_read = std::min(kHeaderSize, file.GetSize());
DCHECK_GE(bytes_to_read, 0);
std::vector<uint8_t> buffer(bytes_to_read);
auto bytes_read =
file.Read(reinterpret_cast<char*>(buffer.data()), bytes_to_read);
DCHECK_EQ(bytes_to_read, bytes_read);
return buffer;
}
void OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
}
// Extract the value of "codecs" parameter from content type. It will return
// "avc1.42E01E" for "video/mp4; codecs="avc1.42E01E".
// Note that this function assumes that the input is always valid and does
// minimum validation..
std::string ExtractCodec(const std::string& content_type) {
static const char kCodecs[] = "codecs=";
// SplitString will also trim the results.
std::vector<std::string> tokens = ::base::SplitString(
content_type, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (size_t i = 1; i < tokens.size(); ++i) {
if (base::strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
continue;
}
auto codec = tokens[i].substr(strlen("codecs="));
base::TrimString(codec, " \"", &codec);
return codec;
}
NOTREACHED();
return "";
}
} // namespace
FormatGuesstimator::FormatGuesstimator(const std::string& path_or_url,
MediaModule* media_module) {
GURL url(path_or_url);
if (url.is_valid()) {
// If it is a url, assume that it is a progressive video.
InitializeAsProgressive(url);
return;
}
base::FilePath path = ResolvePath(path_or_url);
if (path.empty() || !SbFileCanOpen(path.value().c_str(), kSbFileRead)) {
return;
}
InitializeAsAdaptive(path, media_module);
}
void FormatGuesstimator::InitializeAsProgressive(const GURL& url) {
// Mp4 is the only supported progressive format.
if (!IsFormat(url.spec(), ".mp4")) {
return;
}
progressive_url_ = url;
mime_type_ = "video/mp4; codecs=\"avc1.640028, mp4a.40.2\"";
}
void FormatGuesstimator::InitializeAsAdaptive(const base::FilePath& path,
MediaModule* media_module) {
std::vector<uint8_t> header = ReadHeader(path);
for (const auto& expected_supported_mime_type : kSupportedMimeTypes) {
DCHECK(mime_type_.empty());
ChunkDemuxer* chunk_demuxer = NULL;
WebMediaPlayerHelper::ChunkDemuxerOpenCB open_cb = base::Bind(
[](ChunkDemuxer** handle, ChunkDemuxer* chunk_demuxer) -> void {
*handle = chunk_demuxer;
},
&chunk_demuxer);
// We create a new |web_media_player_helper| every iteration in order to
// obtain a handle to a new |ChunkDemuxer| without any accumulated state as
// a result of previous calls to |AddId| and |AppendData| methods.
WebMediaPlayerHelper web_media_player_helper(media_module, open_cb,
math::Size(1920, 1080));
// |chunk_demuxer| will be set when |open_cb| is called asynchronously
// during initialization of |web_media_player_helper|. Wait until it is set
// before proceeding.
while (!chunk_demuxer) {
base::RunLoop().RunUntilIdle();
}
const std::string id = "stream";
if (chunk_demuxer->AddId(id, expected_supported_mime_type) !=
ChunkDemuxer::kOk) {
continue;
}
chunk_demuxer->SetTracksWatcher(id, base::Bind(OnInitSegmentReceived));
base::TimeDelta unused_timestamp;
if (!chunk_demuxer->AppendData(id, header.data(), header.size(),
base::TimeDelta(), media::kInfiniteDuration,
&unused_timestamp)) {
// Failing to |AppendData()| means the chosen format is not the file's
// true format.
continue;
}
// Succeeding |AppendData()| may be a false positive (i.e. the expected
// configuration does not match with the configuration determined by the
// ChunkDemuxer). To confirm, we check the decoder configuration determined
// by the ChunkDemuxer against the chosen format.
if (auto demuxer_stream =
chunk_demuxer->GetStream(DemuxerStream::Type::AUDIO)) {
const AudioDecoderConfig& decoder_config =
demuxer_stream->audio_decoder_config();
if (StringToAudioCodec(ExtractCodec(expected_supported_mime_type)) ==
decoder_config.codec()) {
adaptive_path_ = path;
mime_type_ = expected_supported_mime_type;
break;
}
continue;
}
auto demuxer_stream = chunk_demuxer->GetStream(DemuxerStream::Type::VIDEO);
DCHECK(demuxer_stream);
const VideoDecoderConfig& decoder_config =
demuxer_stream->video_decoder_config();
if (StringToVideoCodec(ExtractCodec(expected_supported_mime_type)) ==
decoder_config.codec()) {
adaptive_path_ = path;
mime_type_ = expected_supported_mime_type;
break;
}
}
}
} // namespace sandbox
} // namespace media
} // namespace cobalt