blob: 8bb222e79e69da3273610fc9ac149e95e63624ea [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/download/download_media_parser.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/browser/android/download/local_media_data_source_factory.h"
#include "chrome/browser/android/download/video_frame_thumbnail_converter.h"
#include "content/public/browser/android/gpu_video_accelerator_factories_provider.h"
#include "content/public/common/service_manager_connection.h"
#include "media/base/overlay_info.h"
#include "media/mojo/clients/mojo_video_decoder.h"
#include "media/mojo/interfaces/constants.mojom.h"
#include "media/mojo/interfaces/media_service.mojom.h"
#include "media/mojo/services/media_interface_provider.h"
#include "media/video/gpu_video_accelerator_factories.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace {
// Returns if the mime type is video or audio.
bool IsSupportedMediaMimeType(const std::string& mime_type) {
return base::StartsWith(mime_type, "audio/",
base::CompareCase::INSENSITIVE_ASCII) ||
base::StartsWith(mime_type, "video/",
base::CompareCase::INSENSITIVE_ASCII);
}
void OnRequestOverlayInfo(bool decoder_requires_restart_for_overlay,
const media::ProvideOverlayInfoCB& overlay_info_cb) {
// No android overlay associated with video thumbnail.
overlay_info_cb.Run(media::OverlayInfo());
}
} // namespace
DownloadMediaParser::DownloadMediaParser(int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
ParseCompleteCB parse_complete_cb)
: size_(size),
mime_type_(mime_type),
file_path_(file_path),
parse_complete_cb_(std::move(parse_complete_cb)),
file_task_runner_(
base::CreateSingleThreadTaskRunnerWithTraits({base::MayBlock()})),
decode_done_(false),
weak_factory_(this) {}
DownloadMediaParser::~DownloadMediaParser() = default;
void DownloadMediaParser::Start() {
// Only process media mime types.
if (!IsSupportedMediaMimeType(mime_type_)) {
OnError();
return;
}
RetrieveMediaParser(
content::ServiceManagerConnection::GetForProcess()->GetConnector());
}
void DownloadMediaParser::OnMediaParserCreated() {
auto media_source_factory = std::make_unique<LocalMediaDataSourceFactory>(
file_path_, file_task_runner_);
chrome::mojom::MediaDataSourcePtr source_ptr;
media_data_source_ = media_source_factory->CreateMediaDataSource(
&source_ptr, base::BindRepeating(&DownloadMediaParser::OnMediaDataReady,
weak_factory_.GetWeakPtr()));
media_parser()->ParseMediaMetadata(
mime_type_, size_, true /* get_attached_images */, std::move(source_ptr),
base::BindOnce(&DownloadMediaParser::OnMediaMetadataParsed,
weak_factory_.GetWeakPtr()));
}
void DownloadMediaParser::OnConnectionError() {
OnError();
}
void DownloadMediaParser::OnMediaMetadataParsed(
bool parse_success,
chrome::mojom::MediaMetadataPtr metadata,
const std::vector<metadata::AttachedImage>& attached_images) {
if (!parse_success) {
OnError();
return;
}
metadata_ = std::move(metadata);
DCHECK(metadata_);
// TODO(xingliu): Make |attached_images| movable and use this as a thumbnail
// source as well as video frame.
attached_images_ = attached_images;
// For audio file, we only need metadata and poster.
if (base::StartsWith(mime_type_, "audio/",
base::CompareCase::INSENSITIVE_ASCII)) {
NotifyComplete(SkBitmap());
return;
}
DCHECK(base::StartsWith(mime_type_, "video/",
base::CompareCase::INSENSITIVE_ASCII));
// Start to retrieve video thumbnail.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DownloadMediaParser::RetrieveEncodedVideoFrame,
weak_factory_.GetWeakPtr()));
}
void DownloadMediaParser::RetrieveEncodedVideoFrame() {
media_data_source_.reset();
auto media_source_factory = std::make_unique<LocalMediaDataSourceFactory>(
file_path_, file_task_runner_);
chrome::mojom::MediaDataSourcePtr source_ptr;
media_data_source_ = media_source_factory->CreateMediaDataSource(
&source_ptr, base::BindRepeating(&DownloadMediaParser::OnMediaDataReady,
weak_factory_.GetWeakPtr()));
media_parser()->ExtractVideoFrame(
mime_type_, base::saturated_cast<uint32_t>(size_), std::move(source_ptr),
base::BindOnce(&DownloadMediaParser::OnEncodedVideoFrameRetrieved,
weak_factory_.GetWeakPtr()));
}
void DownloadMediaParser::OnEncodedVideoFrameRetrieved(
bool success,
const std::vector<uint8_t>& data,
const media::VideoDecoderConfig& config) {
if (data.empty()) {
OnError();
return;
}
encoded_data_ = data;
config_ = config;
content::CreateGpuVideoAcceleratorFactories(base::BindRepeating(
&DownloadMediaParser::OnGpuVideoAcceleratorFactoriesReady,
weak_factory_.GetWeakPtr()));
}
void DownloadMediaParser::OnGpuVideoAcceleratorFactoriesReady(
std::unique_ptr<media::GpuVideoAcceleratorFactories> factories) {
gpu_factories_ = std::move(factories);
DecodeVideoFrame();
}
void DownloadMediaParser::DecodeVideoFrame() {
media::mojom::VideoDecoderPtr video_decoder_ptr;
GetMediaInterfaceFactory()->CreateVideoDecoder(
mojo::MakeRequest(&video_decoder_ptr));
// Build and config the decoder.
DCHECK(gpu_factories_);
decoder_ = std::make_unique<media::MojoVideoDecoder>(
base::ThreadTaskRunnerHandle::Get(), gpu_factories_.get(), this,
std::move(video_decoder_ptr), base::BindRepeating(&OnRequestOverlayInfo),
gfx::ColorSpace());
decoder_->Initialize(
config_, false, nullptr,
base::BindRepeating(&DownloadMediaParser::OnVideoDecoderInitialized,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&DownloadMediaParser::OnVideoFrameDecoded,
weak_factory_.GetWeakPtr()),
base::RepeatingClosure());
}
void DownloadMediaParser::OnVideoDecoderInitialized(bool success) {
if (!success) {
OnError();
return;
}
// Build the video buffer to decode.
auto buffer =
media::DecoderBuffer::CopyFrom(&encoded_data_[0], encoded_data_.size());
encoded_data_.clear();
// Decode one frame buffer, followed by eos buffer.
DCHECK_GE(decoder_->GetMaxDecodeRequests(), 2);
decoder_->Decode(
buffer, base::BindRepeating(&DownloadMediaParser::OnVideoBufferDecoded,
weak_factory_.GetWeakPtr()));
decoder_->Decode(media::DecoderBuffer::CreateEOSBuffer(),
base::BindRepeating(&DownloadMediaParser::OnEosBufferDecoded,
weak_factory_.GetWeakPtr()));
}
void DownloadMediaParser::OnVideoBufferDecoded(media::DecodeStatus status) {
if (status != media::DecodeStatus::OK)
OnError();
}
void DownloadMediaParser::OnEosBufferDecoded(media::DecodeStatus status) {
if (status != media::DecodeStatus::OK)
OnError();
// Fails if no decoded video frame is generated when eos arrives.
if (!decode_done_)
OnError();
}
void DownloadMediaParser::OnVideoFrameDecoded(
const scoped_refptr<media::VideoFrame>& frame) {
DCHECK(frame);
DCHECK(frame->HasTextures());
decode_done_ = true;
RenderVideoFrame(frame);
}
void DownloadMediaParser::RenderVideoFrame(
const scoped_refptr<media::VideoFrame>& video_frame) {
auto converter = VideoFrameThumbnailConverter::Create(
config_.codec(), gpu_factories_->GetMediaContextProvider());
converter->ConvertToBitmap(
video_frame,
base::BindOnce(&DownloadMediaParser::OnBitmapGenerated,
weak_factory_.GetWeakPtr(), std::move(converter)));
}
void DownloadMediaParser::OnBitmapGenerated(
std::unique_ptr<VideoFrameThumbnailConverter>,
bool success,
SkBitmap bitmap) {
if (!success) {
OnError();
return;
}
NotifyComplete(std::move(bitmap));
}
media::mojom::InterfaceFactory*
DownloadMediaParser::GetMediaInterfaceFactory() {
if (!media_interface_factory_) {
service_manager::mojom::InterfaceProviderPtr interfaces;
media_interface_provider_ = std::make_unique<media::MediaInterfaceProvider>(
mojo::MakeRequest(&interfaces));
media::mojom::MediaServicePtr media_service;
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->BindInterface(media::mojom::kMediaServiceName, &media_service);
media_service->CreateInterfaceFactory(
MakeRequest(&media_interface_factory_), std::move(interfaces));
media_interface_factory_.set_connection_error_handler(
base::BindOnce(&DownloadMediaParser::OnDecoderConnectionError,
base::Unretained(this)));
}
return media_interface_factory_.get();
}
void DownloadMediaParser::OnDecoderConnectionError() {
OnError();
}
void DownloadMediaParser::OnMediaDataReady(
chrome::mojom::MediaDataSource::ReadCallback callback,
std::unique_ptr<std::string> data) {
// TODO(xingliu): Change media_parser.mojom to move the data instead of copy.
if (media_parser())
std::move(callback).Run(std::vector<uint8_t>(data->begin(), data->end()));
}
void DownloadMediaParser::NotifyComplete(SkBitmap bitmap) {
// TODO(xingliu): Return the metadata and video thumbnail data in
// |parse_complete_cb_|.
DCHECK(metadata_);
if (parse_complete_cb_)
std::move(parse_complete_cb_)
.Run(true, std::move(metadata_), std::move(bitmap));
}
void DownloadMediaParser::OnError() {
if (parse_complete_cb_)
std::move(parse_complete_cb_)
.Run(false, chrome::mojom::MediaMetadata::New(), SkBitmap());
}