Commit f0a0fb97 authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Video thumbnail: Extract video frame on Android.

This CL implements the functionality to extract one encoded video key
frame in utility process with ffmpeg for video thumbnail retrieval.

The decoding will be done in later CLs with MojoVideoDecoder.

TBR=dtrainor@chromium.org

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: Ia36ffe5ab4bb6fc32e55f1de089f07046e72a1bd
Reviewed-on: https://chromium-review.googlesource.com/1145780
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Reviewed-by: default avatarMartin Barbella <mbarbella@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#588946}
parent f59155eb
......@@ -369,9 +369,7 @@ android_library("chrome_java") {
# can provide their own implementations.
jar_excluded_patterns = [ "*/AppHooksImpl.class" ]
annotation_processor_deps = [
"//third_party/android_deps:dagger_processor"
]
annotation_processor_deps = [ "//third_party/android_deps:dagger_processor" ]
}
action("chrome_android_java_google_api_keys_srcjar") {
......@@ -705,6 +703,8 @@ android_library("chrome_test_java") {
"//content/test/data/media/session/",
"//content/test/data/media/video-player.html",
"//content/test/data/media/webrtc_test_utilities.js",
"//media/test/data/bear.mp4",
"//media/test/data/sfx.mp3",
]
}
......
......@@ -13,8 +13,15 @@ import org.chromium.base.Callback;
public class DownloadMediaParserBridge {
private long mNativeDownloadMediaParserBridge;
public DownloadMediaParserBridge() {
mNativeDownloadMediaParserBridge = nativeInit();
/** Creates a media parser to analyze media metadata and retrieve thumbnails.
* @param mimeType The mime type of the media file.
* @param filePath The absolute path of the media file.
* @param totalSize Total size of the media file.
* @param callback Callback to get the result.
*/
public DownloadMediaParserBridge(
String mimeType, String filePath, long totalSize, Callback<Boolean> callback) {
mNativeDownloadMediaParserBridge = nativeInit(mimeType, filePath, totalSize, callback);
}
/**
......@@ -27,22 +34,16 @@ public class DownloadMediaParserBridge {
}
/**
* Parses a media file to retrieve media metadata and video thumbnail.
* @param mimeType The mime type of the media file.
* @param filePath The absolute path of the media file.
* @param totalSize Total size of the media file.
* @param callback Callback to get the result.
* Starts to parse a media file to retrieve media metadata and video thumbnail.
*/
public void parseMediaFile(
String mimeType, String filePath, long totalSize, Callback<Boolean> callback) {
public void start() {
if (mNativeDownloadMediaParserBridge != 0) {
nativeParseMediaFile(
mNativeDownloadMediaParserBridge, mimeType, filePath, totalSize, callback);
nativeStart(mNativeDownloadMediaParserBridge);
}
}
private native long nativeInit();
private native long nativeInit(
String mimeType, String filePath, long totalSize, Callback<Boolean> callback);
private native void nativeDestory(long nativeDownloadMediaParserBridge);
private native void nativeParseMediaFile(long nativeDownloadMediaParserBridge, String mimeType,
String filePath, long totalSize, Callback<Boolean> callback);
private native void nativeStart(long nativeDownloadMediaParserBridge);
}
......@@ -1782,6 +1782,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/download/DownloadActivityTest.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadMediaParserTest.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadNotificationService2Test.java",
"javatests/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManagerTest.java",
......
// 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.
package org.chromium.chrome.browser.download;
import android.support.test.filters.LargeTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.io.File;
/**
* Tests to verify DownloadMediaParser, which retrieves media metadata and thumbnails.
*
* Most of the work is done in utility process and GPU process.
*
* All download media parser usage must be called on UI thread in this test to get message loop and
* threading contexts in native.
*
* Because each media parser call may perform multiple process and thread hops, it can be slow.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class DownloadMediaParserTest {
private static final long MAX_MEDIA_PARSER_POLL_TIME_MS = 10000;
private static final long MEDIA_PARSER_POLL_INTERVAL_MS = 1000;
@Rule
public ChromeBrowserTestRule mTestRule = new ChromeBrowserTestRule();
/**
* Wraps result from download media parser.
*/
public static class MediaParseResult {
public boolean done;
public boolean success;
}
@Before
public void setUp() throws Exception {
mTestRule.loadNativeLibraryAndInitBrowserProcess();
}
@Test
@LargeTest
@Feature({"Download"})
@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;
MediaParseResult result = new MediaParseResult();
// The native DownloadMediaParser needs to be created on UI thread.
ThreadUtils.runOnUiThreadBlocking(() -> {
DownloadMediaParserBridge parser = new DownloadMediaParserBridge(
"audio/mp3", filePath, audioFile.length(), (success) -> {
result.success = success;
result.done = true;
});
parser.start();
});
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return result.done;
}
}, MAX_MEDIA_PARSER_POLL_TIME_MS, MEDIA_PARSER_POLL_INTERVAL_MS);
Assert.assertTrue("Failed to parse audio metadata.", result.success);
}
@Test
@LargeTest
@Feature({"Download"})
@RetryOnFailure
public void testParseVideoMetatadataThumbnail() throws InterruptedException {
String filePath = UrlUtils.getIsolatedTestRoot() + "/media/test/data/bear.mp4";
File videoFile = new File(filePath);
Assert.assertTrue(videoFile.exists());
boolean done = false;
MediaParseResult result = new MediaParseResult();
// The native DownloadMediaParser needs to be created on UI thread.
ThreadUtils.runOnUiThreadBlocking(() -> {
DownloadMediaParserBridge parser = new DownloadMediaParserBridge(
"video/mp4", filePath, videoFile.length(), (success) -> {
result.success = success;
result.done = true;
});
parser.start();
});
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return result.done;
}
}, MAX_MEDIA_PARSER_POLL_TIME_MS, MEDIA_PARSER_POLL_INTERVAL_MS);
Assert.assertTrue("Failed to parse video file.", result.success);
}
}
......@@ -6,44 +6,146 @@
#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 "content/public/common/service_manager_connection.h"
namespace {
void OnParseMetadataDone(
std::unique_ptr<SafeMediaMetadataParser> parser_keep_alive,
SafeMediaMetadataParser::DoneCallback done_callback,
bool parse_success,
chrome::mojom::MediaMetadataPtr metadata,
std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images) {
// Call done callback on main thread.
std::move(done_callback)
.Run(parse_success, std::move(metadata), std::move(attached_images));
// 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);
}
} // namespace
DownloadMediaParser::DownloadMediaParser(
scoped_refptr<base::SequencedTaskRunner> file_task_runner)
: file_task_runner_(file_task_runner) {}
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()})),
weak_factory_(this) {}
DownloadMediaParser::~DownloadMediaParser() = default;
void DownloadMediaParser::ParseMediaFile(
int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
SafeMediaMetadataParser::DoneCallback callback) {
auto media_data_source_factory =
std::make_unique<LocalMediaDataSourceFactory>(file_path,
file_task_runner_);
auto parser = std::make_unique<SafeMediaMetadataParser>(
size, mime_type, true /* get_attached_images */,
std::move(media_data_source_factory));
SafeMediaMetadataParser* parser_ptr = parser.get();
parser_ptr->Start(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
base::BindOnce(&OnParseMetadataDone, std::move(parser),
std::move(callback)));
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() {
if (parse_complete_cb_)
std::move(parse_complete_cb_).Run(false);
}
void DownloadMediaParser::OnMediaMetadataParsed(
bool parse_success,
chrome::mojom::MediaMetadataPtr metadata,
const std::vector<metadata::AttachedImage>& attached_images) {
if (!parse_success) {
std::move(parse_complete_cb_).Run(false);
return;
}
metadata_ = std::move(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();
return;
}
DCHECK(base::StartsWith(mime_type_, "video/",
base::CompareCase::INSENSITIVE_ASCII));
// Retrieves video thumbnail if needed.
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;
// TODO(xingliu): Decode the video frame with MojoVideoDecoder.
NotifyComplete();
}
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() {
// TODO(xingliu): Return the metadata and video thumbnail data in
// |parse_complete_cb_|.
if (parse_complete_cb_)
std::move(parse_complete_cb_).Run(true);
}
void DownloadMediaParser::OnError() {
if (parse_complete_cb_)
std::move(parse_complete_cb_).Run(false);
}
......@@ -9,35 +9,84 @@
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "chrome/common/media_galleries/metadata_types.h"
#include "chrome/services/media_gallery_util/public/cpp/safe_media_metadata_parser.h"
#include "chrome/services/media_gallery_util/public/cpp/media_parser_provider.h"
#include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h"
namespace media {
class VideoDecoderConfig;
} // namespace media
// Local media files parser is used to process local media files. This object
// lives on main thread in browser process.
class DownloadMediaParser {
class DownloadMediaParser : public MediaParserProvider {
public:
explicit DownloadMediaParser(
scoped_refptr<base::SequencedTaskRunner> file_task_runner);
~DownloadMediaParser();
using ParseCompleteCB = base::OnceCallback<void(bool)>;
DownloadMediaParser(int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
ParseCompleteCB parse_complete_cb);
~DownloadMediaParser() override;
// Parse media metadata in a local file. All file IO will run on
// |file_task_runner|. The metadata is parsed in an utility process safely.
// However, the result is still comes from user-defined input, thus should be
// used with caution.
void ParseMediaFile(int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
SafeMediaMetadataParser::DoneCallback callback);
void Start();
private:
// MediaParserProvider implementation:
void OnMediaParserCreated() override;
void OnConnectionError() override;
// Called after media metadata are parsed.
void OnMediaMetadataParsed(
bool parse_success,
chrome::mojom::MediaMetadataPtr metadata,
const std::vector<metadata::AttachedImage>& attached_images);
// Retrieves an encoded video frame.
void RetrieveEncodedVideoFrame();
void OnEncodedVideoFrameRetrieved(bool success,
const std::vector<uint8_t>& data,
const media::VideoDecoderConfig& config);
// Overlays media data source read operation. Gradually read data from media
// file.
void OnMediaDataReady(chrome::mojom::MediaDataSource::ReadCallback callback,
std::unique_ptr<std::string> data);
void NotifyComplete();
void OnError();
int64_t size_;
std::string mime_type_;
base::FilePath file_path_;
ParseCompleteCB parse_complete_cb_;
chrome::mojom::MediaMetadataPtr metadata_;
std::vector<metadata::AttachedImage> attached_images_;
std::unique_ptr<chrome::mojom::MediaDataSource> media_data_source_;
// The task runner to do blocking disk IO.
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
// Encoded frame to be decoded. The data comes from a safe sandboxed process.
// This data can be large for high resolution video, should be std::move or
// cleared whenever possible.
std::vector<uint8_t> encoded_data_;
media::VideoDecoderConfig config_;
base::WeakPtrFactory<DownloadMediaParser> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DownloadMediaParser);
};
......
......@@ -4,34 +4,25 @@
#include "chrome/browser/android/download/download_media_parser_bridge.h"
#include "base/android/callback_android.h"
#include "base/android/jni_string.h"
#include "base/files/file_path.h"
#include "base/task/post_task.h"
#include "chrome/browser/android/download/download_media_parser.h"
#include "jni/DownloadMediaParserBridge_jni.h"
// static
jlong JNI_DownloadMediaParserBridge_Init(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
auto* bridge = new DownloadMediaParserBridge;
return reinterpret_cast<intptr_t>(bridge);
}
DownloadMediaParserBridge::DownloadMediaParserBridge()
: disk_io_task_runner_(
base::CreateSingleThreadTaskRunnerWithTraits({base::MayBlock()})),
parser_(std::make_unique<DownloadMediaParser>(disk_io_task_runner_)) {}
namespace {
DownloadMediaParserBridge::~DownloadMediaParserBridge() = default;
void DownloadMediaParserBridge::Destory(JNIEnv* env, jobject obj) {
delete this;
void OnMediaParsed(const base::android::ScopedJavaGlobalRef<jobject> jcallback,
bool success) {
base::android::RunBooleanCallbackAndroid(jcallback, success);
}
void DownloadMediaParserBridge::ParseMediaFile(
} // namespace
// static
jlong JNI_DownloadMediaParserBridge_Init(
JNIEnv* env,
jobject obj,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jmime_type,
const base::android::JavaParamRef<jstring>& jfile_path,
jlong jtotal_size,
......@@ -41,7 +32,30 @@ void DownloadMediaParserBridge::ParseMediaFile(
std::string mime_type =
base::android::ConvertJavaStringToUTF8(env, jmime_type);
// TODO(xingliu): Pass the result back to Java.
parser_->ParseMediaFile(static_cast<int64_t>(jtotal_size), mime_type,
file_path, base::DoNothing());
auto* bridge = new DownloadMediaParserBridge(
static_cast<int64_t>(jtotal_size), mime_type, file_path,
base::BindOnce(&OnMediaParsed,
base::android::ScopedJavaGlobalRef<jobject>(jcallback)));
return reinterpret_cast<intptr_t>(bridge);
}
DownloadMediaParserBridge::DownloadMediaParserBridge(
int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
DownloadMediaParser::ParseCompleteCB parse_complete_cb)
: parser_(std::make_unique<DownloadMediaParser>(
size,
mime_type,
file_path,
std::move(parse_complete_cb))) {}
DownloadMediaParserBridge::~DownloadMediaParserBridge() = default;
void DownloadMediaParserBridge::Destory(JNIEnv* env, jobject obj) {
delete this;
}
void DownloadMediaParserBridge::Start(JNIEnv* env, jobject obj) {
parser_->Start();
}
......@@ -10,6 +10,7 @@
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "chrome/browser/android/download/download_media_parser.h"
class DownloadMediaParser;
......@@ -17,21 +18,17 @@ class DownloadMediaParser;
// bridge is owned by the Java side.
class DownloadMediaParserBridge {
public:
DownloadMediaParserBridge();
DownloadMediaParserBridge(
int64_t size,
const std::string& mime_type,
const base::FilePath& file_path,
DownloadMediaParser::ParseCompleteCB parse_complete_cb);
~DownloadMediaParserBridge();
void Destory(JNIEnv* env, jobject obj);
void ParseMediaFile(JNIEnv* env,
jobject obj,
const base::android::JavaParamRef<jstring>& jmime_type,
const base::android::JavaParamRef<jstring>& jfile_path,
jlong jtotal_size,
const base::android::JavaParamRef<jobject>& jcallback);
void Start(JNIEnv* env, jobject obj);
private:
// The task runner that performs blocking disk IO. Created by this object.
scoped_refptr<base::SingleThreadTaskRunner> disk_io_task_runner_;
// The media parser that does actual jobs in a sandboxed process.
std::unique_ptr<DownloadMediaParser> parser_;
......
......@@ -27,12 +27,12 @@ class LocalMediaDataSourceFactory
~LocalMediaDataSourceFactory() override;
private:
// SafeMediaMetadataParser::MediaDataSourceFactory implementation.
std::unique_ptr<chrome::mojom::MediaDataSource> CreateMediaDataSource(
chrome::mojom::MediaDataSourcePtr* request,
MediaDataCallback media_data_callback) override;
private:
// Local downloaded media file path. This is user-defined input.
base::FilePath file_path_;
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
......
......@@ -26,6 +26,13 @@ source_set("lib") {
"//mojo/public/cpp/bindings",
]
if (is_android) {
sources += [
"media_parser_android.cc",
"media_parser_android.h",
]
}
public_deps = [
"//chrome/services/media_gallery_util/public/mojom",
"//services/service_manager/public/cpp",
......@@ -39,6 +46,25 @@ source_set("lib") {
}
}
if (is_android) {
source_set("unit_tests") {
testonly = true
sources = [
"media_parser_android_unittest.cc",
]
data = [
"//media/test/data/bear.mp4",
"//media/test/data/sfx.mp3",
]
deps = [
":lib",
"//testing/gtest",
]
}
}
service_manifest("manifest") {
name = "media_gallery_util"
source = "manifest.json"
......
// 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/services/media_gallery_util/media_parser_android.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/services/media_gallery_util/ipc_data_source.h"
#include "media/base/bind_to_current_loop.h"
#include "media/filters/android/video_frame_extractor.h"
namespace {
void OnThumbnailGenerated(
std::unique_ptr<media::VideoFrameExtractor> video_frame_extractor,
MediaParser::ExtractVideoFrameCallback extract_frame_cb,
bool success,
const std::vector<uint8_t>& data,
const media::VideoDecoderConfig& config) {
std::move(extract_frame_cb).Run(success, data, config);
}
void ExtractVideoFrameOnMediaThread(
media::DataSource* data_source,
MediaParser::ExtractVideoFrameCallback extract_frame_callback) {
auto extractor = std::make_unique<media::VideoFrameExtractor>(data_source);
extractor->Start(base::BindOnce(&OnThumbnailGenerated, std::move(extractor),
std::move(extract_frame_callback)));
}
} // namespace
MediaParserAndroid::MediaParserAndroid(
std::unique_ptr<service_manager::ServiceContextRef> service_ref)
: MediaParser(std::move(service_ref)),
media_task_runner_(
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})) {}
MediaParserAndroid::~MediaParserAndroid() = default;
void MediaParserAndroid::ExtractVideoFrame(
const std::string& mime_type,
uint32_t total_size,
chrome::mojom::MediaDataSourcePtr media_data_source,
MediaParser::ExtractVideoFrameCallback extract_frame_callback) {
data_source_ = std::make_unique<IPCDataSource>(
std::move(media_data_source), static_cast<int64_t>(total_size));
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&ExtractVideoFrameOnMediaThread, data_source_.get(),
media::BindToCurrentLoop(std::move(extract_frame_callback))));
}
// 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_SERVICES_MEDIA_GALLERY_UTIL_MEDIA_PARSER_ANDROID_H_
#define CHROME_SERVICES_MEDIA_GALLERY_UTIL_MEDIA_PARSER_ANDROID_H_
#include <memory>
#include "base/macros.h"
#include "chrome/services/media_gallery_util/media_parser.h"
namespace media {
class DataSource;
} // namespace media
// The media parser on Android that provides video thumbnail generation utility.
class MediaParserAndroid : public MediaParser {
public:
MediaParserAndroid(
std::unique_ptr<service_manager::ServiceContextRef> service_ref);
~MediaParserAndroid() override;
// MediaParser implementation.
void ExtractVideoFrame(const std::string& mime_type,
uint32_t total_size,
chrome::mojom::MediaDataSourcePtr media_data_source,
ExtractVideoFrameCallback callback) override;
private:
// The task runner to do blocking IO. The utility thread cannot be blocked.
scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
std::unique_ptr<media::DataSource> data_source_;
DISALLOW_COPY_AND_ASSIGN(MediaParserAndroid);
};
#endif // CHROME_SERVICES_MEDIA_GALLERY_UTIL_MEDIA_PARSER_ANDROID_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 "chrome/services/media_gallery_util/media_parser_android.h"
#include <memory>
#include <vector>
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h"
#include "media/base/test_data_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/service_manager/public/cpp/service_context_ref.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Used in test that do blocking reads from a local file.
class TestMediaDataSource : public chrome::mojom::MediaDataSource {
public:
TestMediaDataSource(chrome::mojom::MediaDataSourcePtr* interface,
const base::FilePath& file_path)
: file_path_(file_path), binding_(this, mojo::MakeRequest(interface)) {}
~TestMediaDataSource() override {}
private:
// chrome::mojom::MediaDataSource implementation.
void Read(int64_t position,
int64_t length,
chrome::mojom::MediaDataSource::ReadCallback callback) override {
base::File file(file_path_, base::File::Flags::FLAG_OPEN |
base::File::Flags::FLAG_READ);
auto buffer = std::vector<uint8_t>(length);
int bytes_read = file.Read(position, (char*)(buffer.data()), length);
if (bytes_read < length)
buffer.resize(bytes_read);
std::move(callback).Run(std::vector<uint8_t>(std::move(buffer)));
}
base::FilePath file_path_;
mojo::Binding<chrome::mojom::MediaDataSource> binding_;
DISALLOW_COPY_AND_ASSIGN(TestMediaDataSource);
};
class MediaParserAndroidTest : public testing::Test {
public:
MediaParserAndroidTest() : ref_factory_(base::DoNothing()) {}
~MediaParserAndroidTest() override = default;
void SetUp() override {
parser_ = std::make_unique<MediaParserAndroid>(ref_factory_.CreateRef());
}
void TearDown() override { parser_.reset(); }
protected:
MediaParserAndroid* parser() { return parser_.get(); }
private:
std::unique_ptr<MediaParserAndroid> parser_;
service_manager::ServiceContextRefFactory ref_factory_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(MediaParserAndroidTest);
};
// Test to verify encoded video frame can be extracted.
TEST_F(MediaParserAndroidTest, VideoFrameExtraction) {
auto file_path = media::GetTestDataFilePath("bear.mp4");
int64_t size = 0;
ASSERT_TRUE(base::GetFileSize(file_path, &size));
chrome::mojom::MediaDataSourcePtr data_source_ptr;
TestMediaDataSource test_data_source(&data_source_ptr, file_path);
bool result = false;
base::RunLoop run_loop;
parser()->ExtractVideoFrame(
"video/mp4", size, std::move(data_source_ptr),
base::BindLambdaForTesting([&](bool success, const std::vector<uint8_t>&,
const media::VideoDecoderConfig&) {
result = success;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(result);
}
} // namespace
......@@ -5,12 +5,17 @@
#include "chrome/services/media_gallery_util/media_parser_factory.h"
#include "base/allocator/buildflags.h"
#include "build/build_config.h"
#include "chrome/services/media_gallery_util/media_parser.h"
#include "media/base/media.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#if defined(OS_ANDROID)
#include "chrome/services/media_gallery_util/media_parser_android.h"
#endif
MediaParserFactory::MediaParserFactory(
std::unique_ptr<service_manager::ServiceContextRef> service_ref)
: service_ref_(std::move(service_ref)) {}
......@@ -23,7 +28,13 @@ void MediaParserFactory::CreateMediaParser(int64_t libyuv_cpu_flags,
media::InitializeMediaLibraryInSandbox(libyuv_cpu_flags, libavutil_cpu_flags);
chrome::mojom::MediaParserPtr media_parser_ptr;
mojo::MakeStrongBinding(std::make_unique<MediaParser>(service_ref_->Clone()),
std::unique_ptr<MediaParser> media_parser;
#if defined(OS_ANDROID)
media_parser = std::make_unique<MediaParserAndroid>(service_ref_->Clone());
#else
media_parser = std::make_unique<MediaParser>(service_ref_->Clone());
#endif
mojo::MakeStrongBinding(std::move(media_parser),
mojo::MakeRequest(&media_parser_ptr));
std::move(callback).Run(std::move(media_parser_ptr));
......
......@@ -11,6 +11,7 @@ mojom("mojom") {
]
public_deps = [
"//media/mojo/interfaces",
"//mojo/public/mojom/base",
]
}
......@@ -4,6 +4,7 @@
module chrome.mojom;
import "media/mojo/interfaces/media_types.mojom";
import "mojo/public/mojom/base/file.mojom";
import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/values.mojom";
......@@ -21,6 +22,16 @@ interface MediaParser {
MediaMetadata metadata,
array<AttachedImage> attached_images);
// Extracts one video key frame from |media_data_source|. The frame is still
// in encoded format.
[EnableIf=is_android]
ExtractVideoFrame(string mime_type,
uint32 total_size,
MediaDataSource media_data_source)
=> (bool success,
array<uint8> data,
media.mojom.VideoDecoderConfig config);
// Validates the passed media file with sanity checks, and file decoding
// for at most |decode_time| wall clock time. Returns |success| true if
// |file| appears to be a well-formed media file, false otherwise.
......@@ -63,7 +74,6 @@ struct MediaStreamInfo {
// All data are parsed from user-defined media data. The consumer of this API should filter special
// characters if needed. The encoding of strings may be UTF-8 that ffmpeg library assumes strings to
// be UTF-8 but does not validate the encoding.
// TODO(xingliu): Sanitize strings and AttachedImage in utility and browser processes.
struct MediaMetadata {
string mime_type;
......
......@@ -2984,6 +2984,7 @@ test("unit_tests") {
"//base:base_java",
"//chrome/android:app_hooks_java",
"//chrome/android:chrome_java",
"//chrome/services/media_gallery_util:unit_tests",
"//components/favicon/core/test:test_support",
"//components/gcm_driver/instance_id/android:instance_id_driver_java",
"//components/gcm_driver/instance_id/android:instance_id_driver_test_support_java",
......
......@@ -194,6 +194,8 @@ jumbo_source_set("filters") {
sources += [
"android/media_codec_audio_decoder.cc",
"android/media_codec_audio_decoder.h",
"android/video_frame_extractor.cc",
"android/video_frame_extractor.h",
]
deps += [ "//media/base/android" ]
}
......@@ -279,6 +281,10 @@ source_set("unit_tests") {
"vp9_raw_bits_reader_unittest.cc",
]
if (is_android) {
sources += [ "android/video_frame_extractor_unittest.cc" ]
}
deps = [
"//base/test:test_support",
"//media:test_support",
......
// 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/filters/android/video_frame_extractor.h"
#include "base/android/build_info.h"
#include "base/bind.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/android/media_codec_bridge_impl.h"
#include "media/base/data_source.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/blocking_url_protocol.h"
#include "media/filters/ffmpeg_bitstream_converter.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "media/filters/ffmpeg_glue.h"
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
#include "media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h"
#endif
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/base/android/extract_sps_and_pps.h"
#include "media/filters/ffmpeg_aac_bitstream_converter.h"
#include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
#endif
namespace media {
VideoFrameExtractor::VideoFrameExtractor(DataSource* data_source)
: data_source_(data_source), video_stream_index_(-1), weak_factory_(this) {}
VideoFrameExtractor::~VideoFrameExtractor() = default;
void VideoFrameExtractor::Start(VideoFrameCallback video_frame_callback) {
video_frame_callback_ = std::move(video_frame_callback);
protocol_ = std::make_unique<BlockingUrlProtocol>(
data_source_, base::BindRepeating(&VideoFrameExtractor::OnError,
weak_factory_.GetWeakPtr()));
glue_ = std::make_unique<FFmpegGlue>(protocol_.get());
// This will gradually read media data through |data_source_|.
if (!glue_->OpenContext()) {
OnError();
return;
}
// Extract the video stream.
AVFormatContext* format_context = glue_->format_context();
for (unsigned int i = 0; i < format_context->nb_streams; ++i) {
AVStream* stream = format_context->streams[i];
if (!stream)
continue;
const AVCodecParameters* codec_parameters = stream->codecpar;
const AVMediaType codec_type = codec_parameters->codec_type;
// Pick the first video stream.
if (codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_ = stream;
video_stream_index_ = i;
DCHECK_EQ(video_stream_index_, stream->index);
break;
}
}
if (!video_stream_) {
OnError();
return;
}
// Get the config for decoding the video frame later.
if (!AVStreamToVideoDecoderConfig(video_stream_, &video_config_)) {
OnError();
return;
}
auto packet = ReadVideoFrame();
ConvertPacket(packet.get());
NotifyComplete(
std::vector<uint8_t>(packet->data, packet->data + packet->size),
video_config_);
}
void VideoFrameExtractor::ConvertPacket(AVPacket* packet) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
DCHECK(video_stream_ && video_stream_->codecpar);
// TODO(xingliu): Create the bitstream converter in an utility function. This
// logic is shared with FFmpegDemuxer.
switch (video_stream_->codecpar->codec_id) {
case AV_CODEC_ID_H264:
video_config_.SetExtraData(std::vector<uint8_t>());
bitstream_converter_.reset(
new FFmpegH264ToAnnexBBitstreamConverter(video_stream_->codecpar));
break;
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
case AV_CODEC_ID_HEVC:
bitstream_converter_.reset(
new FFmpegH265ToAnnexBBitstreamConverter(video_stream_->codecpar));
break;
#endif
case AV_CODEC_ID_AAC:
bitstream_converter_.reset(
new FFmpegAACBitstreamConverter(video_stream_->codecpar));
break;
default:
break;
}
bitstream_converter_->ConvertPacket(packet);
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
}
ScopedAVPacket VideoFrameExtractor::ReadVideoFrame() {
AVFormatContext* format_context = glue_->format_context();
ScopedAVPacket packet(new AVPacket());
while (av_read_frame(format_context, packet.get()) >= 0) {
// Skip frames from streams other than video.
if (packet->stream_index != video_stream_index_)
continue;
DCHECK(packet->flags & AV_PKT_FLAG_KEY);
return packet;
}
return nullptr;
}
void VideoFrameExtractor::NotifyComplete(std::vector<uint8_t> encoded_frame,
const VideoDecoderConfig& config) {
// Return the encoded video key frame.
DCHECK(video_frame_callback_);
std::move(video_frame_callback_).Run(true, std::move(encoded_frame), config);
}
void VideoFrameExtractor::OnError() {
DCHECK(video_frame_callback_);
std::move(video_frame_callback_)
.Run(false, std::vector<uint8_t>(), VideoDecoderConfig());
}
} // 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_FILTERS_ANDROID_VIDEO_FRAME_EXTRACTOR_H_
#define MEDIA_FILTERS_ANDROID_VIDEO_FRAME_EXTRACTOR_H_
#include <stdint.h>
#include <memory>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "media/base/media_export.h"
#include "media/filters/ffmpeg_demuxer.h"
struct AVPacket;
struct AVStream;
namespace media {
class BlockingUrlProtocol;
class DataSource;
class FFmpegBitstreamConverter;
class FFmpegGlue;
class VideoDecoderConfig;
// This class synchronously extracts a video key frame. Should be used in
// sandboxed process since the media data is user input.
// Work flow:
// 1. Demuxes one video frame with FFMPEG, into an AVPacket.
// 2. Adds the bitstream filter, so decoder can understand the input data.
// 3. Returns the encoded video key frame and the decoding configuration for
// later decoding. On Android, currently decoding needs to happen in privileged
// process to access low level graphic card driver on disk.
class MEDIA_EXPORT VideoFrameExtractor {
public:
using VideoFrameCallback =
base::OnceCallback<void(bool success,
const std::vector<uint8_t>& data,
const VideoDecoderConfig& decoder_config)>;
explicit VideoFrameExtractor(DataSource* data_source);
~VideoFrameExtractor();
// Starts to retrieve thumbnail from video frame.
void Start(VideoFrameCallback video_frame_callback);
private:
// Reads one video frame from video stream.
ScopedAVPacket ReadVideoFrame();
// Converts the video frame to something that the decoder can understand.
void ConvertPacket(AVPacket* packet);
// Called when video frame is successfully extracted.
void NotifyComplete(std::vector<uint8_t> encoded_frame,
const VideoDecoderConfig& config);
// Called when error happens.
void OnError();
// Objects to read video data.
DataSource* data_source_;
std::unique_ptr<BlockingUrlProtocol> protocol_;
std::unique_ptr<FFmpegGlue> glue_;
// FFMPEG related objects to prepare video frame to decode.
ScopedAVPacket packet_;
int video_stream_index_;
AVStream* video_stream_ = nullptr;
VideoDecoderConfig video_config_;
std::unique_ptr<FFmpegBitstreamConverter> bitstream_converter_;
VideoFrameCallback video_frame_callback_;
base::WeakPtrFactory<VideoFrameExtractor> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameExtractor);
};
} // namespace media
#endif // MEDIA_FILTERS_ANDROID_VIDEO_FRAME_EXTRACTOR_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 "media/filters/android/video_frame_extractor.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "media/base/test_data_util.h"
#include "media/filters/file_data_source.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
struct ExtractVideoFrameResult {
bool success = false;
std::vector<uint8_t> encoded_frame;
VideoDecoderConfig decoder_config;
};
void OnThumbnailGenerated(ExtractVideoFrameResult* result,
base::RepeatingClosure quit_closure,
bool success,
const std::vector<uint8_t>& encoded_frame,
const VideoDecoderConfig& decoder_config) {
result->success = success;
result->encoded_frame = encoded_frame;
result->decoder_config = decoder_config;
quit_closure.Run();
}
class VideoFrameExtractorTest : public testing::Test {
public:
VideoFrameExtractorTest() {}
~VideoFrameExtractorTest() override {}
protected:
void SetUp() override {
data_source_ = std::make_unique<FileDataSource>();
CHECK(data_source_->Initialize(GetTestDataFilePath("bear.mp4")));
extractor_ = std::make_unique<VideoFrameExtractor>(data_source_.get());
}
void TearDown() override { extractor_.reset(); }
VideoFrameExtractor* extractor() { return extractor_.get(); }
private:
std::unique_ptr<FileDataSource> data_source_;
std::unique_ptr<VideoFrameExtractor> extractor_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameExtractorTest);
};
// Verifies the encoded video frame is extracted correctly.
TEST_F(VideoFrameExtractorTest, ExtractVideoFrame) {
ExtractVideoFrameResult result;
base::RunLoop loop;
extractor()->Start(
base::BindOnce(&OnThumbnailGenerated, &result, loop.QuitClosure()));
loop.Run();
EXPECT_TRUE(result.success);
EXPECT_GT(result.encoded_frame.size(), 0u);
EXPECT_EQ(result.decoder_config.codec(), VideoCodec::kCodecH264);
}
} // namespace
} // 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