mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-23 00:47:31 +09:00
Remove Gonk build directories
This commit is contained in:
parent
c1cd55bc25
commit
66ea8f0645
355 changed files with 15 additions and 139695 deletions
|
|
@ -1,522 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MediaManager.h"
|
||||
#include "MediaPermissionGonk.h"
|
||||
|
||||
#include "nsArray.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDOMNavigatorUserMedia.h"
|
||||
#include "nsIStringEnumerator.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsQueryObject.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsTArray.h"
|
||||
#include "GetUserMediaRequest.h"
|
||||
#include "mozilla/dom/PBrowserChild.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "mozilla/dom/MediaStreamError.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsArrayUtils.h"
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "mozilla/dom/PermissionMessageUtils.h"
|
||||
|
||||
#define AUDIO_PERMISSION_NAME "audio-capture"
|
||||
#define VIDEO_PERMISSION_NAME "video-capture"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static MediaPermissionManager *gMediaPermMgr = nullptr;
|
||||
|
||||
static void
|
||||
CreateDeviceNameList(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
|
||||
nsTArray<nsString> &aDeviceNameList)
|
||||
{
|
||||
for (uint32_t i = 0; i < aDevices.Length(); ++i) {
|
||||
nsString name;
|
||||
nsresult rv = aDevices[i]->GetName(name);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
aDeviceNameList.AppendElement(name);
|
||||
}
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIMediaDevice>
|
||||
FindDeviceByName(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
|
||||
const nsAString &aDeviceName)
|
||||
{
|
||||
for (uint32_t i = 0; i < aDevices.Length(); ++i) {
|
||||
nsCOMPtr<nsIMediaDevice> device = aDevices[i];
|
||||
nsString deviceName;
|
||||
device->GetName(deviceName);
|
||||
if (deviceName.Equals(aDeviceName)) {
|
||||
return device.forget();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Helper function for notifying permission granted
|
||||
static nsresult
|
||||
NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIMutableArray> array = nsArray::Create();
|
||||
|
||||
for (uint32_t i = 0; i < aDevices.Length(); ++i) {
|
||||
rv = array->AppendElement(aDevices.ElementAt(i), /*weak =*/ false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
|
||||
|
||||
return obs->NotifyObservers(array, "getUserMedia:response:allow",
|
||||
aCallID.BeginReading());
|
||||
}
|
||||
|
||||
// Helper function for notifying permision denial or error
|
||||
static nsresult
|
||||
NotifyPermissionDeny(const nsAString &aCallID, const nsAString &aErrorMsg)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsISupportsString> supportsString =
|
||||
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = supportsString->SetData(aErrorMsg);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
|
||||
|
||||
return obs->NotifyObservers(supportsString, "getUserMedia:response:deny",
|
||||
aCallID.BeginReading());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* MediaPermissionRequest will send a prompt ipdl request to b2g process according
|
||||
* to its owned type.
|
||||
*/
|
||||
class MediaPermissionRequest : public nsIContentPermissionRequest
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICONTENTPERMISSIONREQUEST
|
||||
|
||||
MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices);
|
||||
|
||||
already_AddRefed<nsPIDOMWindowInner> GetOwner();
|
||||
|
||||
protected:
|
||||
virtual ~MediaPermissionRequest() {}
|
||||
|
||||
private:
|
||||
nsresult DoAllow(const nsString &audioDevice, const nsString &videoDevice);
|
||||
|
||||
bool mAudio; // Request for audio permission
|
||||
bool mVideo; // Request for video permission
|
||||
RefPtr<dom::GetUserMediaRequest> mRequest;
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > mAudioDevices; // candidate audio devices
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > mVideoDevices; // candidate video devices
|
||||
nsCOMPtr<nsIContentPermissionRequester> mRequester;
|
||||
};
|
||||
|
||||
// MediaPermissionRequest
|
||||
NS_IMPL_ISUPPORTS(MediaPermissionRequest, nsIContentPermissionRequest)
|
||||
|
||||
MediaPermissionRequest::MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
|
||||
: mRequest(aRequest)
|
||||
{
|
||||
dom::MediaStreamConstraints constraints;
|
||||
mRequest->GetConstraints(constraints);
|
||||
|
||||
mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
|
||||
mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
|
||||
|
||||
for (uint32_t i = 0; i < aDevices.Length(); ++i) {
|
||||
nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
|
||||
nsAutoString deviceType;
|
||||
device->GetType(deviceType);
|
||||
if (mAudio && deviceType.EqualsLiteral("audio")) {
|
||||
mAudioDevices.AppendElement(device);
|
||||
}
|
||||
if (mVideo && deviceType.EqualsLiteral("video")) {
|
||||
mVideoDevices.AppendElement(device);
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
|
||||
mRequester = new nsContentPermissionRequester(window);
|
||||
}
|
||||
|
||||
// nsIContentPermissionRequest methods
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::GetTypes(nsIArray** aTypes)
|
||||
{
|
||||
nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||||
//XXX append device list
|
||||
if (mAudio) {
|
||||
nsTArray<nsString> audioDeviceNames;
|
||||
CreateDeviceNameList(mAudioDevices, audioDeviceNames);
|
||||
nsCOMPtr<nsISupports> AudioType =
|
||||
new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
|
||||
NS_LITERAL_CSTRING("unused"),
|
||||
audioDeviceNames);
|
||||
types->AppendElement(AudioType, false);
|
||||
}
|
||||
if (mVideo) {
|
||||
nsTArray<nsString> videoDeviceNames;
|
||||
CreateDeviceNameList(mVideoDevices, videoDeviceNames);
|
||||
nsCOMPtr<nsISupports> VideoType =
|
||||
new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
|
||||
NS_LITERAL_CSTRING("unused"),
|
||||
videoDeviceNames);
|
||||
types->AppendElement(VideoType, false);
|
||||
}
|
||||
NS_IF_ADDREF(*aTypes = types);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||||
nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
|
||||
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
|
||||
|
||||
NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingWindow);
|
||||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||||
nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
|
||||
window.forget(aRequestingWindow);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::GetElement(nsIDOMElement** aRequestingElement)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestingElement);
|
||||
*aRequestingElement = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::Cancel()
|
||||
{
|
||||
nsString callID;
|
||||
mRequest->GetCallID(callID);
|
||||
NotifyPermissionDeny(callID, NS_LITERAL_STRING("SecurityError"));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::Allow(JS::HandleValue aChoices)
|
||||
{
|
||||
// check if JS object
|
||||
if (!aChoices.isObject()) {
|
||||
MOZ_ASSERT(false, "Not a correct format of PermissionChoice");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
// iterate through audio-capture and video-capture
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(&aChoices.toObject())) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
|
||||
JS::Rooted<JS::Value> v(cx);
|
||||
|
||||
// get selected audio device name
|
||||
nsString audioDevice;
|
||||
if (mAudio) {
|
||||
if (!JS_GetProperty(cx, obj, AUDIO_PERMISSION_NAME, &v) || !v.isString()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsAutoJSString deviceName;
|
||||
if (!deviceName.init(cx, v)) {
|
||||
MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
audioDevice = deviceName;
|
||||
}
|
||||
|
||||
// get selected video device name
|
||||
nsString videoDevice;
|
||||
if (mVideo) {
|
||||
if (!JS_GetProperty(cx, obj, VIDEO_PERMISSION_NAME, &v) || !v.isString()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsAutoJSString deviceName;
|
||||
if (!deviceName.init(cx, v)) {
|
||||
MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
videoDevice = deviceName;
|
||||
}
|
||||
|
||||
return DoAllow(audioDevice, videoDevice);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequester);
|
||||
|
||||
nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
|
||||
requester.forget(aRequester);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaPermissionRequest::DoAllow(const nsString &audioDevice,
|
||||
const nsString &videoDevice)
|
||||
{
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > selectedDevices;
|
||||
if (mAudio) {
|
||||
nsCOMPtr<nsIMediaDevice> device =
|
||||
FindDeviceByName(mAudioDevices, audioDevice);
|
||||
if (device) {
|
||||
selectedDevices.AppendElement(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (mVideo) {
|
||||
nsCOMPtr<nsIMediaDevice> device =
|
||||
FindDeviceByName(mVideoDevices, videoDevice);
|
||||
if (device) {
|
||||
selectedDevices.AppendElement(device);
|
||||
}
|
||||
}
|
||||
|
||||
nsString callID;
|
||||
mRequest->GetCallID(callID);
|
||||
return NotifyPermissionAllow(callID, selectedDevices);
|
||||
}
|
||||
|
||||
already_AddRefed<nsPIDOMWindowInner>
|
||||
MediaPermissionRequest::GetOwner()
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||||
nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
|
||||
return window.forget();
|
||||
}
|
||||
|
||||
// Success callback for MediaManager::GetUserMediaDevices().
|
||||
class MediaDeviceSuccessCallback: public nsIGetUserMediaDevicesSuccessCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIGETUSERMEDIADEVICESSUCCESSCALLBACK
|
||||
|
||||
explicit MediaDeviceSuccessCallback(RefPtr<dom::GetUserMediaRequest> &aRequest)
|
||||
: mRequest(aRequest) {}
|
||||
|
||||
protected:
|
||||
virtual ~MediaDeviceSuccessCallback() {}
|
||||
|
||||
private:
|
||||
nsresult DoPrompt(RefPtr<MediaPermissionRequest> &req);
|
||||
RefPtr<dom::GetUserMediaRequest> mRequest;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(MediaDeviceSuccessCallback, nsIGetUserMediaDevicesSuccessCallback)
|
||||
|
||||
// nsIGetUserMediaDevicesSuccessCallback method
|
||||
NS_IMETHODIMP
|
||||
MediaDeviceSuccessCallback::OnSuccess(nsIVariant* aDevices)
|
||||
{
|
||||
nsIID elementIID;
|
||||
uint16_t elementType;
|
||||
void* rawArray;
|
||||
uint32_t arrayLen;
|
||||
|
||||
nsresult rv;
|
||||
rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (elementType != nsIDataType::VTYPE_INTERFACE) {
|
||||
free(rawArray);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Create array for nsIMediaDevice
|
||||
nsTArray<nsCOMPtr<nsIMediaDevice> > devices;
|
||||
|
||||
nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
|
||||
for (uint32_t i = 0; i < arrayLen; ++i) {
|
||||
nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
|
||||
devices.AppendElement(device);
|
||||
NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer
|
||||
}
|
||||
free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
|
||||
|
||||
// Send MediaPermissionRequest
|
||||
RefPtr<MediaPermissionRequest> req = new MediaPermissionRequest(mRequest, devices);
|
||||
rv = DoPrompt(req);
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Trigger permission prompt UI
|
||||
nsresult
|
||||
MediaDeviceSuccessCallback::DoPrompt(RefPtr<MediaPermissionRequest> &req)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowInner> window(req->GetOwner());
|
||||
return dom::nsContentPermissionUtils::AskPermission(req, window);
|
||||
}
|
||||
|
||||
// Error callback for MediaManager::GetUserMediaDevices()
|
||||
class MediaDeviceErrorCallback: public nsIDOMGetUserMediaErrorCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK
|
||||
|
||||
explicit MediaDeviceErrorCallback(const nsAString &aCallID)
|
||||
: mCallID(aCallID) {}
|
||||
|
||||
protected:
|
||||
virtual ~MediaDeviceErrorCallback() {}
|
||||
|
||||
private:
|
||||
const nsString mCallID;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(MediaDeviceErrorCallback, nsIDOMGetUserMediaErrorCallback)
|
||||
|
||||
// nsIDOMGetUserMediaErrorCallback method
|
||||
NS_IMETHODIMP
|
||||
MediaDeviceErrorCallback::OnError(nsISupports* aError)
|
||||
{
|
||||
RefPtr<MediaStreamError> error = do_QueryObject(aError);
|
||||
if (!error) {
|
||||
return NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
nsString name;
|
||||
error->GetName(name);
|
||||
return NotifyPermissionDeny(mCallID, name);
|
||||
}
|
||||
|
||||
} // namespace anonymous
|
||||
|
||||
// MediaPermissionManager
|
||||
NS_IMPL_ISUPPORTS(MediaPermissionManager, nsIObserver)
|
||||
|
||||
MediaPermissionManager*
|
||||
MediaPermissionManager::GetInstance()
|
||||
{
|
||||
if (!gMediaPermMgr) {
|
||||
gMediaPermMgr = new MediaPermissionManager();
|
||||
}
|
||||
|
||||
return gMediaPermMgr;
|
||||
}
|
||||
|
||||
MediaPermissionManager::MediaPermissionManager()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->AddObserver(this, "getUserMedia:request", false);
|
||||
obs->AddObserver(this, "xpcom-shutdown", false);
|
||||
}
|
||||
}
|
||||
|
||||
MediaPermissionManager::~MediaPermissionManager()
|
||||
{
|
||||
this->Deinit();
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaPermissionManager::Deinit()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->RemoveObserver(this, "getUserMedia:request");
|
||||
obs->RemoveObserver(this, "xpcom-shutdown");
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIObserver method
|
||||
NS_IMETHODIMP
|
||||
MediaPermissionManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
nsresult rv;
|
||||
if (!strcmp(aTopic, "getUserMedia:request")) {
|
||||
RefPtr<dom::GetUserMediaRequest> req =
|
||||
static_cast<dom::GetUserMediaRequest*>(aSubject);
|
||||
rv = HandleRequest(req);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
nsString callID;
|
||||
req->GetCallID(callID);
|
||||
NotifyPermissionDeny(callID, NS_LITERAL_STRING("unable to enumerate media device"));
|
||||
}
|
||||
} else if (!strcmp(aTopic, "xpcom-shutdown")) {
|
||||
rv = this->Deinit();
|
||||
} else {
|
||||
// not reachable
|
||||
rv = NS_ERROR_FAILURE;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Handle GetUserMediaRequest, query available media device first.
|
||||
nsresult
|
||||
MediaPermissionManager::HandleRequest(RefPtr<dom::GetUserMediaRequest> &req)
|
||||
{
|
||||
nsString callID;
|
||||
req->GetCallID(callID);
|
||||
uint64_t innerWindowID = req->InnerWindowID();
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> innerWindow =
|
||||
nsGlobalWindow::GetInnerWindowWithId(innerWindowID)->AsInner();
|
||||
if (!innerWindow) {
|
||||
MOZ_ASSERT(false, "No inner window");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess =
|
||||
new MediaDeviceSuccessCallback(req);
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError =
|
||||
new MediaDeviceErrorCallback(callID);
|
||||
|
||||
dom::MediaStreamConstraints constraints;
|
||||
req->GetConstraints(constraints);
|
||||
|
||||
RefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
|
||||
nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints,
|
||||
onSuccess, onError,
|
||||
innerWindowID, callID);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_MEDIA_MEDIAPERMISSIONGONK_H
|
||||
#define DOM_MEDIA_MEDIAPERMISSIONGONK_H
|
||||
|
||||
#include "nsError.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "GetUserMediaRequest.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* The observer to create the MediaPermissionMgr. This is the entry point of
|
||||
* permission request on b2g.
|
||||
*/
|
||||
class MediaPermissionManager : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
static MediaPermissionManager* GetInstance();
|
||||
|
||||
protected:
|
||||
virtual ~MediaPermissionManager();
|
||||
|
||||
private:
|
||||
MediaPermissionManager();
|
||||
nsresult Deinit();
|
||||
nsresult HandleRequest(RefPtr<dom::GetUserMediaRequest> &req);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // DOM_MEDIA_MEDIAPERMISSIONGONK_H
|
||||
|
||||
|
|
@ -169,11 +169,6 @@ IPDL_SOURCES += [
|
|||
'webrtc/PWebrtcGlobal.ipdl'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_B2G']:
|
||||
EXPORTS.mozilla += [
|
||||
'MediaPermissionGonk.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'AudioStreamTrack.h',
|
||||
'AudioTrack.h',
|
||||
|
|
@ -270,11 +265,6 @@ UNIFIED_SOURCES += [
|
|||
if CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [ 'ThreadPoolCOMListener.cpp' ]
|
||||
|
||||
if CONFIG['MOZ_B2G']:
|
||||
SOURCES += [
|
||||
'MediaPermissionGonk.cpp',
|
||||
]
|
||||
|
||||
# DecoderTraits.cpp needs to be built separately because of Mac OS X headers.
|
||||
SOURCES += [
|
||||
'DecoderTraits.cpp',
|
||||
|
|
@ -325,9 +315,6 @@ else:
|
|||
if CONFIG['ANDROID_VERSION'] > '15':
|
||||
DEFINES['MOZ_OMX_WEBM_DECODER'] = True
|
||||
|
||||
if CONFIG['MOZ_GONK_MEDIACODEC']:
|
||||
DEFINES['MOZ_GONK_MEDIACODEC'] = True
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
# Suppress some GCC warnings being treated as errors:
|
||||
|
|
|
|||
|
|
@ -1,268 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "MediaCodecProxy.h"
|
||||
#include <OMX_IVCommon.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <ICrypto.h>
|
||||
#include "GonkAudioDecoderManager.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "stagefright/MediaBuffer.h"
|
||||
#include "stagefright/MetaData.h"
|
||||
#include "stagefright/MediaErrors.h"
|
||||
#include <stagefright/foundation/AMessage.h>
|
||||
#include <stagefright/foundation/ALooper.h>
|
||||
#include "media/openmax/OMX_Audio.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaInfo.h"
|
||||
|
||||
#define CODECCONFIG_TIMEOUT_US 10000LL
|
||||
#define READ_OUTPUT_BUFFER_TIMEOUT_US 0LL
|
||||
|
||||
#include <android/log.h>
|
||||
#define GADM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkAudioDecoderManager", __VA_ARGS__)
|
||||
|
||||
#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
using namespace android;
|
||||
typedef android::MediaCodecProxy MediaCodecProxy;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
GonkAudioDecoderManager::GonkAudioDecoderManager(const AudioInfo& aConfig)
|
||||
: mAudioChannels(aConfig.mChannels)
|
||||
, mAudioRate(aConfig.mRate)
|
||||
, mAudioProfile(aConfig.mProfile)
|
||||
, mAudioCompactor(mAudioQueue)
|
||||
{
|
||||
MOZ_COUNT_CTOR(GonkAudioDecoderManager);
|
||||
MOZ_ASSERT(mAudioChannels);
|
||||
mCodecSpecificData = aConfig.mCodecSpecificConfig;
|
||||
mMimeType = aConfig.mMimeType;
|
||||
}
|
||||
|
||||
GonkAudioDecoderManager::~GonkAudioDecoderManager()
|
||||
{
|
||||
MOZ_COUNT_DTOR(GonkAudioDecoderManager);
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::InitPromise>
|
||||
GonkAudioDecoderManager::Init()
|
||||
{
|
||||
if (InitMediaCodecProxy()) {
|
||||
return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__);
|
||||
} else {
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GonkAudioDecoderManager::InitMediaCodecProxy()
|
||||
{
|
||||
status_t rv = OK;
|
||||
if (!InitLoopers(MediaData::AUDIO_DATA)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false);
|
||||
if (!mDecoder.get()) {
|
||||
return false;
|
||||
}
|
||||
if (!mDecoder->AllocateAudioMediaCodec())
|
||||
{
|
||||
mDecoder = nullptr;
|
||||
return false;
|
||||
}
|
||||
sp<AMessage> format = new AMessage;
|
||||
// Fixed values
|
||||
GADM_LOG("Configure audio mime type:%s, chan no:%d, sample-rate:%d, profile:%d",
|
||||
mMimeType.get(), mAudioChannels, mAudioRate, mAudioProfile);
|
||||
format->setString("mime", mMimeType.get());
|
||||
format->setInt32("channel-count", mAudioChannels);
|
||||
format->setInt32("sample-rate", mAudioRate);
|
||||
format->setInt32("aac-profile", mAudioProfile);
|
||||
status_t err = mDecoder->configure(format, nullptr, nullptr, 0);
|
||||
if (err != OK || !mDecoder->Prepare()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mMimeType.EqualsLiteral("audio/mp4a-latm")) {
|
||||
rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0,
|
||||
android::MediaCodec::BUFFER_FLAG_CODECCONFIG,
|
||||
CODECCONFIG_TIMEOUT_US);
|
||||
}
|
||||
|
||||
if (rv == OK) {
|
||||
return true;
|
||||
} else {
|
||||
GADM_LOG("Failed to input codec specific data!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkAudioDecoderManager::CreateAudioData(MediaBuffer* aBuffer, int64_t aStreamOffset)
|
||||
{
|
||||
if (!(aBuffer != nullptr && aBuffer->data() != nullptr)) {
|
||||
GADM_LOG("Audio Buffer is not valid!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
int64_t timeUs;
|
||||
if (!aBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (aBuffer->range_length() == 0) {
|
||||
// Some decoders may return spurious empty buffers that we just want to ignore
|
||||
// quoted from Android's AwesomePlayer.cpp
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (mLastTime > timeUs) {
|
||||
GADM_LOG("Output decoded sample time is revert. time=%lld", timeUs);
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
mLastTime = timeUs;
|
||||
|
||||
const uint8_t *data = static_cast<const uint8_t*>(aBuffer->data());
|
||||
size_t dataOffset = aBuffer->range_offset();
|
||||
size_t size = aBuffer->range_length();
|
||||
|
||||
uint32_t frames = size / (2 * mAudioChannels);
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(frames, mAudioRate);
|
||||
if (!duration.isValid()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
typedef AudioCompactor::NativeCopy OmxCopy;
|
||||
mAudioCompactor.Push(aStreamOffset,
|
||||
timeUs,
|
||||
mAudioRate,
|
||||
frames,
|
||||
mAudioChannels,
|
||||
OmxCopy(data+dataOffset,
|
||||
size,
|
||||
mAudioChannels));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkAudioDecoderManager::Output(int64_t aStreamOffset,
|
||||
RefPtr<MediaData>& aOutData)
|
||||
{
|
||||
aOutData = nullptr;
|
||||
if (mAudioQueue.GetSize() > 0) {
|
||||
aOutData = mAudioQueue.PopFront();
|
||||
return mAudioQueue.AtEndOfStream() ? NS_ERROR_ABORT : NS_OK;
|
||||
}
|
||||
|
||||
status_t err;
|
||||
MediaBuffer* audioBuffer = nullptr;
|
||||
err = mDecoder->Output(&audioBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US);
|
||||
AutoReleaseMediaBuffer a(audioBuffer, mDecoder.get());
|
||||
|
||||
switch (err) {
|
||||
case OK:
|
||||
{
|
||||
nsresult rv = CreateAudioData(audioBuffer, aStreamOffset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
break;
|
||||
}
|
||||
case android::INFO_FORMAT_CHANGED:
|
||||
{
|
||||
// If the format changed, update our cached info.
|
||||
GADM_LOG("Decoder format changed");
|
||||
sp<AMessage> audioCodecFormat;
|
||||
|
||||
if (mDecoder->getOutputFormat(&audioCodecFormat) != OK ||
|
||||
audioCodecFormat == nullptr) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
int32_t codec_channel_count = 0;
|
||||
int32_t codec_sample_rate = 0;
|
||||
|
||||
if (!audioCodecFormat->findInt32("channel-count", &codec_channel_count) ||
|
||||
!audioCodecFormat->findInt32("sample-rate", &codec_sample_rate)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Update AudioInfo
|
||||
AudioConfig::ChannelLayout layout(codec_channel_count);
|
||||
if (!layout.IsValid()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mAudioChannels = codec_channel_count;
|
||||
mAudioRate = codec_sample_rate;
|
||||
|
||||
return Output(aStreamOffset, aOutData);
|
||||
}
|
||||
case android::INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
{
|
||||
GADM_LOG("Info Output Buffers Changed");
|
||||
if (mDecoder->UpdateOutputBuffers()) {
|
||||
return Output(aStreamOffset, aOutData);
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
case -EAGAIN:
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
case android::ERROR_END_OF_STREAM:
|
||||
{
|
||||
GADM_LOG("Got EOS frame!");
|
||||
nsresult rv = CreateAudioData(audioBuffer, aStreamOffset);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_ABORT);
|
||||
MOZ_ASSERT(mAudioQueue.GetSize() > 0);
|
||||
mAudioQueue.Finish();
|
||||
break;
|
||||
}
|
||||
case -ETIMEDOUT:
|
||||
{
|
||||
GADM_LOG("Timeout. can try again next time");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
default:
|
||||
{
|
||||
GADM_LOG("Decoder failed, err=%d", err);
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (mAudioQueue.GetSize() > 0) {
|
||||
aOutData = mAudioQueue.PopFront();
|
||||
// Return NS_ERROR_ABORT at the last sample.
|
||||
return mAudioQueue.AtEndOfStream() ? NS_ERROR_ABORT : NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
void
|
||||
GonkAudioDecoderManager::ProcessFlush()
|
||||
{
|
||||
GADM_LOG("FLUSH<<<");
|
||||
mAudioQueue.Reset();
|
||||
GADM_LOG(">>>FLUSH");
|
||||
GonkDecoderManager::ProcessFlush();
|
||||
}
|
||||
|
||||
void
|
||||
GonkAudioDecoderManager::ResetEOS()
|
||||
{
|
||||
GADM_LOG("ResetEOS(<<<");
|
||||
mAudioQueue.Reset();
|
||||
GADM_LOG(">>>ResetEOS(");
|
||||
GonkDecoderManager::ResetEOS();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#if !defined(GonkAudioDecoderManager_h_)
|
||||
#define GonkAudioDecoderManager_h_
|
||||
|
||||
#include "AudioCompactor.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "GonkMediaDataDecoder.h"
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace android {
|
||||
class MOZ_EXPORT MediaBuffer;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class GonkAudioDecoderManager : public GonkDecoderManager {
|
||||
typedef android::MediaCodecProxy MediaCodecProxy;
|
||||
public:
|
||||
GonkAudioDecoderManager(const AudioInfo& aConfig);
|
||||
|
||||
virtual ~GonkAudioDecoderManager();
|
||||
|
||||
RefPtr<InitPromise> Init() override;
|
||||
|
||||
nsresult Output(int64_t aStreamOffset,
|
||||
RefPtr<MediaData>& aOutput) override;
|
||||
|
||||
void ProcessFlush() override;
|
||||
virtual void ResetEOS() override;
|
||||
|
||||
const char* GetDescriptionName() const override
|
||||
{
|
||||
return "gonk audio decoder";
|
||||
}
|
||||
|
||||
private:
|
||||
bool InitMediaCodecProxy();
|
||||
|
||||
nsresult CreateAudioData(MediaBuffer* aBuffer, int64_t aStreamOffset);
|
||||
|
||||
uint32_t mAudioChannels;
|
||||
uint32_t mAudioRate;
|
||||
const uint32_t mAudioProfile;
|
||||
|
||||
MediaQueue<AudioData> mAudioQueue;
|
||||
|
||||
AudioCompactor mAudioCompactor;
|
||||
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GonkAudioDecoderManager_h_
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GonkDecoderModule.h"
|
||||
#include "GonkVideoDecoderManager.h"
|
||||
#include "GonkAudioDecoderManager.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "GonkMediaDataDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
GonkDecoderModule::GonkDecoderModule()
|
||||
{
|
||||
}
|
||||
|
||||
GonkDecoderModule::~GonkDecoderModule()
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
GonkDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
|
||||
{
|
||||
RefPtr<MediaDataDecoder> decoder =
|
||||
new GonkMediaDataDecoder(new GonkVideoDecoderManager(aParams.mImageContainer, aParams.VideoConfig()),
|
||||
aParams.mCallback);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
GonkDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
|
||||
{
|
||||
RefPtr<MediaDataDecoder> decoder =
|
||||
new GonkMediaDataDecoder(new GonkAudioDecoderManager(aParams.AudioConfig()),
|
||||
aParams.mCallback);
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
PlatformDecoderModule::ConversionRequired
|
||||
GonkDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
|
||||
{
|
||||
if (aConfig.IsVideo()) {
|
||||
return ConversionRequired::kNeedAnnexB;
|
||||
} else {
|
||||
return ConversionRequired::kNeedNone;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GonkDecoderModule::SupportsMimeType(const nsACString& aMimeType,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const
|
||||
{
|
||||
return aMimeType.EqualsLiteral("audio/mp4a-latm") ||
|
||||
aMimeType.EqualsLiteral("audio/3gpp") ||
|
||||
aMimeType.EqualsLiteral("audio/amr-wb") ||
|
||||
aMimeType.EqualsLiteral("audio/mpeg") ||
|
||||
aMimeType.EqualsLiteral("video/mp4") ||
|
||||
aMimeType.EqualsLiteral("video/mp4v-es") ||
|
||||
aMimeType.EqualsLiteral("video/avc") ||
|
||||
aMimeType.EqualsLiteral("video/3gpp");
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#if !defined(GonkPlatformDecoderModule_h_)
|
||||
#define GonkPlatformDecoderModule_h_
|
||||
|
||||
#include "PlatformDecoderModule.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class GonkDecoderModule : public PlatformDecoderModule {
|
||||
public:
|
||||
GonkDecoderModule();
|
||||
virtual ~GonkDecoderModule();
|
||||
|
||||
// Decode thread.
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
CreateVideoDecoder(const CreateDecoderParams& aParams) override;
|
||||
|
||||
// Decode thread.
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
CreateAudioDecoder(const CreateDecoderParams& aParams) override;
|
||||
|
||||
ConversionRequired
|
||||
DecoderNeedsConversion(const TrackInfo& aConfig) const override;
|
||||
|
||||
bool SupportsMimeType(const nsACString& aMimeType,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const override;
|
||||
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -1,385 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GonkMediaDataDecoder.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsTArray.h"
|
||||
#include "MediaCodecProxy.h"
|
||||
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
#include <android/log.h>
|
||||
#define GMDD_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkMediaDataDecoder", __VA_ARGS__)
|
||||
#define INPUT_TIMEOUT_US 0LL // Don't wait for buffer if none is available.
|
||||
#define MIN_QUEUED_SAMPLES 2
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <utils/AndroidThreads.h>
|
||||
#endif
|
||||
|
||||
#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
bool
|
||||
GonkDecoderManager::InitLoopers(MediaData::Type aType)
|
||||
{
|
||||
MOZ_ASSERT(mDecodeLooper.get() == nullptr && mTaskLooper.get() == nullptr);
|
||||
MOZ_ASSERT(aType == MediaData::VIDEO_DATA || aType == MediaData::AUDIO_DATA);
|
||||
|
||||
const char* suffix = (aType == MediaData::VIDEO_DATA ? "video" : "audio");
|
||||
mDecodeLooper = new ALooper;
|
||||
android::AString name("MediaCodecProxy/");
|
||||
name.append(suffix);
|
||||
mDecodeLooper->setName(name.c_str());
|
||||
|
||||
mTaskLooper = new ALooper;
|
||||
name.setTo("GonkDecoderManager/");
|
||||
name.append(suffix);
|
||||
mTaskLooper->setName(name.c_str());
|
||||
mTaskLooper->registerHandler(this);
|
||||
|
||||
#ifdef DEBUG
|
||||
sp<AMessage> findThreadId(new AMessage(kNotifyFindLooperId, id()));
|
||||
findThreadId->post();
|
||||
#endif
|
||||
|
||||
return mDecodeLooper->start() == OK && mTaskLooper->start() == OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkDecoderManager::Input(MediaRawData* aSample)
|
||||
{
|
||||
RefPtr<MediaRawData> sample;
|
||||
|
||||
if (aSample) {
|
||||
sample = aSample;
|
||||
} else {
|
||||
// It means EOS with empty sample.
|
||||
sample = new MediaRawData();
|
||||
}
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mQueuedSamples.AppendElement(sample);
|
||||
}
|
||||
|
||||
sp<AMessage> input = new AMessage(kNotifyProcessInput, id());
|
||||
if (!aSample) {
|
||||
input->setInt32("input-eos", 1);
|
||||
}
|
||||
input->post();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
GonkDecoderManager::ProcessQueuedSamples()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskLooper());
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
status_t rv;
|
||||
while (mQueuedSamples.Length()) {
|
||||
RefPtr<MediaRawData> data = mQueuedSamples.ElementAt(0);
|
||||
rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()),
|
||||
data->Size(),
|
||||
data->mTime,
|
||||
0,
|
||||
INPUT_TIMEOUT_US);
|
||||
if (rv == OK) {
|
||||
mQueuedSamples.RemoveElementAt(0);
|
||||
mWaitOutput.AppendElement(WaitOutputInfo(data->mOffset, data->mTime,
|
||||
/* eos */ data->Data() == nullptr));
|
||||
} else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
|
||||
// In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
|
||||
// buffer on time.
|
||||
break;
|
||||
} else {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return mQueuedSamples.Length();
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkDecoderManager::Flush()
|
||||
{
|
||||
if (mDecoder == nullptr) {
|
||||
GMDD_LOG("Decoder is not initialized");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (!mInitPromise.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mQueuedSamples.Clear();
|
||||
}
|
||||
|
||||
MonitorAutoLock lock(mFlushMonitor);
|
||||
mIsFlushing = true;
|
||||
sp<AMessage> flush = new AMessage(kNotifyProcessFlush, id());
|
||||
flush->post();
|
||||
while (mIsFlushing) {
|
||||
lock.Wait();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkDecoderManager::Shutdown()
|
||||
{
|
||||
if (mDecoder.get()) {
|
||||
mDecoder->stop();
|
||||
mDecoder->ReleaseMediaResources();
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
|
||||
mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
size_t
|
||||
GonkDecoderManager::NumQueuedSamples()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
return mQueuedSamples.Length();
|
||||
}
|
||||
|
||||
void
|
||||
GonkDecoderManager::ProcessInput(bool aEndOfStream)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskLooper());
|
||||
|
||||
status_t rv = ProcessQueuedSamples();
|
||||
if (rv >= 0) {
|
||||
if (!aEndOfStream && rv <= MIN_QUEUED_SAMPLES) {
|
||||
mDecodeCallback->InputExhausted();
|
||||
}
|
||||
|
||||
if (mToDo.get() == nullptr) {
|
||||
mToDo = new AMessage(kNotifyDecoderActivity, id());
|
||||
if (aEndOfStream) {
|
||||
mToDo->setInt32("input-eos", 1);
|
||||
}
|
||||
mDecoder->requestActivityNotification(mToDo);
|
||||
} else if (aEndOfStream) {
|
||||
mToDo->setInt32("input-eos", 1);
|
||||
}
|
||||
} else {
|
||||
GMDD_LOG("input processed: error#%d", rv);
|
||||
mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GonkDecoderManager::ProcessFlush()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskLooper());
|
||||
|
||||
mLastTime = INT64_MIN;
|
||||
MonitorAutoLock lock(mFlushMonitor);
|
||||
mWaitOutput.Clear();
|
||||
if (mDecoder->flush() != OK) {
|
||||
GMDD_LOG("flush error");
|
||||
mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__));
|
||||
}
|
||||
mIsFlushing = false;
|
||||
lock.NotifyAll();
|
||||
}
|
||||
|
||||
// Use output timestamp to determine which output buffer is already returned
|
||||
// and remove corresponding info, except for EOS, from the waiting list.
|
||||
// This method handles the cases that audio decoder sends multiple output
|
||||
// buffers for one input.
|
||||
void
|
||||
GonkDecoderManager::UpdateWaitingList(int64_t aForgetUpTo)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskLooper());
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < mWaitOutput.Length(); i++) {
|
||||
const auto& item = mWaitOutput.ElementAt(i);
|
||||
if (item.mEOS || item.mTimestamp > aForgetUpTo) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
mWaitOutput.RemoveElementsAt(0, i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GonkDecoderManager::ProcessToDo(bool aEndOfStream)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskLooper());
|
||||
|
||||
MOZ_ASSERT(mToDo.get() != nullptr);
|
||||
mToDo.clear();
|
||||
|
||||
if (NumQueuedSamples() > 0 && ProcessQueuedSamples() < 0) {
|
||||
mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__));
|
||||
return;
|
||||
}
|
||||
|
||||
while (mWaitOutput.Length() > 0) {
|
||||
RefPtr<MediaData> output;
|
||||
WaitOutputInfo wait = mWaitOutput.ElementAt(0);
|
||||
nsresult rv = Output(wait.mOffset, output);
|
||||
if (rv == NS_OK) {
|
||||
MOZ_ASSERT(output);
|
||||
mDecodeCallback->Output(output);
|
||||
UpdateWaitingList(output->mTime);
|
||||
} else if (rv == NS_ERROR_ABORT) {
|
||||
// EOS
|
||||
MOZ_ASSERT(mQueuedSamples.IsEmpty());
|
||||
if (output) {
|
||||
mDecodeCallback->Output(output);
|
||||
UpdateWaitingList(output->mTime);
|
||||
}
|
||||
MOZ_ASSERT(mWaitOutput.Length() == 1);
|
||||
mWaitOutput.RemoveElementAt(0);
|
||||
mDecodeCallback->DrainComplete();
|
||||
ResetEOS();
|
||||
return;
|
||||
} else if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
break;
|
||||
} else {
|
||||
mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!aEndOfStream && NumQueuedSamples() <= MIN_QUEUED_SAMPLES) {
|
||||
mDecodeCallback->InputExhausted();
|
||||
// No need to shedule todo task this time because InputExhausted() will
|
||||
// cause Input() to be invoked and do it for us.
|
||||
return;
|
||||
}
|
||||
|
||||
if (NumQueuedSamples() || mWaitOutput.Length() > 0) {
|
||||
mToDo = new AMessage(kNotifyDecoderActivity, id());
|
||||
if (aEndOfStream) {
|
||||
mToDo->setInt32("input-eos", 1);
|
||||
}
|
||||
mDecoder->requestActivityNotification(mToDo);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GonkDecoderManager::ResetEOS()
|
||||
{
|
||||
// After eos, android::MediaCodec needs to be flushed to receive next input
|
||||
mWaitOutput.Clear();
|
||||
if (mDecoder->flush() != OK) {
|
||||
GMDD_LOG("flush error");
|
||||
mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GonkDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
|
||||
{
|
||||
switch (aMessage->what()) {
|
||||
case kNotifyProcessInput:
|
||||
{
|
||||
int32_t eos = 0;
|
||||
ProcessInput(aMessage->findInt32("input-eos", &eos) && eos);
|
||||
break;
|
||||
}
|
||||
case kNotifyProcessFlush:
|
||||
{
|
||||
ProcessFlush();
|
||||
break;
|
||||
}
|
||||
case kNotifyDecoderActivity:
|
||||
{
|
||||
int32_t eos = 0;
|
||||
ProcessToDo(aMessage->findInt32("input-eos", &eos) && eos);
|
||||
break;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
case kNotifyFindLooperId:
|
||||
{
|
||||
mTaskLooperId = androidGetThreadId();
|
||||
MOZ_ASSERT(mTaskLooperId);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
{
|
||||
TRESPASS();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool
|
||||
GonkDecoderManager::OnTaskLooper()
|
||||
{
|
||||
return androidGetThreadId() == mTaskLooperId;
|
||||
}
|
||||
#endif
|
||||
|
||||
GonkMediaDataDecoder::GonkMediaDataDecoder(GonkDecoderManager* aManager,
|
||||
MediaDataDecoderCallback* aCallback)
|
||||
: mManager(aManager)
|
||||
{
|
||||
MOZ_COUNT_CTOR(GonkMediaDataDecoder);
|
||||
mManager->SetDecodeCallback(aCallback);
|
||||
}
|
||||
|
||||
GonkMediaDataDecoder::~GonkMediaDataDecoder()
|
||||
{
|
||||
MOZ_COUNT_DTOR(GonkMediaDataDecoder);
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::InitPromise>
|
||||
GonkMediaDataDecoder::Init()
|
||||
{
|
||||
return mManager->Init();
|
||||
}
|
||||
|
||||
void
|
||||
GonkMediaDataDecoder::Shutdown()
|
||||
{
|
||||
mManager->Shutdown();
|
||||
|
||||
// Because codec allocated runnable and init promise is at reader TaskQueue,
|
||||
// so manager needs to be destroyed at reader TaskQueue to prevent racing.
|
||||
mManager = nullptr;
|
||||
}
|
||||
|
||||
// Inserts data into the decoder's pipeline.
|
||||
void
|
||||
GonkMediaDataDecoder::Input(MediaRawData* aSample)
|
||||
{
|
||||
mManager->Input(aSample);
|
||||
}
|
||||
|
||||
void
|
||||
GonkMediaDataDecoder::Flush()
|
||||
{
|
||||
mManager->Flush();
|
||||
}
|
||||
|
||||
void
|
||||
GonkMediaDataDecoder::Drain()
|
||||
{
|
||||
mManager->Input(nullptr);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#if !defined(GonkMediaDataDecoder_h_)
|
||||
#define GonkMediaDataDecoder_h_
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include <stagefright/foundation/AHandler.h>
|
||||
|
||||
namespace android {
|
||||
struct ALooper;
|
||||
class MediaBuffer;
|
||||
class MediaCodecProxy;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
class MediaRawData;
|
||||
|
||||
// Manage the data flow from inputting encoded data and outputting decode data.
|
||||
class GonkDecoderManager : public android::AHandler {
|
||||
public:
|
||||
typedef TrackInfo::TrackType TrackType;
|
||||
typedef MediaDataDecoder::InitPromise InitPromise;
|
||||
|
||||
virtual ~GonkDecoderManager() {}
|
||||
|
||||
virtual RefPtr<InitPromise> Init() = 0;
|
||||
virtual const char* GetDescriptionName() const = 0;
|
||||
|
||||
// Asynchronously send sample into mDecoder. If out of input buffer, aSample
|
||||
// will be queued for later re-send.
|
||||
nsresult Input(MediaRawData* aSample);
|
||||
|
||||
// Flush the queued samples and signal decoder to throw all pending input/output away.
|
||||
nsresult Flush();
|
||||
|
||||
// Shutdown decoder and rejects the init promise.
|
||||
virtual nsresult Shutdown();
|
||||
|
||||
// How many samples are waiting for processing.
|
||||
size_t NumQueuedSamples();
|
||||
|
||||
// Set callback for decoder events, such as requesting more input,
|
||||
// returning output, or reporting error.
|
||||
void SetDecodeCallback(MediaDataDecoderCallback* aCallback)
|
||||
{
|
||||
mDecodeCallback = aCallback;
|
||||
}
|
||||
|
||||
protected:
|
||||
GonkDecoderManager()
|
||||
: mMutex("GonkDecoderManager")
|
||||
, mLastTime(INT64_MIN)
|
||||
, mFlushMonitor("GonkDecoderManager::Flush")
|
||||
, mIsFlushing(false)
|
||||
, mDecodeCallback(nullptr)
|
||||
{}
|
||||
|
||||
bool InitLoopers(MediaData::Type aType);
|
||||
|
||||
void onMessageReceived(const android::sp<android::AMessage> &aMessage) override;
|
||||
|
||||
// Produces decoded output. It returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE
|
||||
// when output is not produced yet.
|
||||
// If this returns a failure code other than NS_ERROR_NOT_AVAILABLE, an error
|
||||
// will be reported through mDecodeCallback.
|
||||
virtual nsresult Output(int64_t aStreamOffset,
|
||||
RefPtr<MediaData>& aOutput) = 0;
|
||||
|
||||
// Send queued samples to OMX. It returns how many samples are still in
|
||||
// queue after processing, or negative error code if failed.
|
||||
int32_t ProcessQueuedSamples();
|
||||
|
||||
void ProcessInput(bool aEndOfStream);
|
||||
virtual void ProcessFlush();
|
||||
void ProcessToDo(bool aEndOfStream);
|
||||
virtual void ResetEOS();
|
||||
|
||||
RefPtr<MediaByteBuffer> mCodecSpecificData;
|
||||
|
||||
nsAutoCString mMimeType;
|
||||
|
||||
// MediaCodedc's wrapper that performs the decoding.
|
||||
android::sp<android::MediaCodecProxy> mDecoder;
|
||||
// Looper for mDecoder to run on.
|
||||
android::sp<android::ALooper> mDecodeLooper;
|
||||
// Looper to run decode tasks such as processing input, output, flush, and
|
||||
// recycling output buffers.
|
||||
android::sp<android::ALooper> mTaskLooper;
|
||||
// Message codes for tasks running on mTaskLooper.
|
||||
enum {
|
||||
// Decoder will send this to indicate internal state change such as input or
|
||||
// output buffers availability. Used to run pending input & output tasks.
|
||||
kNotifyDecoderActivity = 'nda ',
|
||||
// Signal the decoder to flush.
|
||||
kNotifyProcessFlush = 'npf ',
|
||||
// Used to process queued samples when there is new input.
|
||||
kNotifyProcessInput = 'npi ',
|
||||
#ifdef DEBUG
|
||||
kNotifyFindLooperId = 'nfli',
|
||||
#endif
|
||||
};
|
||||
|
||||
MozPromiseHolder<InitPromise> mInitPromise;
|
||||
|
||||
Mutex mMutex; // Protects mQueuedSamples.
|
||||
// A queue that stores the samples waiting to be sent to mDecoder.
|
||||
// Empty element means EOS and there shouldn't be any sample be queued after it.
|
||||
// Samples are queued in caller's thread and dequeued in mTaskLooper.
|
||||
nsTArray<RefPtr<MediaRawData>> mQueuedSamples;
|
||||
|
||||
// The last decoded frame presentation time. Only accessed on mTaskLooper.
|
||||
int64_t mLastTime;
|
||||
|
||||
Monitor mFlushMonitor; // Waits for flushing to complete.
|
||||
bool mIsFlushing; // Protected by mFlushMonitor.
|
||||
|
||||
// Remembers the notification that is currently waiting for the decoder event
|
||||
// to avoid requesting more than one notification at the time, which is
|
||||
// forbidden by mDecoder.
|
||||
android::sp<android::AMessage> mToDo;
|
||||
|
||||
// Stores sample info for output buffer processing later.
|
||||
struct WaitOutputInfo {
|
||||
WaitOutputInfo(int64_t aOffset, int64_t aTimestamp, bool aEOS)
|
||||
: mOffset(aOffset)
|
||||
, mTimestamp(aTimestamp)
|
||||
, mEOS(aEOS)
|
||||
{}
|
||||
const int64_t mOffset;
|
||||
const int64_t mTimestamp;
|
||||
const bool mEOS;
|
||||
};
|
||||
|
||||
nsTArray<WaitOutputInfo> mWaitOutput;
|
||||
|
||||
MediaDataDecoderCallback* mDecodeCallback; // Reports decoder output or error.
|
||||
|
||||
private:
|
||||
void UpdateWaitingList(int64_t aForgetUpTo);
|
||||
|
||||
#ifdef DEBUG
|
||||
typedef void* LooperId;
|
||||
|
||||
bool OnTaskLooper();
|
||||
LooperId mTaskLooperId;
|
||||
#endif
|
||||
};
|
||||
|
||||
class AutoReleaseMediaBuffer
|
||||
{
|
||||
public:
|
||||
AutoReleaseMediaBuffer(android::MediaBuffer* aBuffer, android::MediaCodecProxy* aCodec)
|
||||
: mBuffer(aBuffer)
|
||||
, mCodec(aCodec)
|
||||
{}
|
||||
|
||||
~AutoReleaseMediaBuffer()
|
||||
{
|
||||
MOZ_ASSERT(mCodec.get());
|
||||
if (mBuffer) {
|
||||
mCodec->ReleaseMediaBuffer(mBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
android::MediaBuffer* forget()
|
||||
{
|
||||
android::MediaBuffer* tmp = mBuffer;
|
||||
mBuffer = nullptr;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
android::MediaBuffer* mBuffer;
|
||||
android::sp<android::MediaCodecProxy> mCodec;
|
||||
};
|
||||
|
||||
// Samples are decoded using the GonkDecoder (MediaCodec)
|
||||
// created by the GonkDecoderManager. This class implements
|
||||
// the higher-level logic that drives mapping the Gonk to the async
|
||||
// MediaDataDecoder interface. The specifics of decoding the exact stream
|
||||
// type are handled by GonkDecoderManager and the GonkDecoder it creates.
|
||||
class GonkMediaDataDecoder : public MediaDataDecoder {
|
||||
public:
|
||||
GonkMediaDataDecoder(GonkDecoderManager* aDecoderManager,
|
||||
MediaDataDecoderCallback* aCallback);
|
||||
|
||||
~GonkMediaDataDecoder();
|
||||
|
||||
RefPtr<InitPromise> Init() override;
|
||||
|
||||
void Input(MediaRawData* aSample) override;
|
||||
|
||||
void Flush() override;
|
||||
|
||||
void Drain() override;
|
||||
|
||||
void Shutdown() override;
|
||||
|
||||
const char* GetDescriptionName() const override
|
||||
{
|
||||
return "gonk decoder";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
android::sp<GonkDecoderManager> mManager;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GonkMediaDataDecoder_h_
|
||||
|
|
@ -1,772 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "MediaCodecProxy.h"
|
||||
#include <OMX_IVCommon.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <ICrypto.h>
|
||||
#include "GonkVideoDecoderManager.h"
|
||||
#include "GrallocImages.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include <stagefright/MediaBuffer.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#include <stagefright/MediaErrors.h>
|
||||
#include <stagefright/foundation/AString.h>
|
||||
#include "GonkNativeWindow.h"
|
||||
#include "mozilla/layers/GrallocTextureClient.h"
|
||||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
#include "mozilla/layers/TextureClient.h"
|
||||
#include "mozilla/layers/TextureClientRecycleAllocator.h"
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#define CODECCONFIG_TIMEOUT_US 10000LL
|
||||
#define READ_OUTPUT_BUFFER_TIMEOUT_US 0LL
|
||||
|
||||
#include <android/log.h>
|
||||
#define GVDM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkVideoDecoderManager", __VA_ARGS__)
|
||||
|
||||
#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
using namespace mozilla::layers;
|
||||
using namespace android;
|
||||
typedef android::MediaCodecProxy MediaCodecProxy;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class GonkTextureClientAllocationHelper : public layers::ITextureClientAllocationHelper
|
||||
{
|
||||
public:
|
||||
GonkTextureClientAllocationHelper(uint32_t aGrallocFormat,
|
||||
gfx::IntSize aSize)
|
||||
: ITextureClientAllocationHelper(gfx::SurfaceFormat::UNKNOWN,
|
||||
aSize,
|
||||
BackendSelector::Content,
|
||||
TextureFlags::DEALLOCATE_CLIENT,
|
||||
ALLOC_DISALLOW_BUFFERTEXTURECLIENT)
|
||||
, mGrallocFormat(aGrallocFormat)
|
||||
{}
|
||||
|
||||
already_AddRefed<TextureClient> Allocate(KnowsCompositor* aAllocator) override
|
||||
{
|
||||
uint32_t usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
android::GraphicBuffer::USAGE_HW_TEXTURE;
|
||||
|
||||
GrallocTextureData* texData = GrallocTextureData::Create(mSize, mGrallocFormat,
|
||||
gfx::BackendType::NONE,
|
||||
usage, aAllocator->GetTextureForwarder());
|
||||
if (!texData) {
|
||||
return nullptr;
|
||||
}
|
||||
sp<GraphicBuffer> graphicBuffer = texData->GetGraphicBuffer();
|
||||
if (!graphicBuffer.get()) {
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<TextureClient> textureClient =
|
||||
TextureClient::CreateWithData(texData, TextureFlags::DEALLOCATE_CLIENT, aAllocator->GetTextureForwarder());
|
||||
return textureClient.forget();
|
||||
}
|
||||
|
||||
bool IsCompatible(TextureClient* aTextureClient) override
|
||||
{
|
||||
if (!aTextureClient) {
|
||||
return false;
|
||||
}
|
||||
sp<GraphicBuffer> graphicBuffer =
|
||||
static_cast<GrallocTextureData*>(aTextureClient->GetInternalData())->GetGraphicBuffer();
|
||||
if (!graphicBuffer.get() ||
|
||||
static_cast<uint32_t>(graphicBuffer->getPixelFormat()) != mGrallocFormat ||
|
||||
aTextureClient->GetSize() != mSize) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mGrallocFormat;
|
||||
};
|
||||
|
||||
GonkVideoDecoderManager::GonkVideoDecoderManager(
|
||||
mozilla::layers::ImageContainer* aImageContainer,
|
||||
const VideoInfo& aConfig)
|
||||
: mConfig(aConfig)
|
||||
, mImageContainer(aImageContainer)
|
||||
, mColorConverterBufferSize(0)
|
||||
, mPendingReleaseItemsLock("GonkVideoDecoderManager::mPendingReleaseItemsLock")
|
||||
, mNeedsCopyBuffer(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(GonkVideoDecoderManager);
|
||||
}
|
||||
|
||||
GonkVideoDecoderManager::~GonkVideoDecoderManager()
|
||||
{
|
||||
MOZ_COUNT_DTOR(GonkVideoDecoderManager);
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkVideoDecoderManager::Shutdown()
|
||||
{
|
||||
mVideoCodecRequest.DisconnectIfExists();
|
||||
return GonkDecoderManager::Shutdown();
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::InitPromise>
|
||||
GonkVideoDecoderManager::Init()
|
||||
{
|
||||
mNeedsCopyBuffer = false;
|
||||
|
||||
uint32_t maxWidth, maxHeight;
|
||||
char propValue[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.moz.omx.hw.max_width", propValue, "-1");
|
||||
maxWidth = -1 == atoi(propValue) ? MAX_VIDEO_WIDTH : atoi(propValue);
|
||||
property_get("ro.moz.omx.hw.max_height", propValue, "-1");
|
||||
maxHeight = -1 == atoi(propValue) ? MAX_VIDEO_HEIGHT : atoi(propValue) ;
|
||||
|
||||
if (uint32_t(mConfig.mImage.width * mConfig.mImage.height) > maxWidth * maxHeight) {
|
||||
GVDM_LOG("Video resolution exceeds hw codec capability");
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
|
||||
// Validate the container-reported frame and pictureRect sizes. This ensures
|
||||
// that our video frame creation code doesn't overflow.
|
||||
if (!IsValidVideoRegion(mConfig.mImage, mConfig.ImageRect(), mConfig.mDisplay)) {
|
||||
GVDM_LOG("It is not a valid region");
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
|
||||
mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue();
|
||||
MOZ_ASSERT(mReaderTaskQueue);
|
||||
|
||||
if (mDecodeLooper.get() != nullptr) {
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
|
||||
if (!InitLoopers(MediaData::VIDEO_DATA)) {
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
|
||||
RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
|
||||
android::sp<GonkVideoDecoderManager> self = this;
|
||||
mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper,
|
||||
mConfig.mMimeType.get(),
|
||||
false);
|
||||
|
||||
uint32_t capability = MediaCodecProxy::kEmptyCapability;
|
||||
if (mDecoder->getCapability(&capability) == OK && (capability &
|
||||
MediaCodecProxy::kCanExposeGraphicBuffer)) {
|
||||
#if ANDROID_VERSION >= 21
|
||||
sp<IGonkGraphicBufferConsumer> consumer;
|
||||
GonkBufferQueue::createBufferQueue(&mGraphicBufferProducer, &consumer);
|
||||
mNativeWindow = new GonkNativeWindow(consumer);
|
||||
#else
|
||||
mNativeWindow = new GonkNativeWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
mVideoCodecRequest.Begin(mDecoder->AsyncAllocateVideoMediaCodec()
|
||||
->Then(mReaderTaskQueue, __func__,
|
||||
[self] (bool) -> void {
|
||||
self->mVideoCodecRequest.Complete();
|
||||
self->codecReserved();
|
||||
}, [self] (bool) -> void {
|
||||
self->mVideoCodecRequest.Complete();
|
||||
self->codecCanceled();
|
||||
}));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GonkVideoDecoderManager::CreateVideoData(MediaBuffer* aBuffer,
|
||||
int64_t aStreamOffset,
|
||||
VideoData **v)
|
||||
{
|
||||
*v = nullptr;
|
||||
RefPtr<VideoData> data;
|
||||
int64_t timeUs;
|
||||
int32_t keyFrame;
|
||||
|
||||
if (aBuffer == nullptr) {
|
||||
GVDM_LOG("Video Buffer is not valid!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
AutoReleaseMediaBuffer autoRelease(aBuffer, mDecoder.get());
|
||||
|
||||
if (!aBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
|
||||
GVDM_LOG("Decoder did not return frame time");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (mLastTime > timeUs) {
|
||||
GVDM_LOG("Output decoded sample time is revert. time=%lld", timeUs);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
mLastTime = timeUs;
|
||||
|
||||
if (aBuffer->range_length() == 0) {
|
||||
// Some decoders may return spurious empty buffers that we just want to ignore
|
||||
// quoted from Android's AwesomePlayer.cpp
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (!aBuffer->meta_data()->findInt32(kKeyIsSyncFrame, &keyFrame)) {
|
||||
keyFrame = 0;
|
||||
}
|
||||
|
||||
gfx::IntRect picture =
|
||||
mConfig.ScaledImageRect(mFrameInfo.mWidth, mFrameInfo.mHeight);
|
||||
if (aBuffer->graphicBuffer().get()) {
|
||||
data = CreateVideoDataFromGraphicBuffer(aBuffer, picture);
|
||||
if (data && !mNeedsCopyBuffer) {
|
||||
// RecycleCallback() will be responsible for release the buffer.
|
||||
autoRelease.forget();
|
||||
}
|
||||
mNeedsCopyBuffer = false;
|
||||
} else {
|
||||
data = CreateVideoDataFromDataBuffer(aBuffer, picture);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
// Fill necessary info.
|
||||
data->mOffset = aStreamOffset;
|
||||
data->mTime = timeUs;
|
||||
data->mKeyframe = keyFrame;
|
||||
|
||||
data.forget(v);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Copy pixels from one planar YUV to another.
|
||||
static void
|
||||
CopyYUV(PlanarYCbCrData& aSource, PlanarYCbCrData& aDestination)
|
||||
{
|
||||
// Fill Y plane.
|
||||
uint8_t* srcY = aSource.mYChannel;
|
||||
gfx::IntSize ySize = aSource.mYSize;
|
||||
uint8_t* destY = aDestination.mYChannel;
|
||||
// Y plane.
|
||||
for (int i = 0; i < ySize.height; i++) {
|
||||
memcpy(destY, srcY, ySize.width);
|
||||
srcY += aSource.mYStride;
|
||||
destY += aDestination.mYStride;
|
||||
}
|
||||
|
||||
// Fill UV plane.
|
||||
// Line start
|
||||
uint8_t* srcU = aSource.mCbChannel;
|
||||
uint8_t* srcV = aSource.mCrChannel;
|
||||
uint8_t* destU = aDestination.mCbChannel;
|
||||
uint8_t* destV = aDestination.mCrChannel;
|
||||
|
||||
gfx::IntSize uvSize = aSource.mCbCrSize;
|
||||
for (int i = 0; i < uvSize.height; i++) {
|
||||
uint8_t* su = srcU;
|
||||
uint8_t* sv = srcV;
|
||||
uint8_t* du = destU;
|
||||
uint8_t* dv =destV;
|
||||
for (int j = 0; j < uvSize.width; j++) {
|
||||
*du++ = *su++;
|
||||
*dv++ = *sv++;
|
||||
// Move to next pixel.
|
||||
su += aSource.mCbSkip;
|
||||
sv += aSource.mCrSkip;
|
||||
du += aDestination.mCbSkip;
|
||||
dv += aDestination.mCrSkip;
|
||||
}
|
||||
// Move to next line.
|
||||
srcU += aSource.mCbCrStride;
|
||||
srcV += aSource.mCbCrStride;
|
||||
destU += aDestination.mCbCrStride;
|
||||
destV += aDestination.mCbCrStride;
|
||||
}
|
||||
}
|
||||
|
||||
inline static int
|
||||
Align(int aX, int aAlign)
|
||||
{
|
||||
return (aX + aAlign - 1) & ~(aAlign - 1);
|
||||
}
|
||||
|
||||
// Venus formats are doucmented in kernel/include/media/msm_media_info.h:
|
||||
// * Y_Stride : Width aligned to 128
|
||||
// * UV_Stride : Width aligned to 128
|
||||
// * Y_Scanlines: Height aligned to 32
|
||||
// * UV_Scanlines: Height/2 aligned to 16
|
||||
// * Total size = align((Y_Stride * Y_Scanlines
|
||||
// * + UV_Stride * UV_Scanlines + 4096), 4096)
|
||||
static void
|
||||
CopyVenus(uint8_t* aSrc, uint8_t* aDest, uint32_t aWidth, uint32_t aHeight)
|
||||
{
|
||||
size_t yStride = Align(aWidth, 128);
|
||||
uint8_t* s = aSrc;
|
||||
uint8_t* d = aDest;
|
||||
for (size_t i = 0; i < aHeight; i++) {
|
||||
memcpy(d, s, aWidth);
|
||||
s += yStride;
|
||||
d += yStride;
|
||||
}
|
||||
size_t uvStride = yStride;
|
||||
size_t uvLines = (aHeight + 1) / 2;
|
||||
size_t ySize = yStride * Align(aHeight, 32);
|
||||
s = aSrc + ySize;
|
||||
d = aDest + ySize;
|
||||
for (size_t i = 0; i < uvLines; i++) {
|
||||
memcpy(d, s, aWidth);
|
||||
s += uvStride;
|
||||
d += uvStride;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
CopyGraphicBuffer(sp<GraphicBuffer>& aSource, sp<GraphicBuffer>& aDestination)
|
||||
{
|
||||
void* srcPtr = nullptr;
|
||||
aSource->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &srcPtr);
|
||||
void* destPtr = nullptr;
|
||||
aDestination->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &destPtr);
|
||||
MOZ_ASSERT(srcPtr && destPtr);
|
||||
|
||||
// Build PlanarYCbCrData for source buffer.
|
||||
PlanarYCbCrData srcData;
|
||||
switch (aSource->getPixelFormat()) {
|
||||
case HAL_PIXEL_FORMAT_YV12: {
|
||||
// Android YV12 format is defined in system/core/include/system/graphics.h
|
||||
srcData.mYChannel = static_cast<uint8_t*>(srcPtr);
|
||||
srcData.mYSkip = 0;
|
||||
srcData.mYSize.width = aSource->getWidth();
|
||||
srcData.mYSize.height = aSource->getHeight();
|
||||
srcData.mYStride = aSource->getStride();
|
||||
// 4:2:0.
|
||||
srcData.mCbCrSize.width = srcData.mYSize.width / 2;
|
||||
srcData.mCbCrSize.height = srcData.mYSize.height / 2;
|
||||
srcData.mCrChannel = srcData.mYChannel + (srcData.mYStride * srcData.mYSize.height);
|
||||
// Aligned to 16 bytes boundary.
|
||||
srcData.mCbCrStride = Align(srcData.mYStride / 2, 16);
|
||||
srcData.mCrSkip = 0;
|
||||
srcData.mCbChannel = srcData.mCrChannel + (srcData.mCbCrStride * srcData.mCbCrSize.height);
|
||||
srcData.mCbSkip = 0;
|
||||
|
||||
// Build PlanarYCbCrData for destination buffer.
|
||||
PlanarYCbCrData destData;
|
||||
destData.mYChannel = static_cast<uint8_t*>(destPtr);
|
||||
destData.mYSkip = 0;
|
||||
destData.mYSize.width = aDestination->getWidth();
|
||||
destData.mYSize.height = aDestination->getHeight();
|
||||
destData.mYStride = aDestination->getStride();
|
||||
// 4:2:0.
|
||||
destData.mCbCrSize.width = destData.mYSize.width / 2;
|
||||
destData.mCbCrSize.height = destData.mYSize.height / 2;
|
||||
destData.mCrChannel = destData.mYChannel + (destData.mYStride * destData.mYSize.height);
|
||||
// Aligned to 16 bytes boundary.
|
||||
destData.mCbCrStride = Align(destData.mYStride / 2, 16);
|
||||
destData.mCrSkip = 0;
|
||||
destData.mCbChannel = destData.mCrChannel + (destData.mCbCrStride * destData.mCbCrSize.height);
|
||||
destData.mCbSkip = 0;
|
||||
|
||||
CopyYUV(srcData, destData);
|
||||
break;
|
||||
}
|
||||
case GrallocImage::HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS:
|
||||
CopyVenus(static_cast<uint8_t*>(srcPtr),
|
||||
static_cast<uint8_t*>(destPtr),
|
||||
aSource->getWidth(),
|
||||
aSource->getHeight());
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("Unsupported input gralloc image type. Should never be here.");
|
||||
}
|
||||
|
||||
|
||||
aSource->unlock();
|
||||
aDestination->unlock();
|
||||
}
|
||||
|
||||
already_AddRefed<VideoData>
|
||||
GonkVideoDecoderManager::CreateVideoDataFromGraphicBuffer(MediaBuffer* aSource,
|
||||
gfx::IntRect& aPicture)
|
||||
{
|
||||
sp<GraphicBuffer> srcBuffer(aSource->graphicBuffer());
|
||||
RefPtr<TextureClient> textureClient;
|
||||
|
||||
if (mNeedsCopyBuffer) {
|
||||
// Copy buffer contents for bug 1199809.
|
||||
if (!mCopyAllocator) {
|
||||
RefPtr<layers::ImageBridgeChild> bridge = layers::ImageBridgeChild::GetSingleton();
|
||||
mCopyAllocator = new TextureClientRecycleAllocator(bridge);
|
||||
}
|
||||
if (!mCopyAllocator) {
|
||||
GVDM_LOG("Create buffer allocator failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gfx::IntSize size(srcBuffer->getWidth(), srcBuffer->getHeight());
|
||||
GonkTextureClientAllocationHelper helper(srcBuffer->getPixelFormat(), size);
|
||||
textureClient = mCopyAllocator->CreateOrRecycle(helper);
|
||||
if (!textureClient) {
|
||||
GVDM_LOG("Copy buffer allocation failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sp<GraphicBuffer> destBuffer =
|
||||
static_cast<GrallocTextureData*>(textureClient->GetInternalData())->GetGraphicBuffer();
|
||||
|
||||
CopyGraphicBuffer(srcBuffer, destBuffer);
|
||||
} else {
|
||||
textureClient = mNativeWindow->getTextureClientFromBuffer(srcBuffer.get());
|
||||
textureClient->SetRecycleCallback(GonkVideoDecoderManager::RecycleCallback, this);
|
||||
static_cast<GrallocTextureData*>(textureClient->GetInternalData())->SetMediaBuffer(aSource);
|
||||
}
|
||||
|
||||
RefPtr<VideoData> data =
|
||||
VideoData::CreateAndCopyIntoTextureClient(mConfig,
|
||||
0, // Filled later by caller.
|
||||
0, // Filled later by caller.
|
||||
1, // No way to pass sample duration from muxer to
|
||||
// OMX codec, so we hardcode the duration here.
|
||||
textureClient,
|
||||
false, // Filled later by caller.
|
||||
-1,
|
||||
aPicture);
|
||||
return data.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<VideoData>
|
||||
GonkVideoDecoderManager::CreateVideoDataFromDataBuffer(MediaBuffer* aSource, gfx::IntRect& aPicture)
|
||||
{
|
||||
if (!aSource->data()) {
|
||||
GVDM_LOG("No data in Video Buffer!");
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t *yuv420p_buffer = (uint8_t *)aSource->data();
|
||||
int32_t stride = mFrameInfo.mStride;
|
||||
int32_t slice_height = mFrameInfo.mSliceHeight;
|
||||
|
||||
// Converts to OMX_COLOR_FormatYUV420Planar
|
||||
if (mFrameInfo.mColorFormat != OMX_COLOR_FormatYUV420Planar) {
|
||||
ARect crop;
|
||||
crop.top = 0;
|
||||
crop.bottom = mFrameInfo.mHeight;
|
||||
crop.left = 0;
|
||||
crop.right = mFrameInfo.mWidth;
|
||||
yuv420p_buffer = GetColorConverterBuffer(mFrameInfo.mWidth, mFrameInfo.mHeight);
|
||||
if (mColorConverter.convertDecoderOutputToI420(aSource->data(),
|
||||
mFrameInfo.mWidth, mFrameInfo.mHeight, crop, yuv420p_buffer) != OK) {
|
||||
GVDM_LOG("Color conversion failed!");
|
||||
return nullptr;
|
||||
}
|
||||
stride = mFrameInfo.mWidth;
|
||||
slice_height = mFrameInfo.mHeight;
|
||||
}
|
||||
|
||||
size_t yuv420p_y_size = stride * slice_height;
|
||||
size_t yuv420p_u_size = ((stride + 1) / 2) * ((slice_height + 1) / 2);
|
||||
uint8_t *yuv420p_y = yuv420p_buffer;
|
||||
uint8_t *yuv420p_u = yuv420p_y + yuv420p_y_size;
|
||||
uint8_t *yuv420p_v = yuv420p_u + yuv420p_u_size;
|
||||
|
||||
VideoData::YCbCrBuffer b;
|
||||
b.mPlanes[0].mData = yuv420p_y;
|
||||
b.mPlanes[0].mWidth = mFrameInfo.mWidth;
|
||||
b.mPlanes[0].mHeight = mFrameInfo.mHeight;
|
||||
b.mPlanes[0].mStride = stride;
|
||||
b.mPlanes[0].mOffset = 0;
|
||||
b.mPlanes[0].mSkip = 0;
|
||||
|
||||
b.mPlanes[1].mData = yuv420p_u;
|
||||
b.mPlanes[1].mWidth = (mFrameInfo.mWidth + 1) / 2;
|
||||
b.mPlanes[1].mHeight = (mFrameInfo.mHeight + 1) / 2;
|
||||
b.mPlanes[1].mStride = (stride + 1) / 2;
|
||||
b.mPlanes[1].mOffset = 0;
|
||||
b.mPlanes[1].mSkip = 0;
|
||||
|
||||
b.mPlanes[2].mData = yuv420p_v;
|
||||
b.mPlanes[2].mWidth =(mFrameInfo.mWidth + 1) / 2;
|
||||
b.mPlanes[2].mHeight = (mFrameInfo.mHeight + 1) / 2;
|
||||
b.mPlanes[2].mStride = (stride + 1) / 2;
|
||||
b.mPlanes[2].mOffset = 0;
|
||||
b.mPlanes[2].mSkip = 0;
|
||||
|
||||
RefPtr<VideoData> data =
|
||||
VideoData::CreateAndCopyData(mConfig,
|
||||
mImageContainer,
|
||||
0, // Filled later by caller.
|
||||
0, // Filled later by caller.
|
||||
1, // We don't know the duration.
|
||||
b,
|
||||
0, // Filled later by caller.
|
||||
-1,
|
||||
aPicture);
|
||||
|
||||
return data.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
GonkVideoDecoderManager::SetVideoFormat()
|
||||
{
|
||||
// read video metadata from MediaCodec
|
||||
sp<AMessage> codecFormat;
|
||||
if (mDecoder->getOutputFormat(&codecFormat) == OK) {
|
||||
AString mime;
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
int32_t stride = 0;
|
||||
int32_t slice_height = 0;
|
||||
int32_t color_format = 0;
|
||||
int32_t crop_left = 0;
|
||||
int32_t crop_top = 0;
|
||||
int32_t crop_right = 0;
|
||||
int32_t crop_bottom = 0;
|
||||
if (!codecFormat->findString("mime", &mime) ||
|
||||
!codecFormat->findInt32("width", &width) ||
|
||||
!codecFormat->findInt32("height", &height) ||
|
||||
!codecFormat->findInt32("stride", &stride) ||
|
||||
!codecFormat->findInt32("slice-height", &slice_height) ||
|
||||
!codecFormat->findInt32("color-format", &color_format) ||
|
||||
!codecFormat->findRect("crop", &crop_left, &crop_top, &crop_right, &crop_bottom)) {
|
||||
GVDM_LOG("Failed to find values");
|
||||
return false;
|
||||
}
|
||||
mFrameInfo.mWidth = width;
|
||||
mFrameInfo.mHeight = height;
|
||||
mFrameInfo.mStride = stride;
|
||||
mFrameInfo.mSliceHeight = slice_height;
|
||||
mFrameInfo.mColorFormat = color_format;
|
||||
|
||||
nsIntSize displaySize(width, height);
|
||||
if (!IsValidVideoRegion(mConfig.mDisplay,
|
||||
mConfig.ScaledImageRect(width, height),
|
||||
displaySize)) {
|
||||
GVDM_LOG("It is not a valid region");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
GVDM_LOG("Fail to get output format");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Blocks until decoded sample is produced by the deoder.
|
||||
nsresult
|
||||
GonkVideoDecoderManager::Output(int64_t aStreamOffset,
|
||||
RefPtr<MediaData>& aOutData)
|
||||
{
|
||||
aOutData = nullptr;
|
||||
status_t err;
|
||||
if (mDecoder == nullptr) {
|
||||
GVDM_LOG("Decoder is not inited");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
MediaBuffer* outputBuffer = nullptr;
|
||||
err = mDecoder->Output(&outputBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US);
|
||||
|
||||
switch (err) {
|
||||
case OK:
|
||||
{
|
||||
RefPtr<VideoData> data;
|
||||
nsresult rv = CreateVideoData(outputBuffer, aStreamOffset, getter_AddRefs(data));
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
// Decoder outputs a empty video buffer, try again
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
} else if (rv != NS_OK || data == nullptr) {
|
||||
GVDM_LOG("Failed to create VideoData");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
aOutData = data;
|
||||
return NS_OK;
|
||||
}
|
||||
case android::INFO_FORMAT_CHANGED:
|
||||
{
|
||||
// If the format changed, update our cached info.
|
||||
GVDM_LOG("Decoder format changed");
|
||||
if (!SetVideoFormat()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
return Output(aStreamOffset, aOutData);
|
||||
}
|
||||
case android::INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
{
|
||||
if (mDecoder->UpdateOutputBuffers()) {
|
||||
return Output(aStreamOffset, aOutData);
|
||||
}
|
||||
GVDM_LOG("Fails to update output buffers!");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
case -EAGAIN:
|
||||
{
|
||||
// GVDM_LOG("Need to try again!");
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
case android::ERROR_END_OF_STREAM:
|
||||
{
|
||||
GVDM_LOG("Got the EOS frame!");
|
||||
RefPtr<VideoData> data;
|
||||
nsresult rv = CreateVideoData(outputBuffer, aStreamOffset, getter_AddRefs(data));
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
// For EOS, no need to do any thing.
|
||||
return NS_ERROR_ABORT;
|
||||
}
|
||||
if (rv != NS_OK || data == nullptr) {
|
||||
GVDM_LOG("Failed to create video data");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
aOutData = data;
|
||||
return NS_ERROR_ABORT;
|
||||
}
|
||||
case -ETIMEDOUT:
|
||||
{
|
||||
GVDM_LOG("Timeout. can try again next time");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
default:
|
||||
{
|
||||
GVDM_LOG("Decoder failed, err=%d", err);
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
GonkVideoDecoderManager::codecReserved()
|
||||
{
|
||||
if (mInitPromise.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
GVDM_LOG("codecReserved");
|
||||
sp<AMessage> format = new AMessage;
|
||||
sp<Surface> surface;
|
||||
status_t rv = OK;
|
||||
// Fixed values
|
||||
GVDM_LOG("Configure video mime type: %s, width:%d, height:%d", mConfig.mMimeType.get(), mConfig.mImage.width, mConfig.mImage.height);
|
||||
format->setString("mime", mConfig.mMimeType.get());
|
||||
format->setInt32("width", mConfig.mImage.width);
|
||||
format->setInt32("height", mConfig.mImage.height);
|
||||
// Set the "moz-use-undequeued-bufs" to use the undeque buffers to accelerate
|
||||
// the video decoding.
|
||||
format->setInt32("moz-use-undequeued-bufs", 1);
|
||||
if (mNativeWindow != nullptr) {
|
||||
#if ANDROID_VERSION >= 21
|
||||
surface = new Surface(mGraphicBufferProducer);
|
||||
#else
|
||||
surface = new Surface(mNativeWindow->getBufferQueue());
|
||||
#endif
|
||||
}
|
||||
mDecoder->configure(format, surface, nullptr, 0);
|
||||
mDecoder->Prepare();
|
||||
|
||||
if (mConfig.mMimeType.EqualsLiteral("video/mp4v-es")) {
|
||||
rv = mDecoder->Input(mConfig.mCodecSpecificConfig->Elements(),
|
||||
mConfig.mCodecSpecificConfig->Length(), 0,
|
||||
android::MediaCodec::BUFFER_FLAG_CODECCONFIG,
|
||||
CODECCONFIG_TIMEOUT_US);
|
||||
}
|
||||
|
||||
if (rv != OK) {
|
||||
GVDM_LOG("Failed to configure codec!!!!");
|
||||
mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
mInitPromise.Resolve(TrackType::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
GonkVideoDecoderManager::codecCanceled()
|
||||
{
|
||||
GVDM_LOG("codecCanceled");
|
||||
mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
||||
}
|
||||
|
||||
// Called on GonkDecoderManager::mTaskLooper thread.
|
||||
void
|
||||
GonkVideoDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
|
||||
{
|
||||
switch (aMessage->what()) {
|
||||
case kNotifyPostReleaseBuffer:
|
||||
{
|
||||
ReleaseAllPendingVideoBuffers();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
GonkDecoderManager::onMessageReceived(aMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
GonkVideoDecoderManager::GetColorConverterBuffer(int32_t aWidth, int32_t aHeight)
|
||||
{
|
||||
// Allocate a temporary YUV420Planer buffer.
|
||||
size_t yuv420p_y_size = aWidth * aHeight;
|
||||
size_t yuv420p_u_size = ((aWidth + 1) / 2) * ((aHeight + 1) / 2);
|
||||
size_t yuv420p_v_size = yuv420p_u_size;
|
||||
size_t yuv420p_size = yuv420p_y_size + yuv420p_u_size + yuv420p_v_size;
|
||||
if (mColorConverterBufferSize != yuv420p_size) {
|
||||
mColorConverterBuffer = MakeUnique<uint8_t[]>(yuv420p_size);
|
||||
mColorConverterBufferSize = yuv420p_size;
|
||||
}
|
||||
return mColorConverterBuffer.get();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
GonkVideoDecoderManager::RecycleCallback(TextureClient* aClient, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClient && !aClient->IsDead());
|
||||
GonkVideoDecoderManager* videoManager = static_cast<GonkVideoDecoderManager*>(aClosure);
|
||||
GrallocTextureData* client = static_cast<GrallocTextureData*>(aClient->GetInternalData());
|
||||
aClient->ClearRecycleCallback();
|
||||
FenceHandle handle = aClient->GetAndResetReleaseFenceHandle();
|
||||
videoManager->PostReleaseVideoBuffer(client->GetMediaBuffer(), handle);
|
||||
}
|
||||
|
||||
void GonkVideoDecoderManager::PostReleaseVideoBuffer(
|
||||
android::MediaBuffer *aBuffer,
|
||||
FenceHandle aReleaseFence)
|
||||
{
|
||||
{
|
||||
MutexAutoLock autoLock(mPendingReleaseItemsLock);
|
||||
if (aBuffer) {
|
||||
mPendingReleaseItems.AppendElement(ReleaseItem(aBuffer, aReleaseFence));
|
||||
}
|
||||
}
|
||||
sp<AMessage> notify =
|
||||
new AMessage(kNotifyPostReleaseBuffer, id());
|
||||
notify->post();
|
||||
|
||||
}
|
||||
|
||||
void GonkVideoDecoderManager::ReleaseAllPendingVideoBuffers()
|
||||
{
|
||||
nsTArray<ReleaseItem> releasingItems;
|
||||
{
|
||||
MutexAutoLock autoLock(mPendingReleaseItemsLock);
|
||||
releasingItems.AppendElements(mPendingReleaseItems);
|
||||
mPendingReleaseItems.Clear();
|
||||
}
|
||||
|
||||
// Free all pending video buffers without holding mPendingReleaseItemsLock.
|
||||
size_t size = releasingItems.Length();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
RefPtr<FenceHandle::FdObj> fdObj = releasingItems[i].mReleaseFence.GetAndResetFdObj();
|
||||
sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
|
||||
fence->waitForever("GonkVideoDecoderManager");
|
||||
mDecoder->ReleaseMediaBuffer(releasingItems[i].mBuffer);
|
||||
}
|
||||
releasingItems.Clear();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#if !defined(GonkVideoDecoderManager_h_)
|
||||
#define GonkVideoDecoderManager_h_
|
||||
|
||||
#include "nsRect.h"
|
||||
#include "GonkMediaDataDecoder.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "I420ColorConverterHelper.h"
|
||||
#include "MediaCodecProxy.h"
|
||||
#include "GonkNativeWindow.h"
|
||||
#include "mozilla/layers/FenceUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include <ui/Fence.h>
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace android {
|
||||
class MediaBuffer;
|
||||
struct MOZ_EXPORT AString;
|
||||
class GonkNativeWindow;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class TextureClient;
|
||||
class TextureClientRecycleAllocator;
|
||||
} // namespace mozilla::layers
|
||||
|
||||
class GonkVideoDecoderManager : public GonkDecoderManager {
|
||||
typedef android::MediaCodecProxy MediaCodecProxy;
|
||||
typedef mozilla::layers::TextureClient TextureClient;
|
||||
|
||||
public:
|
||||
GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer,
|
||||
const VideoInfo& aConfig);
|
||||
|
||||
virtual ~GonkVideoDecoderManager();
|
||||
|
||||
RefPtr<InitPromise> Init() override;
|
||||
|
||||
nsresult Output(int64_t aStreamOffset,
|
||||
RefPtr<MediaData>& aOutput) override;
|
||||
|
||||
nsresult Shutdown() override;
|
||||
|
||||
const char* GetDescriptionName() const override
|
||||
{
|
||||
return "gonk video decoder";
|
||||
}
|
||||
|
||||
static void RecycleCallback(TextureClient* aClient, void* aClosure);
|
||||
|
||||
protected:
|
||||
// Bug 1199809: workaround to avoid sending the graphic buffer by making a
|
||||
// copy of output buffer after calling flush(). Bug 1203859 was created to
|
||||
// reimplementing Gonk PDM on top of OpenMax IL directly. Its buffer
|
||||
// management will work better with Gecko and solve problems like this.
|
||||
void ProcessFlush() override
|
||||
{
|
||||
mNeedsCopyBuffer = true;
|
||||
GonkDecoderManager::ProcessFlush();
|
||||
}
|
||||
|
||||
private:
|
||||
struct FrameInfo
|
||||
{
|
||||
int32_t mWidth = 0;
|
||||
int32_t mHeight = 0;
|
||||
int32_t mStride = 0;
|
||||
int32_t mSliceHeight = 0;
|
||||
int32_t mColorFormat = 0;
|
||||
int32_t mCropLeft = 0;
|
||||
int32_t mCropTop = 0;
|
||||
int32_t mCropRight = 0;
|
||||
int32_t mCropBottom = 0;
|
||||
};
|
||||
|
||||
void onMessageReceived(const android::sp<android::AMessage> &aMessage) override;
|
||||
|
||||
bool SetVideoFormat();
|
||||
|
||||
nsresult CreateVideoData(MediaBuffer* aBuffer, int64_t aStreamOffset, VideoData** aOutData);
|
||||
already_AddRefed<VideoData> CreateVideoDataFromGraphicBuffer(android::MediaBuffer* aSource,
|
||||
gfx::IntRect& aPicture);
|
||||
already_AddRefed<VideoData> CreateVideoDataFromDataBuffer(android::MediaBuffer* aSource,
|
||||
gfx::IntRect& aPicture);
|
||||
|
||||
uint8_t* GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
|
||||
|
||||
// For codec resource management
|
||||
void codecReserved();
|
||||
void codecCanceled();
|
||||
|
||||
void ReleaseAllPendingVideoBuffers();
|
||||
void PostReleaseVideoBuffer(android::MediaBuffer *aBuffer,
|
||||
layers::FenceHandle mReleaseFence);
|
||||
|
||||
VideoInfo mConfig;
|
||||
|
||||
RefPtr<layers::ImageContainer> mImageContainer;
|
||||
RefPtr<layers::TextureClientRecycleAllocator> mCopyAllocator;
|
||||
|
||||
MozPromiseRequestHolder<android::MediaCodecProxy::CodecPromise> mVideoCodecRequest;
|
||||
FrameInfo mFrameInfo;
|
||||
|
||||
// color converter
|
||||
android::I420ColorConverterHelper mColorConverter;
|
||||
UniquePtr<uint8_t[]> mColorConverterBuffer;
|
||||
size_t mColorConverterBufferSize;
|
||||
|
||||
android::sp<android::GonkNativeWindow> mNativeWindow;
|
||||
#if ANDROID_VERSION >= 21
|
||||
android::sp<android::IGraphicBufferProducer> mGraphicBufferProducer;
|
||||
#endif
|
||||
|
||||
enum {
|
||||
kNotifyPostReleaseBuffer = 'nprb',
|
||||
};
|
||||
|
||||
struct ReleaseItem {
|
||||
ReleaseItem(android::MediaBuffer* aBuffer, layers::FenceHandle& aFence)
|
||||
: mBuffer(aBuffer)
|
||||
, mReleaseFence(aFence) {}
|
||||
android::MediaBuffer* mBuffer;
|
||||
layers::FenceHandle mReleaseFence;
|
||||
};
|
||||
nsTArray<ReleaseItem> mPendingReleaseItems;
|
||||
|
||||
// The lock protects mPendingReleaseItems.
|
||||
Mutex mPendingReleaseItemsLock;
|
||||
|
||||
// This TaskQueue should be the same one in mDecodeCallback->OnReaderTaskQueue().
|
||||
// It is for codec resource mangement, decoding task should not dispatch to it.
|
||||
RefPtr<TaskQueue> mReaderTaskQueue;
|
||||
|
||||
// Bug 1199809: do we need to make a copy of output buffer? Used only when
|
||||
// the decoder outputs graphic buffers.
|
||||
bool mNeedsCopyBuffer;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GonkVideoDecoderManager_h_
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS += [
|
||||
'GonkAudioDecoderManager.h',
|
||||
'GonkDecoderModule.h',
|
||||
'GonkMediaDataDecoder.h',
|
||||
'GonkVideoDecoderManager.h',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'GonkAudioDecoderManager.cpp',
|
||||
'GonkDecoderModule.cpp',
|
||||
'GonkMediaDataDecoder.cpp',
|
||||
'GonkVideoDecoderManager.cpp',
|
||||
]
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/media/omx/',
|
||||
]
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
# Suppress some GCC/clang warnings being treated as errors:
|
||||
# - about attributes on forward declarations for types that are already
|
||||
# defined, which complains about an important MOZ_EXPORT for android::AString
|
||||
# - about multi-character constants which are used in codec-related code
|
||||
if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
|
||||
CXXFLAGS += [
|
||||
'-Wno-error=attributes',
|
||||
'-Wno-error=multichar'
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
|
||||
'frameworks/native/opengl/include',]
|
||||
]
|
||||
|
|
@ -70,10 +70,6 @@ if CONFIG['MOZ_APPLEMEDIA']:
|
|||
'-framework AudioToolbox',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_GONK_MEDIACODEC']:
|
||||
DEFINES['MOZ_GONK_MEDIACODEC'] = True
|
||||
DIRS += ['gonk']
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue