Commit 4679a2dc authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Android video thumbnail: Generate thumbnails for vp8, vp9 videos.

This CL does the following:

1. Use VpxVideoDecoder in utility process to do software decoding for
vp8, vp9 video files.

2. Simplify the class to render media::VideoFrame into bitmap, since
PaintCanvasVideoRenderer works for all kind of VideoFrames.

3. Do not retrieve poster image from metadata. The mime type of the
poster image is inferred from mime sniffer, and needs to be
processed in sandbox process.


Bug: 826021
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I91c9cf72a75be4bc3a95203a40633647e079a40a
Reviewed-on: https://chromium-review.googlesource.com/1234859
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#595523}
parent 888b9a2f
...@@ -728,6 +728,8 @@ android_library("chrome_test_java") { ...@@ -728,6 +728,8 @@ android_library("chrome_test_java") {
"//content/test/data/media/video-player.html", "//content/test/data/media/video-player.html",
"//content/test/data/media/webrtc_test_utilities.js", "//content/test/data/media/webrtc_test_utilities.js",
"//media/test/data/bear.mp4", "//media/test/data/bear.mp4",
"//media/test/data/bear-vp8-webvtt.webm",
"//media/test/data/bear-vp8a.webm",
"//media/test/data/sfx.mp3", "//media/test/data/sfx.mp3",
] ]
} }
......
...@@ -44,7 +44,7 @@ public class DownloadMediaParserTest { ...@@ -44,7 +44,7 @@ public class DownloadMediaParserTest {
/** /**
* Wraps result from download media parser. * Wraps result from download media parser.
*/ */
public static class MediaParseResult { public static class MediaParserResult {
public boolean done; public boolean done;
public DownloadMediaData mediaData; public DownloadMediaData mediaData;
} }
...@@ -54,21 +54,16 @@ public class DownloadMediaParserTest { ...@@ -54,21 +54,16 @@ public class DownloadMediaParserTest {
mTestRule.loadNativeLibraryAndInitBrowserProcess(); mTestRule.loadNativeLibraryAndInitBrowserProcess();
} }
@Test private MediaParserResult parseMediaFile(String filePath, String mimeType) {
@LargeTest File mediaFile = new File(filePath);
@Feature({"Download"}) Assert.assertTrue(mediaFile.exists());
@RetryOnFailure
public void testParseAudioMetatadata() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/sfx.mp3";
File audioFile = new File(filePath);
Assert.assertTrue(audioFile.exists());
boolean done = false; boolean done = false;
MediaParseResult result = new MediaParseResult(); MediaParserResult result = new MediaParserResult();
// The native DownloadMediaParser needs to be created on UI thread. // The native DownloadMediaParser needs to be created on UI thread.
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
DownloadMediaParserBridge parser = new DownloadMediaParserBridge( DownloadMediaParserBridge parser = new DownloadMediaParserBridge(
"audio/mp3", filePath, audioFile.length(), (DownloadMediaData mediaData) -> { mimeType, filePath, mediaFile.length(), (DownloadMediaData mediaData) -> {
result.mediaData = mediaData; result.mediaData = mediaData;
result.done = true; result.done = true;
}); });
...@@ -81,7 +76,20 @@ public class DownloadMediaParserTest { ...@@ -81,7 +76,20 @@ public class DownloadMediaParserTest {
return result.done; return result.done;
} }
}, MAX_MEDIA_PARSER_POLL_TIME_MS, MEDIA_PARSER_POLL_INTERVAL_MS); }, MAX_MEDIA_PARSER_POLL_TIME_MS, MEDIA_PARSER_POLL_INTERVAL_MS);
return result;
}
@Test
@LargeTest
@Feature({"Download"})
@RetryOnFailure
/**
* Verify that the metadata from audio file can be retrieved correctly.
* @throws InterruptedException
*/
public void testParseAudioMetatadata() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/sfx.mp3";
MediaParserResult result = parseMediaFile(filePath, "audio/mp3");
Assert.assertTrue("Failed to parse audio metadata.", result.mediaData != null); Assert.assertTrue("Failed to parse audio metadata.", result.mediaData != null);
} }
...@@ -89,30 +97,50 @@ public class DownloadMediaParserTest { ...@@ -89,30 +97,50 @@ public class DownloadMediaParserTest {
@LargeTest @LargeTest
@Feature({"Download"}) @Feature({"Download"})
@RetryOnFailure @RetryOnFailure
public void testParseVideoMetatadataThumbnail() throws InterruptedException { /**
* Verify metadata and thumbnail can be retrieved correctly from h264 video file.
* @throws InterruptedException
*/
public void testParseVideoH264() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/bear.mp4"; String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/bear.mp4";
File videoFile = new File(filePath); MediaParserResult result = parseMediaFile(filePath, "video/mp4");
Assert.assertTrue(videoFile.exists()); Assert.assertTrue("Failed to parse video file.", result.mediaData != null);
boolean done = false; Assert.assertTrue(
MediaParseResult result = new MediaParseResult(); "Failed to retrieve thumbnail.", result.mediaData.thumbnail.getWidth() > 0);
Assert.assertTrue(
// The native DownloadMediaParser needs to be created on UI thread. "Failed to retrieve thumbnail.", result.mediaData.thumbnail.getHeight() > 0);
ThreadUtils.runOnUiThreadBlocking(() -> { }
DownloadMediaParserBridge parser = new DownloadMediaParserBridge(
"video/mp4", filePath, videoFile.length(), (DownloadMediaData mediaData) -> {
result.mediaData = mediaData;
result.done = true;
});
parser.start();
});
CriteriaHelper.pollUiThread(new Criteria() { @Test
@Override @LargeTest
public boolean isSatisfied() { @Feature({"Download"})
return result.done; @RetryOnFailure
} /**
}, MAX_MEDIA_PARSER_POLL_TIME_MS, MEDIA_PARSER_POLL_INTERVAL_MS); * Verify metadata and thumbnail can be retrieved correctly from vp8 video file.
* @throws InterruptedException
*/
public void testParseVideoThumbnailVp8() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/bear-vp8-webvtt.webm";
MediaParserResult result = parseMediaFile(filePath, "video/webm");
Assert.assertTrue("Failed to parse video file.", result.mediaData != null);
Assert.assertTrue(
"Failed to retrieve thumbnail.", result.mediaData.thumbnail.getWidth() > 0);
Assert.assertTrue(
"Failed to retrieve thumbnail.", result.mediaData.thumbnail.getHeight() > 0);
}
@Test
@LargeTest
@Feature({"Download"})
@RetryOnFailure
/**
* Verify metadata and thumbnail can be retrieved correctly from vp8 video file with alpha
* plane.
* @throws InterruptedException
*/
public void testParseVideoThumbnailVp8WithAlphaPlane() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/bear-vp8a.webm";
MediaParserResult result = parseMediaFile(filePath, "video/webm");
Assert.assertTrue("Failed to parse video file.", result.mediaData != null); Assert.assertTrue("Failed to parse video file.", result.mediaData != null);
Assert.assertTrue( Assert.assertTrue(
"Failed to retrieve thumbnail.", result.mediaData.thumbnail.getWidth() > 0); "Failed to retrieve thumbnail.", result.mediaData.thumbnail.getWidth() > 0);
......
...@@ -2127,8 +2127,6 @@ jumbo_split_static_library("browser") { ...@@ -2127,8 +2127,6 @@ jumbo_split_static_library("browser") {
"android/download/service/download_background_task.cc", "android/download/service/download_background_task.cc",
"android/download/service/download_task_scheduler.cc", "android/download/service/download_task_scheduler.cc",
"android/download/service/download_task_scheduler.h", "android/download/service/download_task_scheduler.h",
"android/download/video_frame_thumbnail_converter.cc",
"android/download/video_frame_thumbnail_converter.h",
"android/explore_sites/catalog.cc", "android/explore_sites/catalog.cc",
"android/explore_sites/catalog.h", "android/explore_sites/catalog.h",
"android/explore_sites/explore_sites_bridge.cc", "android/explore_sites/explore_sites_bridge.cc",
......
...@@ -9,15 +9,17 @@ ...@@ -9,15 +9,17 @@
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "cc/paint/skia_paint_canvas.h"
#include "chrome/browser/android/download/local_media_data_source_factory.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/browser/android/gpu_video_accelerator_factories_provider.h"
#include "content/public/common/service_manager_connection.h" #include "content/public/common/service_manager_connection.h"
#include "media/base/overlay_info.h" #include "media/base/overlay_info.h"
#include "media/base/video_thumbnail_decoder.h"
#include "media/mojo/clients/mojo_video_decoder.h" #include "media/mojo/clients/mojo_video_decoder.h"
#include "media/mojo/interfaces/constants.mojom.h" #include "media/mojo/interfaces/constants.mojom.h"
#include "media/mojo/interfaces/media_service.mojom.h" #include "media/mojo/interfaces/media_service.mojom.h"
#include "media/mojo/services/media_interface_provider.h" #include "media/mojo/services/media_interface_provider.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "media/video/gpu_video_accelerator_factories.h" #include "media/video/gpu_video_accelerator_factories.h"
#include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/connector.h"
#include "services/ws/public/cpp/gpu/context_provider_command_buffer.h" #include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
...@@ -76,7 +78,7 @@ void DownloadMediaParser::OnMediaParserCreated() { ...@@ -76,7 +78,7 @@ void DownloadMediaParser::OnMediaParserCreated() {
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
media_parser()->ParseMediaMetadata( media_parser()->ParseMediaMetadata(
mime_type_, size_, true /* get_attached_images */, std::move(source_ptr), mime_type_, size_, false /* get_attached_images */, std::move(source_ptr),
base::BindOnce(&DownloadMediaParser::OnMediaMetadataParsed, base::BindOnce(&DownloadMediaParser::OnMediaMetadataParsed,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
...@@ -96,10 +98,6 @@ void DownloadMediaParser::OnMediaMetadataParsed( ...@@ -96,10 +98,6 @@ void DownloadMediaParser::OnMediaMetadataParsed(
metadata_ = std::move(metadata); metadata_ = std::move(metadata);
DCHECK(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. // For audio file, we only need metadata and poster.
if (base::StartsWith(mime_type_, "audio/", if (base::StartsWith(mime_type_, "audio/",
base::CompareCase::INSENSITIVE_ASCII)) { base::CompareCase::INSENSITIVE_ASCII)) {
...@@ -128,22 +126,39 @@ void DownloadMediaParser::RetrieveEncodedVideoFrame() { ...@@ -128,22 +126,39 @@ void DownloadMediaParser::RetrieveEncodedVideoFrame() {
media_parser()->ExtractVideoFrame( media_parser()->ExtractVideoFrame(
mime_type_, base::saturated_cast<uint32_t>(size_), std::move(source_ptr), mime_type_, base::saturated_cast<uint32_t>(size_), std::move(source_ptr),
base::BindOnce(&DownloadMediaParser::OnEncodedVideoFrameRetrieved, base::BindOnce(&DownloadMediaParser::OnVideoFrameRetrieved,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
void DownloadMediaParser::OnEncodedVideoFrameRetrieved( void DownloadMediaParser::OnVideoFrameRetrieved(
bool success, bool success,
const std::vector<uint8_t>& data, chrome::mojom::VideoFrameDataPtr video_frame_data,
const media::VideoDecoderConfig& config) { const media::VideoDecoderConfig& config) {
if (data.empty()) { if (!success) {
OnError(); OnError();
return; return;
} }
encoded_data_ = data; video_frame_data_ = std::move(video_frame_data);
config_ = config; config_ = config;
// For vp8, vp9 codec, we directly do software decoding in utility process.
// Render now.
if (video_frame_data_->which() ==
chrome::mojom::VideoFrameData::Tag::DECODED_FRAME) {
decode_done_ = true;
RenderVideoFrame(std::move(video_frame_data_->get_decoded_frame()));
return;
}
// For other codec, the encoded frame is retrieved in utility process, send
// the data to GPU process to do hardware decoding.
if (video_frame_data_->get_encoded_data().empty()) {
OnError();
return;
}
// Starts to decode with MojoVideoDecoder.
content::CreateGpuVideoAcceleratorFactories(base::BindRepeating( content::CreateGpuVideoAcceleratorFactories(base::BindRepeating(
&DownloadMediaParser::OnGpuVideoAcceleratorFactoriesReady, &DownloadMediaParser::OnGpuVideoAcceleratorFactoriesReady,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
...@@ -162,82 +177,49 @@ void DownloadMediaParser::DecodeVideoFrame() { ...@@ -162,82 +177,49 @@ void DownloadMediaParser::DecodeVideoFrame() {
// Build and config the decoder. // Build and config the decoder.
DCHECK(gpu_factories_); DCHECK(gpu_factories_);
decoder_ = std::make_unique<media::MojoVideoDecoder>( auto mojo_decoder = std::make_unique<media::MojoVideoDecoder>(
base::ThreadTaskRunnerHandle::Get(), gpu_factories_.get(), this, base::ThreadTaskRunnerHandle::Get(), gpu_factories_.get(), this,
std::move(video_decoder_ptr), base::BindRepeating(&OnRequestOverlayInfo), std::move(video_decoder_ptr), base::BindRepeating(&OnRequestOverlayInfo),
gfx::ColorSpace()); gfx::ColorSpace());
decoder_->Initialize( decoder_ = std::make_unique<media::VideoThumbnailDecoder>(
config_, false, nullptr, std::move(mojo_decoder), config_,
base::BindRepeating(&DownloadMediaParser::OnVideoDecoderInitialized, std::move(video_frame_data_->get_encoded_data()));
weak_factory_.GetWeakPtr()),
base::BindRepeating(&DownloadMediaParser::OnVideoFrameDecoded, decoder_->Start(base::BindOnce(&DownloadMediaParser::OnVideoFrameDecoded,
weak_factory_.GetWeakPtr()), weak_factory_.GetWeakPtr()));
base::RepeatingClosure());
} }
void DownloadMediaParser::OnVideoDecoderInitialized(bool success) { void DownloadMediaParser::OnVideoFrameDecoded(
if (!success) { scoped_refptr<media::VideoFrame> frame) {
if (!frame) {
OnError(); OnError();
return; 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()); DCHECK(frame->HasTextures());
decode_done_ = true; decode_done_ = true;
RenderVideoFrame(frame); RenderVideoFrame(std::move(frame));
} }
void DownloadMediaParser::RenderVideoFrame( void DownloadMediaParser::RenderVideoFrame(
const scoped_refptr<media::VideoFrame>& video_frame) { scoped_refptr<media::VideoFrame> video_frame) {
auto converter = VideoFrameThumbnailConverter::Create( auto context_provider =
config_.codec(), gpu_factories_->GetMediaContextProvider()); gpu_factories_ ? gpu_factories_->GetMediaContextProvider() : nullptr;
converter->ConvertToBitmap(
video_frame, media::PaintCanvasVideoRenderer renderer;
base::BindOnce(&DownloadMediaParser::OnBitmapGenerated, SkBitmap bitmap;
weak_factory_.GetWeakPtr(), std::move(converter))); bitmap.allocN32Pixels(video_frame->visible_rect().width(),
} video_frame->visible_rect().height());
void DownloadMediaParser::OnBitmapGenerated( // Draw the video frame to |bitmap|.
std::unique_ptr<VideoFrameThumbnailConverter>, cc::SkiaPaintCanvas canvas(bitmap);
bool success, media::Context3D context =
SkBitmap bitmap) { context_provider ? media::Context3D(context_provider->ContextGL(),
if (!success) { context_provider->GrContext())
OnError(); : media::Context3D();
return; renderer.Copy(video_frame, &canvas, context);
}
NotifyComplete(std::move(bitmap)); NotifyComplete(std::move(bitmap));
} }
...@@ -275,8 +257,6 @@ void DownloadMediaParser::OnMediaDataReady( ...@@ -275,8 +257,6 @@ void DownloadMediaParser::OnMediaDataReady(
} }
void DownloadMediaParser::NotifyComplete(SkBitmap bitmap) { void DownloadMediaParser::NotifyComplete(SkBitmap bitmap) {
// TODO(xingliu): Return the metadata and video thumbnail data in
// |parse_complete_cb_|.
DCHECK(metadata_); DCHECK(metadata_);
if (parse_complete_cb_) if (parse_complete_cb_)
std::move(parse_complete_cb_) std::move(parse_complete_cb_)
......
...@@ -26,10 +26,10 @@ class GpuVideoAcceleratorFactories; ...@@ -26,10 +26,10 @@ class GpuVideoAcceleratorFactories;
class MediaInterfaceProvider; class MediaInterfaceProvider;
class MojoVideoDecoder; class MojoVideoDecoder;
class VideoDecoderConfig; class VideoDecoderConfig;
class VideoThumbnailDecoder;
} // namespace media } // namespace media
class SkBitmap; class SkBitmap;
class VideoFrameThumbnailConverter;
// Parse local media files, including media metadata and thumbnails. // Parse local media files, including media metadata and thumbnails.
// Metadata is always parsed in utility process for both audio and video files. // Metadata is always parsed in utility process for both audio and video files.
...@@ -69,27 +69,21 @@ class DownloadMediaParser : public MediaParserProvider, public media::MediaLog { ...@@ -69,27 +69,21 @@ class DownloadMediaParser : public MediaParserProvider, public media::MediaLog {
// Retrieves an encoded video frame. // Retrieves an encoded video frame.
void RetrieveEncodedVideoFrame(); void RetrieveEncodedVideoFrame();
void OnEncodedVideoFrameRetrieved(bool success, void OnVideoFrameRetrieved(bool success,
const std::vector<uint8_t>& data, chrome::mojom::VideoFrameDataPtr video_frame_data,
const media::VideoDecoderConfig& config); const media::VideoDecoderConfig& config);
// Decodes the video frame. // Decodes the video frame.
void OnGpuVideoAcceleratorFactoriesReady( void OnGpuVideoAcceleratorFactoriesReady(
std::unique_ptr<media::GpuVideoAcceleratorFactories>); std::unique_ptr<media::GpuVideoAcceleratorFactories>);
void DecodeVideoFrame(); void DecodeVideoFrame();
void OnVideoDecoderInitialized(bool success); void OnVideoFrameDecoded(scoped_refptr<media::VideoFrame> decoded_frame);
void OnVideoBufferDecoded(media::DecodeStatus status);
void OnEosBufferDecoded(media::DecodeStatus status);
void OnVideoFrameDecoded(
const scoped_refptr<media::VideoFrame>& decoded_frame);
media::mojom::InterfaceFactory* GetMediaInterfaceFactory();
void OnDecoderConnectionError();
// Renders the video frame to bitmap. // Renders the video frame to bitmap.
void RenderVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame); void RenderVideoFrame(scoped_refptr<media::VideoFrame> video_frame);
void OnBitmapGenerated(std::unique_ptr<VideoFrameThumbnailConverter>,
bool success, media::mojom::InterfaceFactory* GetMediaInterfaceFactory();
SkBitmap bitmap); void OnDecoderConnectionError();
// Overlays media data source read operation. Gradually read data from media // Overlays media data source read operation. Gradually read data from media
// file. // file.
...@@ -106,21 +100,21 @@ class DownloadMediaParser : public MediaParserProvider, public media::MediaLog { ...@@ -106,21 +100,21 @@ class DownloadMediaParser : public MediaParserProvider, public media::MediaLog {
ParseCompleteCB parse_complete_cb_; ParseCompleteCB parse_complete_cb_;
chrome::mojom::MediaMetadataPtr metadata_; chrome::mojom::MediaMetadataPtr metadata_;
// Poster images obtained with |metadata_|. // Used to read media files chunks to feed to IPC channel.
std::vector<metadata::AttachedImage> attached_images_;
std::unique_ptr<chrome::mojom::MediaDataSource> media_data_source_; std::unique_ptr<chrome::mojom::MediaDataSource> media_data_source_;
// The task runner to do blocking disk IO. // The task runner to do blocking disk IO.
scoped_refptr<base::SequencedTaskRunner> file_task_runner_; scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
// Encoded video frame to be decoded. This data can be large for high // Cached video frame data, which contains either encoded frame or decoded
// resolution video, should be std::move or cleared whenever possible. // video frame. Encoded frame is extracted with ffmpeg, the data can be large
std::vector<uint8_t> encoded_data_; // for high resolution video.
chrome::mojom::VideoFrameDataPtr video_frame_data_;
// Objects used to decode the video into media::VideoFrame. // Objects used to decode the video into media::VideoFrame with
// MojoVideoDecoder.
media::VideoDecoderConfig config_; media::VideoDecoderConfig config_;
std::unique_ptr<media::MojoVideoDecoder> decoder_; std::unique_ptr<media::VideoThumbnailDecoder> decoder_;
media::mojom::InterfaceFactoryPtr media_interface_factory_; media::mojom::InterfaceFactoryPtr media_interface_factory_;
std::unique_ptr<media::MediaInterfaceProvider> media_interface_provider_; std::unique_ptr<media::MediaInterfaceProvider> media_interface_provider_;
std::unique_ptr<media::GpuVideoAcceleratorFactories> gpu_factories_; std::unique_ptr<media::GpuVideoAcceleratorFactories> gpu_factories_;
......
// 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/video_frame_thumbnail_converter.h"
#include "base/macros.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/gl_helper.h"
#include "media/base/video_frame.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
namespace {
// Class to generate bitmap based on video frame backed by texture in remote
// process.
class TextureFrameThumbnailConverter : public VideoFrameThumbnailConverter {
public:
TextureFrameThumbnailConverter(
scoped_refptr<ws::ContextProviderCommandBuffer> context_provider)
: context_provider_(std::move(context_provider)) {}
TextureFrameThumbnailConverter() = default;
~TextureFrameThumbnailConverter() override = default;
private:
// VideoFrameThumbnailConverter implementation.
void ConvertToBitmap(const scoped_refptr<media::VideoFrame>& frame,
BitmapCallback pixel_callback) override {
DCHECK(frame->HasTextures())
<< "This implementation only do texture read backs.";
// Read back the texture data contained in the video frame.
media::PaintCanvasVideoRenderer renderer;
SkBitmap skbitmap;
skbitmap.allocN32Pixels(frame->visible_rect().width(),
frame->visible_rect().height());
cc::SkiaPaintCanvas canvas(skbitmap);
renderer.Copy(frame, &canvas,
media::Context3D(context_provider_->ContextGL(),
context_provider_->GrContext()));
std::move(pixel_callback).Run(true, std::move(skbitmap));
}
// Command buffer channel used to read back the pixel data from texture in
// remote process.
scoped_refptr<ws::ContextProviderCommandBuffer> context_provider_;
DISALLOW_COPY_AND_ASSIGN(TextureFrameThumbnailConverter);
};
} // namespace
// static
std::unique_ptr<VideoFrameThumbnailConverter>
VideoFrameThumbnailConverter::Create(
media::VideoCodec codec,
scoped_refptr<ws::ContextProviderCommandBuffer> context_provider) {
// TODO(xingliu): Implement vpx decode and render pipeline.
DCHECK_NE(codec, media::VideoCodec::kCodecVP8);
DCHECK_NE(codec, media::VideoCodec::kCodecVP9);
return std::make_unique<TextureFrameThumbnailConverter>(
std::move(context_provider));
}
// 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.
#ifndef CHROME_BROWSER_ANDROID_DOWNLOAD_VIDEO_FRAME_THUMBNAIL_CONVERTER_H_
#define CHROME_BROWSER_ANDROID_DOWNLOAD_VIDEO_FRAME_THUMBNAIL_CONVERTER_H_
#include <memory>
#include <vector>
#include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "media/base/video_codecs.h"
namespace media {
class VideoFrame;
} // namespace media
namespace ws {
class ContextProviderCommandBuffer;
} // namespace ws
class SkBitmap;
// Generates video thumbnail from a video frame. For most video format such as
// H264, the input video frame contains a texture created in GPU process, we do
// a readback and generate the bitmap. For vp8, vp9 format, the video frame
// contains in-memory YUV data.
class VideoFrameThumbnailConverter {
public:
using BitmapCallback = base::OnceCallback<void(bool, SkBitmap)>;
// Create the video thumbnail renderer for video type with |codec|. The
// decoder to generate video frame may yields video frames backed by texture
// in remote process or raw YUV data.
static std::unique_ptr<VideoFrameThumbnailConverter> Create(
media::VideoCodec codec,
scoped_refptr<ws::ContextProviderCommandBuffer> context_provider);
virtual void ConvertToBitmap(const scoped_refptr<media::VideoFrame>& frame,
BitmapCallback pixel_callback) = 0;
virtual ~VideoFrameThumbnailConverter() = default;
};
#endif // CHROME_BROWSER_ANDROID_DOWNLOAD_VIDEO_FRAME_THUMBNAIL_CONVERTER_H_
...@@ -55,6 +55,8 @@ if (is_android) { ...@@ -55,6 +55,8 @@ if (is_android) {
data = [ data = [
"//media/test/data/bear.mp4", "//media/test/data/bear.mp4",
"//media/test/data/bear-vp8-webvtt.webm",
"//media/test/data/bear-vp8a.webm",
"//media/test/data/sfx.mp3", "//media/test/data/sfx.mp3",
] ]
......
...@@ -8,25 +8,85 @@ ...@@ -8,25 +8,85 @@
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "chrome/services/media_gallery_util/ipc_data_source.h" #include "chrome/services/media_gallery_util/ipc_data_source.h"
#include "media/base/bind_to_current_loop.h" #include "media/base/bind_to_current_loop.h"
#include "media/base/video_codecs.h"
#include "media/base/video_thumbnail_decoder.h"
#include "media/filters/android/video_frame_extractor.h" #include "media/filters/android/video_frame_extractor.h"
#include "media/filters/vpx_video_decoder.h"
#include "media/media_buildflags.h"
#include "media/mojo/common/mojo_shared_buffer_video_frame.h"
namespace { namespace {
void OnThumbnailGenerated( // Return the video frame back to browser process. A valid |config| is
// needed for deserialization.
void OnSoftwareVideoFrameDecoded(
std::unique_ptr<media::VideoThumbnailDecoder>,
MediaParser::ExtractVideoFrameCallback video_frame_callback,
const media::VideoDecoderConfig& config,
scoped_refptr<media::VideoFrame> frame) {
DCHECK(video_frame_callback);
if (!frame) {
std::move(video_frame_callback)
.Run(false, chrome::mojom::VideoFrameData::New(), config);
return;
}
std::move(video_frame_callback)
.Run(true,
chrome::mojom::VideoFrameData::NewDecodedFrame(
media::MojoSharedBufferVideoFrame::CreateFromYUVFrame(*frame)),
config);
}
void OnEncodedVideoFrameExtracted(
std::unique_ptr<media::VideoFrameExtractor> video_frame_extractor, std::unique_ptr<media::VideoFrameExtractor> video_frame_extractor,
MediaParser::ExtractVideoFrameCallback extract_frame_cb, MediaParser::ExtractVideoFrameCallback video_frame_callback,
bool success, bool success,
const std::vector<uint8_t>& data, std::vector<uint8_t> data,
const media::VideoDecoderConfig& config) { const media::VideoDecoderConfig& config) {
std::move(extract_frame_cb).Run(success, data, config); if (!success || data.empty()) {
std::move(video_frame_callback)
.Run(false, chrome::mojom::VideoFrameData::New(), config);
return;
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS) && \
!BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
// H264 currently needs to be decoded in GPU process when no software decoder
// is provided.
if (config.codec() == media::VideoCodec::kCodecH264) {
std::move(video_frame_callback)
.Run(success,
chrome::mojom::VideoFrameData::NewEncodedData(std::move(data)),
config);
return;
}
#endif
if (config.codec() != media::VideoCodec::kCodecVP8 &&
config.codec() != media::VideoCodec::kCodecVP9) {
std::move(video_frame_callback)
.Run(false, chrome::mojom::VideoFrameData::New(), config);
return;
}
// Decode with libvpx for vp8, vp9.
auto thumbnail_decoder = std::make_unique<media::VideoThumbnailDecoder>(
std::make_unique<media::VpxVideoDecoder>(), config, std::move(data));
thumbnail_decoder->Start(
base::BindOnce(&OnSoftwareVideoFrameDecoded, std::move(thumbnail_decoder),
std::move(video_frame_callback), config));
} }
void ExtractVideoFrameOnMediaThread( void ExtractVideoFrameOnMediaThread(
media::DataSource* data_source, media::DataSource* data_source,
MediaParser::ExtractVideoFrameCallback extract_frame_callback) { MediaParser::ExtractVideoFrameCallback video_frame_callback) {
auto extractor = std::make_unique<media::VideoFrameExtractor>(data_source); auto extractor = std::make_unique<media::VideoFrameExtractor>(data_source);
extractor->Start(base::BindOnce(&OnThumbnailGenerated, std::move(extractor), extractor->Start(base::BindOnce(&OnEncodedVideoFrameExtracted,
std::move(extract_frame_callback))); std::move(extractor),
std::move(video_frame_callback)));
} }
} // namespace } // namespace
...@@ -43,7 +103,7 @@ void MediaParserAndroid::ExtractVideoFrame( ...@@ -43,7 +103,7 @@ void MediaParserAndroid::ExtractVideoFrame(
const std::string& mime_type, const std::string& mime_type,
uint32_t total_size, uint32_t total_size,
chrome::mojom::MediaDataSourcePtr media_data_source, chrome::mojom::MediaDataSourcePtr media_data_source,
MediaParser::ExtractVideoFrameCallback extract_frame_callback) { MediaParser::ExtractVideoFrameCallback video_frame_callback) {
data_source_ = std::make_unique<IPCDataSource>( data_source_ = std::make_unique<IPCDataSource>(
std::move(media_data_source), static_cast<int64_t>(total_size)); std::move(media_data_source), static_cast<int64_t>(total_size));
...@@ -51,5 +111,5 @@ void MediaParserAndroid::ExtractVideoFrame( ...@@ -51,5 +111,5 @@ void MediaParserAndroid::ExtractVideoFrame(
FROM_HERE, FROM_HERE,
base::BindOnce( base::BindOnce(
&ExtractVideoFrameOnMediaThread, data_source_.get(), &ExtractVideoFrameOnMediaThread, data_source_.get(),
media::BindToCurrentLoop(std::move(extract_frame_callback)))); media::BindToCurrentLoop(std::move(video_frame_callback))));
} }
...@@ -22,10 +22,11 @@ class MediaParserAndroid : public MediaParser { ...@@ -22,10 +22,11 @@ class MediaParserAndroid : public MediaParser {
~MediaParserAndroid() override; ~MediaParserAndroid() override;
// MediaParser implementation. // MediaParser implementation.
void ExtractVideoFrame(const std::string& mime_type, void ExtractVideoFrame(
uint32_t total_size, const std::string& mime_type,
chrome::mojom::MediaDataSourcePtr media_data_source, uint32_t total_size,
ExtractVideoFrameCallback callback) override; chrome::mojom::MediaDataSourcePtr media_data_source,
ExtractVideoFrameCallback video_frame_callback) override;
private: private:
// The task runner to do blocking IO. The utility thread cannot be blocked. // The task runner to do blocking IO. The utility thread cannot be blocked.
......
...@@ -15,12 +15,41 @@ ...@@ -15,12 +15,41 @@
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h" #include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h"
#include "media/base/test_data_util.h" #include "media/base/test_data_util.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
#include "services/service_manager/public/cpp/service_context_ref.h" #include "services/service_manager/public/cpp/service_context_ref.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace { namespace {
struct ExtractVideoFrameResult {
bool success = false;
chrome::mojom::VideoFrameDataPtr video_frame_data;
media::VideoDecoderConfig config;
};
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// Returns if the first 3 or 4 bytes of H264 encoded |data| is the start code,
// 0x000001 or 0x00000001.
bool HasH264StartCode(const std::vector<uint8_t>& data) {
if (data.size() < 4 || (data[0] != 0u || data[1] != 0u))
return false;
return data[2] == 0x01 || (data[2] == 0u && data[3] == 0x01);
}
#endif
// Returns if the first few bytes in the YUV frame are not all zero. This is a
// rough method to verify the frame is not empty.
bool HasValidYUVData(const scoped_refptr<media::VideoFrame>& frame) {
bool valid = false;
for (size_t i = 0; i < 8; ++i) {
valid |= *(frame->data(media::VideoFrame::kYPlane) + i);
if (valid)
break;
}
return valid;
}
// Used in test that do blocking reads from a local file. // Used in test that do blocking reads from a local file.
class TestMediaDataSource : public chrome::mojom::MediaDataSource { class TestMediaDataSource : public chrome::mojom::MediaDataSource {
public: public:
...@@ -65,6 +94,33 @@ class MediaParserAndroidTest : public testing::Test { ...@@ -65,6 +94,33 @@ class MediaParserAndroidTest : public testing::Test {
protected: protected:
MediaParserAndroid* parser() { return parser_.get(); } MediaParserAndroid* parser() { return parser_.get(); }
ExtractVideoFrameResult ExtractFrame(const std::string& file_name,
const std::string& mime_type) {
auto file_path = media::GetTestDataFilePath(file_name);
int64_t size = 0;
EXPECT_TRUE(base::GetFileSize(file_path, &size));
chrome::mojom::MediaDataSourcePtr data_source_ptr;
TestMediaDataSource test_data_source(&data_source_ptr, file_path);
ExtractVideoFrameResult result;
base::RunLoop run_loop;
parser()->ExtractVideoFrame(
mime_type, size, std::move(data_source_ptr),
base::BindLambdaForTesting(
[&](bool success, chrome::mojom::VideoFrameDataPtr video_frame_data,
const media::VideoDecoderConfig& config) {
result.success = success;
result.video_frame_data = std::move(video_frame_data);
result.config = config;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
private: private:
std::unique_ptr<MediaParserAndroid> parser_; std::unique_ptr<MediaParserAndroid> parser_;
...@@ -74,26 +130,50 @@ class MediaParserAndroidTest : public testing::Test { ...@@ -74,26 +130,50 @@ class MediaParserAndroidTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(MediaParserAndroidTest); DISALLOW_COPY_AND_ASSIGN(MediaParserAndroidTest);
}; };
// Test to verify encoded video frame can be extracted. #if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(MediaParserAndroidTest, VideoFrameExtraction) { // Test to verify an encoded video frame can be extracted for h264 codec video
auto file_path = media::GetTestDataFilePath("bear.mp4"); // file. Decoding needs to happen in other process.
int64_t size = 0; TEST_F(MediaParserAndroidTest, VideoFrameExtractionH264) {
ASSERT_TRUE(base::GetFileSize(file_path, &size)); auto result = ExtractFrame("bear.mp4", "video/mp4");
EXPECT_TRUE(result.success);
chrome::mojom::MediaDataSourcePtr data_source_ptr; EXPECT_EQ(result.video_frame_data->which(),
TestMediaDataSource test_data_source(&data_source_ptr, file_path); chrome::mojom::VideoFrameData::Tag::ENCODED_DATA);
EXPECT_FALSE(result.video_frame_data->get_encoded_data().empty());
bool result = false; EXPECT_TRUE(HasH264StartCode(result.video_frame_data->get_encoded_data()));
base::RunLoop run_loop; }
parser()->ExtractVideoFrame( #endif
"video/mp4", size, std::move(data_source_ptr),
base::BindLambdaForTesting([&](bool success, const std::vector<uint8_t>&, // Test to verify a decoded video frame can be extracted for vp8 codec video
const media::VideoDecoderConfig&) { // file with YUV420 color format.
result = success; TEST_F(MediaParserAndroidTest, VideoFrameExtractionVp8) {
run_loop.Quit(); auto result = ExtractFrame("bear-vp8-webvtt.webm", "video/webm");
})); EXPECT_TRUE(result.success);
run_loop.Run(); EXPECT_EQ(result.video_frame_data->which(),
EXPECT_TRUE(result); chrome::mojom::VideoFrameData::Tag::DECODED_FRAME);
const auto& frame = result.video_frame_data->get_decoded_frame();
EXPECT_TRUE(frame);
EXPECT_TRUE(HasValidYUVData(frame));
EXPECT_TRUE(frame->IsMappable());
EXPECT_FALSE(frame->HasTextures());
EXPECT_EQ(frame->storage_type(),
media::VideoFrame::StorageType::STORAGE_MOJO_SHARED_BUFFER);
}
// Test to verify a decoded video frame can be extracted for vp8 codec with
// alpha plane.
TEST_F(MediaParserAndroidTest, VideoFrameExtractionVp8WithAlphaPlane) {
auto result = ExtractFrame("bear-vp8a.webm", "video/webm");
EXPECT_TRUE(result.success);
EXPECT_EQ(result.video_frame_data->which(),
chrome::mojom::VideoFrameData::Tag::DECODED_FRAME);
const auto& frame = result.video_frame_data->get_decoded_frame();
EXPECT_TRUE(frame);
EXPECT_TRUE(HasValidYUVData(frame));
EXPECT_TRUE(frame->IsMappable());
EXPECT_FALSE(frame->HasTextures());
EXPECT_EQ(frame->storage_type(),
media::VideoFrame::StorageType::STORAGE_MOJO_SHARED_BUFFER);
} }
} // namespace } // namespace
...@@ -9,6 +9,13 @@ import "mojo/public/mojom/base/file.mojom"; ...@@ -9,6 +9,13 @@ import "mojo/public/mojom/base/file.mojom";
import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/values.mojom"; import "mojo/public/mojom/base/values.mojom";
// Contains either encoded video frame in |encoded_data|, or decoded video
// frame in |decoded_frame|.
union VideoFrameData {
array<uint8> encoded_data;
media.mojom.VideoFrame? decoded_frame;
};
interface MediaParser { interface MediaParser {
// Extracts metadata from media data with |mime_type|, |total_size| and // Extracts metadata from media data with |mime_type|, |total_size| and
// available from |media_data_source|. If there are images referred to in the // available from |media_data_source|. If there are images referred to in the
...@@ -22,14 +29,15 @@ interface MediaParser { ...@@ -22,14 +29,15 @@ interface MediaParser {
MediaMetadata metadata, MediaMetadata metadata,
array<AttachedImage> attached_images); array<AttachedImage> attached_images);
// Extracts one video key frame from |media_data_source|. The frame is still // Extracts one video key frame from |media_data_source|. Returns the decoded
// in encoded format. // frame if decoding can be done in utility process, or an encoded frame if
// decoding needs to happen in other process.
[EnableIf=is_android] [EnableIf=is_android]
ExtractVideoFrame(string mime_type, ExtractVideoFrame(string mime_type,
uint32 total_size, uint32 total_size,
MediaDataSource media_data_source) MediaDataSource media_data_source)
=> (bool success, => (bool success,
array<uint8> data, VideoFrameData frame_data,
media.mojom.VideoDecoderConfig config); media.mojom.VideoDecoderConfig config);
// Validates the passed media file with sanity checks, and file decoding // Validates the passed media file with sanity checks, and file decoding
......
...@@ -278,6 +278,8 @@ jumbo_source_set("base") { ...@@ -278,6 +278,8 @@ jumbo_source_set("base") {
"video_renderer.h", "video_renderer.h",
"video_rotation.cc", "video_rotation.cc",
"video_rotation.h", "video_rotation.h",
"video_thumbnail_decoder.cc",
"video_thumbnail_decoder.h",
"video_types.cc", "video_types.cc",
"video_types.h", "video_types.h",
"video_util.cc", "video_util.cc",
...@@ -521,6 +523,7 @@ source_set("unit_tests") { ...@@ -521,6 +523,7 @@ source_set("unit_tests") {
"video_frame_layout_unittest.cc", "video_frame_layout_unittest.cc",
"video_frame_pool_unittest.cc", "video_frame_pool_unittest.cc",
"video_frame_unittest.cc", "video_frame_unittest.cc",
"video_thumbnail_decoder_unittest.cc",
"video_types_unittest.cc", "video_types_unittest.cc",
"video_util_unittest.cc", "video_util_unittest.cc",
"wall_clock_time_source_unittest.cc", "wall_clock_time_source_unittest.cc",
......
// 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 "media/base/video_thumbnail_decoder.h"
#include "base/bind_helpers.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_frame.h"
namespace media {
VideoThumbnailDecoder::VideoThumbnailDecoder(
std::unique_ptr<VideoDecoder> decoder,
const VideoDecoderConfig& config,
std::vector<uint8_t> encoded_data)
: decoder_(std::move(decoder)),
config_(config),
encoded_data_(std::move(encoded_data)),
weak_factory_(this) {
DCHECK(!encoded_data_.empty());
DCHECK(config_.IsValidConfig());
}
VideoThumbnailDecoder::~VideoThumbnailDecoder() = default;
void VideoThumbnailDecoder::Start(VideoFrameCallback video_frame_callback) {
video_frame_callback_ = std::move(video_frame_callback);
DCHECK(video_frame_callback_);
decoder_->Initialize(
config_, false, nullptr,
base::BindRepeating(&VideoThumbnailDecoder::OnVideoDecoderInitialized,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&VideoThumbnailDecoder::OnVideoFrameDecoded,
weak_factory_.GetWeakPtr()),
base::DoNothing());
}
void VideoThumbnailDecoder::OnVideoDecoderInitialized(bool success) {
if (!success) {
NotifyComplete(nullptr);
return;
}
auto buffer =
DecoderBuffer::CopyFrom(&encoded_data_[0], encoded_data_.size());
encoded_data_.clear();
decoder_->Decode(
buffer, base::BindRepeating(&VideoThumbnailDecoder::OnVideoBufferDecoded,
weak_factory_.GetWeakPtr()));
}
void VideoThumbnailDecoder::OnVideoBufferDecoded(DecodeStatus status) {
if (status != DecodeStatus::OK) {
NotifyComplete(nullptr);
return;
}
// Enqueue eos since only one video frame is needed for thumbnail.
decoder_->Decode(
DecoderBuffer::CreateEOSBuffer(),
base::BindRepeating(&VideoThumbnailDecoder::OnEosBufferDecoded,
weak_factory_.GetWeakPtr()));
}
void VideoThumbnailDecoder::OnEosBufferDecoded(DecodeStatus status) {
if (status != DecodeStatus::OK)
NotifyComplete(nullptr);
}
void VideoThumbnailDecoder::OnVideoFrameDecoded(
const scoped_refptr<VideoFrame>& frame) {
NotifyComplete(std::move(frame));
}
void VideoThumbnailDecoder::NotifyComplete(scoped_refptr<VideoFrame> frame) {
DCHECK(video_frame_callback_);
std::move(video_frame_callback_).Run(std::move(frame));
}
} // namespace media
// 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.
#ifndef MEDIA_BASE_VIDEO_THUMBNAIL_DECODER_H_
#define MEDIA_BASE_VIDEO_THUMBNAIL_DECODER_H_
#include <memory>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "media/base/media_export.h"
#include "media/base/video_decoder.h"
#include "media/base/video_decoder_config.h"
namespace media {
class VideoFrame;
// Class to decode a single video frame for video thumbnail generation.
class MEDIA_EXPORT VideoThumbnailDecoder {
public:
using VideoFrameCallback =
base::OnceCallback<void(scoped_refptr<VideoFrame>)>;
// Creates the thumbnail decoder with |decoder| that performs the actual work.
// |encoded_data| is the encoded frame extracted with ffmpeg. A video frame
// will be returned through |video_frame_callback|.
VideoThumbnailDecoder(std::unique_ptr<VideoDecoder> decoder,
const VideoDecoderConfig& config,
std::vector<uint8_t> encoded_data);
~VideoThumbnailDecoder();
// Starts to decode the video frame.
void Start(VideoFrameCallback video_frame_callback);
private:
void OnVideoDecoderInitialized(bool success);
void OnVideoBufferDecoded(DecodeStatus status);
void OnEosBufferDecoded(DecodeStatus status);
// Called when the output frame is generated.
void OnVideoFrameDecoded(const scoped_refptr<VideoFrame>& frame);
void NotifyComplete(scoped_refptr<VideoFrame> frame);
// The decoder that does the actual decoding.
std::unique_ptr<VideoDecoder> decoder_;
const VideoDecoderConfig config_;
// The demuxed, encoded video frame waiting to be decoded.
std::vector<uint8_t> encoded_data_;
VideoFrameCallback video_frame_callback_;
base::WeakPtrFactory<VideoThumbnailDecoder> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(VideoThumbnailDecoder);
};
} // namespace media
#endif // MEDIA_BASE_VIDEO_THUMBNAIL_DECODER_H_
// 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 <memory>
#include <vector>
#include "base/bind_helpers.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/base/video_thumbnail_decoder.h"
#include "media/base/video_types.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::DoAll;
namespace media {
namespace {
class VideoThumbnailDecoderTest : public testing::Test {
public:
VideoThumbnailDecoderTest() {}
~VideoThumbnailDecoderTest() override {}
protected:
void SetUp() override {
auto mock_video_decoder = std::make_unique<MockVideoDecoder>();
mock_video_decoder_ = mock_video_decoder.get();
VideoDecoderConfig valid_config(
kCodecVP8, VP8PROFILE_ANY, PIXEL_FORMAT_I420, COLOR_SPACE_UNSPECIFIED,
VIDEO_ROTATION_0, gfx::Size(1, 1), gfx::Rect(1, 1), gfx::Size(1, 1),
EmptyExtraData(), Unencrypted());
thumbnail_decoder_ = std::make_unique<VideoThumbnailDecoder>(
std::move(mock_video_decoder), valid_config, std::vector<uint8_t>{0u});
}
void TearDown() override {}
void Start() {
thumbnail_decoder_->Start(base::BindOnce(
&VideoThumbnailDecoderTest::OnFrameDecoded, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
scoped_refptr<VideoFrame> CreateFrame() {
return VideoFrame::CreateZeroInitializedFrame(
VideoPixelFormat::PIXEL_FORMAT_I420, gfx::Size(1, 1), gfx::Rect(1, 1),
gfx::Size(1, 1), base::TimeDelta());
}
VideoThumbnailDecoder* thumbnail_decoder() {
return thumbnail_decoder_.get();
}
MockVideoDecoder* mock_video_decoder() { return mock_video_decoder_; }
const scoped_refptr<VideoFrame>& frame() { return frame_; }
private:
void OnFrameDecoded(scoped_refptr<VideoFrame> frame) {
frame_ = std::move(frame);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
MockVideoDecoder* mock_video_decoder_;
std::unique_ptr<VideoThumbnailDecoder> thumbnail_decoder_;
// The video frame returned from the thumbnail decoder.
scoped_refptr<VideoFrame> frame_;
DISALLOW_COPY_AND_ASSIGN(VideoThumbnailDecoderTest);
};
// Verifies a video frame can be delivered when decoder successfully created
// the video frame.
TEST_F(VideoThumbnailDecoderTest, Success) {
auto expected_frame = CreateFrame();
EXPECT_CALL(*mock_video_decoder(), Initialize(_, _, _, _, _, _))
.WillOnce(DoAll(RunCallback<3>(true), RunCallback<4>(expected_frame)));
EXPECT_CALL(*mock_video_decoder(), Decode(_, _))
.Times(2)
.WillRepeatedly(RunCallback<1>(DecodeStatus::OK));
Start();
EXPECT_TRUE(frame());
}
// No output video frame when decoder failed to initialize.
TEST_F(VideoThumbnailDecoderTest, InitializationFailed) {
auto expected_frame = CreateFrame();
EXPECT_CALL(*mock_video_decoder(), Initialize(_, _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
Start();
EXPECT_FALSE(frame());
}
// No output video frame when decoder failed to decode.
TEST_F(VideoThumbnailDecoderTest, DecodingFailed) {
auto expected_frame = CreateFrame();
EXPECT_CALL(*mock_video_decoder(), Initialize(_, _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*mock_video_decoder(), Decode(_, _))
.WillOnce(RunCallback<1>(DecodeStatus::DECODE_ERROR));
Start();
EXPECT_FALSE(frame());
}
} // namespace
} // namespace media
...@@ -107,7 +107,8 @@ void VideoFrameExtractor::ConvertPacket(AVPacket* packet) { ...@@ -107,7 +107,8 @@ void VideoFrameExtractor::ConvertPacket(AVPacket* packet) {
break; break;
} }
bitstream_converter_->ConvertPacket(packet); if (bitstream_converter_)
bitstream_converter_->ConvertPacket(packet);
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) #endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
} }
......
...@@ -38,7 +38,7 @@ class MEDIA_EXPORT VideoFrameExtractor { ...@@ -38,7 +38,7 @@ class MEDIA_EXPORT VideoFrameExtractor {
public: public:
using VideoFrameCallback = using VideoFrameCallback =
base::OnceCallback<void(bool success, base::OnceCallback<void(bool success,
const std::vector<uint8_t>& data, std::vector<uint8_t> data,
const VideoDecoderConfig& decoder_config)>; const VideoDecoderConfig& decoder_config)>;
explicit VideoFrameExtractor(DataSource* data_source); explicit VideoFrameExtractor(DataSource* data_source);
......
...@@ -23,13 +23,13 @@ struct ExtractVideoFrameResult { ...@@ -23,13 +23,13 @@ struct ExtractVideoFrameResult {
VideoDecoderConfig decoder_config; VideoDecoderConfig decoder_config;
}; };
void OnThumbnailGenerated(ExtractVideoFrameResult* result, void OnFrameExtracted(ExtractVideoFrameResult* result,
base::RepeatingClosure quit_closure, base::RepeatingClosure quit_closure,
bool success, bool success,
const std::vector<uint8_t>& encoded_frame, std::vector<uint8_t> encoded_frame,
const VideoDecoderConfig& decoder_config) { const VideoDecoderConfig& decoder_config) {
result->success = success; result->success = success;
result->encoded_frame = encoded_frame; result->encoded_frame = std::move(encoded_frame);
result->decoder_config = decoder_config; result->decoder_config = decoder_config;
quit_closure.Run(); quit_closure.Run();
...@@ -65,7 +65,7 @@ TEST_F(VideoFrameExtractorTest, ExtractVideoFrame) { ...@@ -65,7 +65,7 @@ TEST_F(VideoFrameExtractorTest, ExtractVideoFrame) {
ExtractVideoFrameResult result; ExtractVideoFrameResult result;
base::RunLoop loop; base::RunLoop loop;
extractor()->Start( extractor()->Start(
base::BindOnce(&OnThumbnailGenerated, &result, loop.QuitClosure())); base::BindOnce(&OnFrameExtracted, &result, loop.QuitClosure()));
loop.Run(); loop.Run();
EXPECT_TRUE(result.success); EXPECT_TRUE(result.success);
EXPECT_GT(result.encoded_frame.size(), 0u); EXPECT_GT(result.encoded_frame.size(), 0u);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h" #include "base/numerics/safe_math.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace media { namespace media {
...@@ -56,6 +57,64 @@ MojoSharedBufferVideoFrame::CreateDefaultI420(const gfx::Size& dimensions, ...@@ -56,6 +57,64 @@ MojoSharedBufferVideoFrame::CreateDefaultI420(const gfx::Size& dimensions,
coded_size.width() / 2, coded_size.width() / 2, timestamp); coded_size.width() / 2, coded_size.width() / 2, timestamp);
} }
scoped_refptr<MojoSharedBufferVideoFrame>
MojoSharedBufferVideoFrame::CreateFromYUVFrame(const VideoFrame& frame) {
DCHECK_EQ(VideoFrame::NumPlanes(frame.format()), 3u);
// The data from |frame| may not be consecutive between planes. Copy data
// into a shared memory buffer which is tightly packed.
size_t allocation_size =
VideoFrame::AllocationSize(frame.format(), frame.coded_size());
mojo::ScopedSharedBufferHandle handle =
mojo::SharedBufferHandle::Create(allocation_size);
const size_t y_size =
VideoFrame::PlaneSize(frame.format(), VideoFrame::kYPlane,
frame.coded_size())
.GetArea();
const size_t u_size =
VideoFrame::PlaneSize(frame.format(), VideoFrame::kUPlane,
frame.coded_size())
.GetArea();
const size_t v_size =
VideoFrame::PlaneSize(frame.format(), VideoFrame::kVPlane,
frame.coded_size())
.GetArea();
// Computes the offset of planes in shared memory buffer.
const size_t y_offset = 0u;
const size_t u_offset = y_offset + y_size;
const size_t v_offset = y_offset + y_size + u_size;
// Create a mojo video frame backed by shared memory, so it can be sent to
// the browser process.
scoped_refptr<MojoSharedBufferVideoFrame> mojo_frame =
MojoSharedBufferVideoFrame::Create(
frame.format(), frame.coded_size(), frame.visible_rect(),
frame.natural_size(), std::move(handle), allocation_size, y_offset,
u_offset, v_offset, frame.stride(VideoFrame::kYPlane),
frame.stride(VideoFrame::kUPlane), frame.stride(VideoFrame::kVPlane),
frame.timestamp());
// Copy Y plane.
memcpy(mojo_frame->shared_buffer_data(),
static_cast<const void*>(frame.data(VideoFrame::kYPlane)), y_size);
// Copy U plane.
memcpy(mojo_frame->shared_buffer_data() + u_offset,
static_cast<const void*>(frame.data(VideoFrame::kUPlane)), u_size);
// Copy V plane.
memcpy(mojo_frame->shared_buffer_data() + v_offset,
static_cast<const void*>(frame.data(VideoFrame::kVPlane)), v_size);
// TODO(xingliu): Maybe also copy the alpha plane in
// |MojoSharedBufferVideoFrame|. The alpha plane is ignored here, but
// the |shared_memory| should contain the space for alpha plane.
return mojo_frame;
}
// static // static
scoped_refptr<MojoSharedBufferVideoFrame> MojoSharedBufferVideoFrame::Create( scoped_refptr<MojoSharedBufferVideoFrame> MojoSharedBufferVideoFrame::Create(
VideoPixelFormat format, VideoPixelFormat format,
......
...@@ -39,6 +39,12 @@ class MojoSharedBufferVideoFrame : public VideoFrame { ...@@ -39,6 +39,12 @@ class MojoSharedBufferVideoFrame : public VideoFrame {
const gfx::Size& dimensions, const gfx::Size& dimensions,
base::TimeDelta timestamp); base::TimeDelta timestamp);
// Creates a YUV frame backed by shared memory from in-memory YUV frame.
// Internally the data from in-memory YUV frame will be copied to a
// consecutive block in shared memory. Will return null on failure.
static scoped_refptr<MojoSharedBufferVideoFrame> CreateFromYUVFrame(
const VideoFrame& frame);
// Creates a MojoSharedBufferVideoFrame that uses the memory in |handle|. // Creates a MojoSharedBufferVideoFrame that uses the memory in |handle|.
// This will take ownership of |handle|, so the caller can no longer use it. // This will take ownership of |handle|, so the caller can no longer use it.
// |mojo_shared_buffer_done_cb|, if not null, is called on destruction, // |mojo_shared_buffer_done_cb|, if not null, is called on destruction,
......
...@@ -225,4 +225,28 @@ TEST(MojoSharedBufferVideoFrameTest, InterleavedData) { ...@@ -225,4 +225,28 @@ TEST(MojoSharedBufferVideoFrameTest, InterleavedData) {
EXPECT_TRUE(frame->data(VideoFrame::kVPlane)); EXPECT_TRUE(frame->data(VideoFrame::kVPlane));
} }
TEST(MojoSharedBufferVideoFrameTest, YUVFrameToMojoFrame) {
std::vector<uint8_t> data = std::vector<uint8_t>(12, 1u);
const auto pixel_format = VideoPixelFormat::PIXEL_FORMAT_I420;
const auto size = gfx::Size(1, 1);
scoped_refptr<VideoFrame> frame = VideoFrame::WrapExternalYuvData(
pixel_format, size, gfx::Rect(1, 1), size, 4, 4, 4, &data[0], &data[4],
&data[8], base::TimeDelta());
auto mojo_frame = MojoSharedBufferVideoFrame::CreateFromYUVFrame(*frame);
EXPECT_TRUE(mojo_frame);
const size_t u_offset =
VideoFrame::PlaneSize(pixel_format, VideoFrame::kYPlane, size).GetArea();
const size_t v_offset =
u_offset +
VideoFrame::PlaneSize(pixel_format, VideoFrame::kUPlane, size).GetArea();
// Verifies mapped size and offset.
EXPECT_EQ(mojo_frame->MappedSize(),
frame->AllocationSize(pixel_format, size));
EXPECT_EQ(mojo_frame->PlaneOffset(VideoFrame::kYPlane), 0u);
EXPECT_EQ(mojo_frame->PlaneOffset(VideoFrame::kUPlane), u_offset);
EXPECT_EQ(mojo_frame->PlaneOffset(VideoFrame::kVPlane), v_offset);
}
} // namespace media } // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment