зеркало из https://github.com/mozilla/gecko-dev.git
899 строки
27 KiB
C++
899 строки
27 KiB
C++
/* 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 "MediaEngineGonkVideoSource.h"
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "MediaEngineGonkVideoSource"
|
|
|
|
#include <utils/Log.h>
|
|
|
|
#include "GrallocImages.h"
|
|
#include "mozilla/layers/GrallocTextureClient.h"
|
|
#include "mozilla/layers/ImageBridgeChild.h"
|
|
#include "VideoUtils.h"
|
|
#include "ScreenOrientation.h"
|
|
|
|
#include "libyuv.h"
|
|
#include "mtransport/runnable_utils.h"
|
|
#include "GonkCameraImage.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::gfx;
|
|
using namespace android;
|
|
|
|
#undef LOG
|
|
extern LogModule* GetMediaManagerLog();
|
|
#define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
|
|
#define LOGFRAME(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
|
|
|
|
class MediaBufferListener : public GonkCameraSource::DirectBufferListener {
|
|
public:
|
|
MediaBufferListener(MediaEngineGonkVideoSource* aMediaEngine)
|
|
: mMediaEngine(aMediaEngine)
|
|
{
|
|
}
|
|
|
|
status_t BufferAvailable(MediaBuffer* aBuffer)
|
|
{
|
|
nsresult rv = mMediaEngine->OnNewMediaBufferFrame(aBuffer);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return OK;
|
|
}
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
~MediaBufferListener()
|
|
{
|
|
}
|
|
|
|
RefPtr<MediaEngineGonkVideoSource> mMediaEngine;
|
|
};
|
|
|
|
#define WEBRTC_GONK_VIDEO_SOURCE_POOL_BUFFERS 10
|
|
|
|
// We are subclassed from CameraControlListener, which implements a
|
|
// threadsafe reference-count for us.
|
|
NS_IMPL_QUERY_INTERFACE(MediaEngineGonkVideoSource, nsISupports)
|
|
NS_IMPL_ADDREF_INHERITED(MediaEngineGonkVideoSource, CameraControlListener)
|
|
NS_IMPL_RELEASE_INHERITED(MediaEngineGonkVideoSource, CameraControlListener)
|
|
|
|
// Called if the graph thinks it's running out of buffered video; repeat
|
|
// the last frame for whatever minimum period it think it needs. Note that
|
|
// this means that no *real* frame can be inserted during this period.
|
|
void
|
|
MediaEngineGonkVideoSource::NotifyPull(MediaStreamGraph* aGraph,
|
|
SourceMediaStream* aSource,
|
|
TrackID aID,
|
|
StreamTime aDesiredTime)
|
|
{
|
|
VideoSegment segment;
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
// B2G does AddTrack, but holds kStarted until the hardware changes state.
|
|
// So mState could be kReleased here. We really don't care about the state,
|
|
// though.
|
|
|
|
// Note: we're not giving up mImage here
|
|
RefPtr<layers::Image> image = mImage;
|
|
StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
|
|
LOGFRAME(("NotifyPull, desired = %" PRIi64 ", delta = %" PRIi64 " %s",
|
|
(int64_t) aDesiredTime, (int64_t) delta, image ? "" : "<null>"));
|
|
|
|
// Bug 846188 We may want to limit incoming frames to the requested frame rate
|
|
// mFps - if you want 30FPS, and the camera gives you 60FPS, this could
|
|
// cause issues.
|
|
// We may want to signal if the actual frame rate is below mMinFPS -
|
|
// cameras often don't return the requested frame rate especially in low
|
|
// light; we should consider surfacing this so that we can switch to a
|
|
// lower resolution (which may up the frame rate)
|
|
|
|
// Don't append if we've already provided a frame that supposedly goes past the current aDesiredTime
|
|
// Doing so means a negative delta and thus messes up handling of the graph
|
|
if (delta > 0) {
|
|
// nullptr images are allowed
|
|
IntSize size(image ? mWidth : 0, image ? mHeight : 0);
|
|
segment.AppendFrame(image.forget(), delta, size);
|
|
// This can fail if either a) we haven't added the track yet, or b)
|
|
// we've removed or finished the track.
|
|
aSource->AppendToTrack(aID, &(segment));
|
|
}
|
|
}
|
|
|
|
size_t
|
|
MediaEngineGonkVideoSource::NumCapabilities()
|
|
{
|
|
// TODO: Stop hardcoding. Use GetRecorderProfiles+GetProfileInfo (Bug 1128550)
|
|
//
|
|
// The camera-selecting constraints algorithm needs a set of capabilities to
|
|
// work on. In lieu of something better, here are some generic values based on
|
|
// http://en.wikipedia.org/wiki/Comparison_of_Firefox_OS_devices on Jan 2015.
|
|
// When unknown, better overdo it with choices to not block legitimate asks.
|
|
// TODO: Match with actual hardware or add code to query hardware.
|
|
|
|
if (mHardcodedCapabilities.IsEmpty()) {
|
|
const struct { int width, height; } hardcodes[] = {
|
|
{ 800, 1280 },
|
|
{ 720, 1280 },
|
|
{ 600, 1024 },
|
|
{ 540, 960 },
|
|
{ 480, 854 },
|
|
{ 480, 800 },
|
|
{ 320, 480 },
|
|
{ 240, 320 }, // sole mode supported by emulator on try
|
|
};
|
|
const int framerates[] = { 15, 30 };
|
|
|
|
for (auto& hardcode : hardcodes) {
|
|
webrtc::CaptureCapability c;
|
|
c.width = hardcode.width;
|
|
c.height = hardcode.height;
|
|
for (int framerate : framerates) {
|
|
c.maxFPS = framerate;
|
|
mHardcodedCapabilities.AppendElement(c); // portrait
|
|
}
|
|
c.width = hardcode.height;
|
|
c.height = hardcode.width;
|
|
for (int framerate : framerates) {
|
|
c.maxFPS = framerate;
|
|
mHardcodedCapabilities.AppendElement(c); // landscape
|
|
}
|
|
}
|
|
}
|
|
return mHardcodedCapabilities.Length();
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
|
const MediaEnginePrefs& aPrefs,
|
|
const nsString& aDeviceId)
|
|
{
|
|
LOG((__FUNCTION__));
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
if (mState == kReleased && mInitDone) {
|
|
ChooseCapability(aConstraints, aPrefs, aDeviceId);
|
|
NS_DispatchToMainThread(WrapRunnable(RefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::AllocImpl));
|
|
mCallbackMonitor.Wait();
|
|
if (mState != kAllocated) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Deallocate()
|
|
{
|
|
LOG((__FUNCTION__));
|
|
bool empty;
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
empty = mSources.IsEmpty();
|
|
}
|
|
if (empty) {
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState != kStopped && mState != kAllocated) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// We do not register success callback here
|
|
|
|
NS_DispatchToMainThread(WrapRunnable(RefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::DeallocImpl));
|
|
mCallbackMonitor.Wait();
|
|
if (mState != kReleased) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mState = kReleased;
|
|
LOG(("Video device %d deallocated", mCaptureIndex));
|
|
} else {
|
|
LOG(("Video device %d deallocated but still in use", mCaptureIndex));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
|
|
{
|
|
LOG((__FUNCTION__));
|
|
if (!mInitDone || !aStream) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
mSources.AppendElement(aStream);
|
|
}
|
|
|
|
aStream->AddTrack(aID, 0, new VideoSegment());
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
MOZ_ASSERT(mCameraControl, "mCameraControl is nullptr");
|
|
if (mState == kStarted) {
|
|
return NS_OK;
|
|
} else if (!mCameraControl) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mTrackID = aID;
|
|
mImageContainer = layers::LayerManager::CreateImageContainer();
|
|
|
|
NS_DispatchToMainThread(WrapRunnable(RefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::StartImpl,
|
|
mCapability));
|
|
mCallbackMonitor.Wait();
|
|
if (mState != kStarted) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsTArray<nsString> focusModes;
|
|
mCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES, focusModes);
|
|
for (nsTArray<nsString>::index_type i = 0; i < focusModes.Length(); ++i) {
|
|
if (focusModes[i].EqualsASCII("continuous-video")) {
|
|
mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, focusModes[i]);
|
|
mCameraControl->ResumeContinuousFocus();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// XXX some devices support recording camera frame only in metadata mode.
|
|
// But GonkCameraSource requests non-metadata recording mode.
|
|
#if ANDROID_VERSION < 21
|
|
if (NS_FAILED(InitDirectMediaBuffer())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::InitDirectMediaBuffer()
|
|
{
|
|
// Check available buffer resolution.
|
|
nsTArray<ICameraControl::Size> videoSizes;
|
|
mCameraControl->Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, videoSizes);
|
|
if (!videoSizes.Length()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// TODO: MediaEgnine should use supported recording frame sizes as the size
|
|
// range in MediaTrackConstraintSet and find the best match.
|
|
// Here we use the first one as the default size (largest supported size).
|
|
android::Size videoSize;
|
|
videoSize.width = videoSizes[0].width;
|
|
videoSize.height = videoSizes[0].height;
|
|
|
|
LOG(("Intial size, width: %d, height: %d", videoSize.width, videoSize.height));
|
|
mCameraSource = GonkCameraSource::Create(mCameraControl,
|
|
videoSize,
|
|
MediaEngine::DEFAULT_VIDEO_FPS);
|
|
|
|
status_t rv;
|
|
rv = mCameraSource->AddDirectBufferListener(new MediaBufferListener(this));
|
|
if (rv != OK) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = mCameraSource->start(nullptr);
|
|
if (rv != OK) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Stop(SourceMediaStream* aSource, TrackID aID)
|
|
{
|
|
LOG((__FUNCTION__));
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
if (!mSources.RemoveElement(aSource)) {
|
|
// Already stopped - this is allowed
|
|
return NS_OK;
|
|
}
|
|
if (!mSources.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState != kStarted) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
mState = kStopped;
|
|
aSource->EndTrack(aID);
|
|
// Drop any cached image so we don't start with a stale image on next
|
|
// usage
|
|
mImage = nullptr;
|
|
}
|
|
|
|
NS_DispatchToMainThread(WrapRunnable(RefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::StopImpl));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
|
|
const MediaEnginePrefs& aPrefs,
|
|
const nsString& aDeviceId)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Initialization and Shutdown functions for the video source, called by the
|
|
* constructor and destructor respectively.
|
|
*/
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::Init()
|
|
{
|
|
nsAutoCString deviceName;
|
|
ICameraControl::GetCameraName(mCaptureIndex, deviceName);
|
|
SetName(NS_ConvertUTF8toUTF16(deviceName));
|
|
SetUUID(deviceName.get());
|
|
|
|
mInitDone = true;
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::Shutdown()
|
|
{
|
|
LOG((__FUNCTION__));
|
|
if (!mInitDone) {
|
|
return;
|
|
}
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState == kStarted) {
|
|
SourceMediaStream *source;
|
|
bool empty;
|
|
|
|
while (1) {
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
empty = mSources.IsEmpty();
|
|
if (empty) {
|
|
break;
|
|
}
|
|
source = mSources[0];
|
|
}
|
|
Stop(source, kVideoTrack); // XXX change to support multiple tracks
|
|
}
|
|
MOZ_ASSERT(mState == kStopped);
|
|
}
|
|
|
|
if (mState == kAllocated || mState == kStopped) {
|
|
Deallocate();
|
|
}
|
|
|
|
mState = kReleased;
|
|
mInitDone = false;
|
|
}
|
|
|
|
// All these functions must be run on MainThread!
|
|
void
|
|
MediaEngineGonkVideoSource::AllocImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
mCameraControl = ICameraControl::Create(mCaptureIndex);
|
|
if (mCameraControl) {
|
|
mState = kAllocated;
|
|
// Add this as a listener for CameraControl events. We don't need
|
|
// to explicitly remove this--destroying the CameraControl object
|
|
// in DeallocImpl() will do that for us.
|
|
mCameraControl->AddListener(this);
|
|
mTextureClientAllocator =
|
|
new layers::TextureClientRecycleAllocator(layers::ImageBridgeChild::GetSingleton());
|
|
mTextureClientAllocator->SetMaxPoolSize(WEBRTC_GONK_VIDEO_SOURCE_POOL_BUFFERS);
|
|
}
|
|
mCallbackMonitor.Notify();
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::DeallocImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mCameraControl = nullptr;
|
|
mTextureClientAllocator = nullptr;
|
|
}
|
|
|
|
// The same algorithm from bug 840244
|
|
static int
|
|
GetRotateAmount(ScreenOrientationInternal aScreen, int aCameraMountAngle, bool aBackCamera) {
|
|
int screenAngle = 0;
|
|
switch (aScreen) {
|
|
case eScreenOrientation_PortraitPrimary:
|
|
screenAngle = 0;
|
|
break;
|
|
case eScreenOrientation_PortraitSecondary:
|
|
screenAngle = 180;
|
|
break;
|
|
case eScreenOrientation_LandscapePrimary:
|
|
screenAngle = 90;
|
|
break;
|
|
case eScreenOrientation_LandscapeSecondary:
|
|
screenAngle = 270;
|
|
break;
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
int result;
|
|
|
|
if (aBackCamera) {
|
|
// back camera
|
|
result = (aCameraMountAngle - screenAngle + 360) % 360;
|
|
} else {
|
|
// front camera
|
|
result = (aCameraMountAngle + screenAngle) % 360;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// undefine to remove on-the-fly rotation support
|
|
#define DYNAMIC_GUM_ROTATION
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::Notify(const hal::ScreenConfiguration& aConfiguration) {
|
|
#ifdef DYNAMIC_GUM_ROTATION
|
|
if (mHasDirectListeners) {
|
|
// aka hooked to PeerConnection
|
|
MonitorAutoLock enter(mMonitor);
|
|
mRotation = GetRotateAmount(aConfiguration.orientation(), mCameraAngle, mBackCamera);
|
|
|
|
LOG(("*** New orientation: %d (Camera %d Back %d MountAngle: %d)",
|
|
mRotation, mCaptureIndex, mBackCamera, mCameraAngle));
|
|
}
|
|
#endif
|
|
|
|
mOrientationChanged = true;
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::StartImpl(webrtc::CaptureCapability aCapability) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
ICameraControl::Configuration config;
|
|
config.mMode = ICameraControl::kPictureMode;
|
|
config.mPreviewSize.width = aCapability.width;
|
|
config.mPreviewSize.height = aCapability.height;
|
|
config.mPictureSize.width = aCapability.width;
|
|
config.mPictureSize.height = aCapability.height;
|
|
mCameraControl->Start(&config);
|
|
|
|
hal::RegisterScreenConfigurationObserver(this);
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::StopImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mCameraSource.get()) {
|
|
mCameraSource->stop();
|
|
mCameraSource = nullptr;
|
|
}
|
|
|
|
hal::UnregisterScreenConfigurationObserver(this);
|
|
mCameraControl->Stop();
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::OnHardwareStateChange(HardwareState aState,
|
|
nsresult aReason)
|
|
{
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
switch (aState) {
|
|
case CameraControlListener::kHardwareClosed:
|
|
case CameraControlListener::kHardwareOpenFailed:
|
|
mState = kReleased;
|
|
mCallbackMonitor.Notify();
|
|
break;
|
|
case CameraControlListener::kHardwareOpen:
|
|
// Can't read this except on MainThread (ugh)
|
|
NS_DispatchToMainThread(WrapRunnable(RefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::GetRotation));
|
|
mState = kStarted;
|
|
mCallbackMonitor.Notify();
|
|
break;
|
|
case CameraControlListener::kHardwareUninitialized:
|
|
// When the first CameraControl listener is added, it gets pushed
|
|
// the current state of the camera--normally 'uninitialized'.
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::GetRotation()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MonitorAutoLock enter(mMonitor);
|
|
|
|
mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, mCameraAngle);
|
|
MOZ_ASSERT(mCameraAngle == 0 || mCameraAngle == 90 || mCameraAngle == 180 ||
|
|
mCameraAngle == 270);
|
|
hal::ScreenConfiguration config;
|
|
hal::GetCurrentScreenConfiguration(&config);
|
|
|
|
nsCString deviceName;
|
|
ICameraControl::GetCameraName(mCaptureIndex, deviceName);
|
|
if (deviceName.EqualsASCII("back")) {
|
|
mBackCamera = true;
|
|
}
|
|
|
|
mRotation = GetRotateAmount(config.orientation(), mCameraAngle, mBackCamera);
|
|
LOG(("*** Initial orientation: %d (Camera %d Back %d MountAngle: %d)",
|
|
mRotation, mCaptureIndex, mBackCamera, mCameraAngle));
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::OnUserError(UserContext aContext, nsresult aError)
|
|
{
|
|
{
|
|
// Scope the monitor, since there is another monitor below and we don't want
|
|
// unexpected deadlock.
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
mCallbackMonitor.Notify();
|
|
}
|
|
|
|
// A main thread runnable to send error code to all queued PhotoCallbacks.
|
|
class TakePhotoError : public nsRunnable {
|
|
public:
|
|
TakePhotoError(nsTArray<RefPtr<PhotoCallback>>& aCallbacks,
|
|
nsresult aRv)
|
|
: mRv(aRv)
|
|
{
|
|
mCallbacks.SwapElements(aCallbacks);
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
uint32_t callbackNumbers = mCallbacks.Length();
|
|
for (uint8_t i = 0; i < callbackNumbers; i++) {
|
|
mCallbacks[i]->PhotoError(mRv);
|
|
}
|
|
// PhotoCallback needs to dereference on main thread.
|
|
mCallbacks.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
nsTArray<RefPtr<PhotoCallback>> mCallbacks;
|
|
nsresult mRv;
|
|
};
|
|
|
|
if (aContext == UserContext::kInTakePicture) {
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (mPhotoCallbacks.Length()) {
|
|
NS_DispatchToMainThread(new TakePhotoError(mPhotoCallbacks, aError));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::OnTakePictureComplete(const uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
|
|
{
|
|
// It needs to start preview because Gonk camera will stop preview while
|
|
// taking picture.
|
|
mCameraControl->StartPreview();
|
|
|
|
// Create a main thread runnable to generate a blob and call all current queued
|
|
// PhotoCallbacks.
|
|
class GenerateBlobRunnable : public nsRunnable {
|
|
public:
|
|
GenerateBlobRunnable(nsTArray<RefPtr<PhotoCallback>>& aCallbacks,
|
|
const uint8_t* aData,
|
|
uint32_t aLength,
|
|
const nsAString& aMimeType)
|
|
: mPhotoDataLength(aLength)
|
|
{
|
|
mCallbacks.SwapElements(aCallbacks);
|
|
mPhotoData = (uint8_t*) malloc(aLength);
|
|
memcpy(mPhotoData, aData, mPhotoDataLength);
|
|
mMimeType = aMimeType;
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
RefPtr<dom::Blob> blob =
|
|
dom::Blob::CreateMemoryBlob(nullptr, mPhotoData, mPhotoDataLength, mMimeType);
|
|
uint32_t callbackCounts = mCallbacks.Length();
|
|
for (uint8_t i = 0; i < callbackCounts; i++) {
|
|
RefPtr<dom::Blob> tempBlob = blob;
|
|
mCallbacks[i]->PhotoComplete(tempBlob.forget());
|
|
}
|
|
// PhotoCallback needs to dereference on main thread.
|
|
mCallbacks.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTArray<RefPtr<PhotoCallback>> mCallbacks;
|
|
uint8_t* mPhotoData;
|
|
nsString mMimeType;
|
|
uint32_t mPhotoDataLength;
|
|
};
|
|
|
|
// All elements in mPhotoCallbacks will be swapped in GenerateBlobRunnable
|
|
// constructor. This captured image will be sent to all the queued
|
|
// PhotoCallbacks in this runnable.
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (mPhotoCallbacks.Length()) {
|
|
NS_DispatchToMainThread(
|
|
new GenerateBlobRunnable(mPhotoCallbacks, aData, aLength, aMimeType));
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::TakePhoto(PhotoCallback* aCallback)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
// If other callback exists, that means there is a captured picture on the way,
|
|
// it doesn't need to TakePicture() again.
|
|
if (!mPhotoCallbacks.Length()) {
|
|
nsresult rv;
|
|
if (mOrientationChanged) {
|
|
UpdatePhotoOrientation();
|
|
}
|
|
rv = mCameraControl->TakePicture();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mPhotoCallbacks.AppendElement(aCallback);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::UpdatePhotoOrientation()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
hal::ScreenConfiguration config;
|
|
hal::GetCurrentScreenConfiguration(&config);
|
|
|
|
// The rotation angle is clockwise.
|
|
int orientation = 0;
|
|
switch (config.orientation()) {
|
|
case eScreenOrientation_PortraitPrimary:
|
|
orientation = 0;
|
|
break;
|
|
case eScreenOrientation_PortraitSecondary:
|
|
orientation = 180;
|
|
break;
|
|
case eScreenOrientation_LandscapePrimary:
|
|
orientation = 270;
|
|
break;
|
|
case eScreenOrientation_LandscapeSecondary:
|
|
orientation = 90;
|
|
break;
|
|
}
|
|
|
|
// Front camera is inverse angle comparing to back camera.
|
|
orientation = (mBackCamera ? orientation : (-orientation));
|
|
|
|
ICameraControlParameterSetAutoEnter batch(mCameraControl);
|
|
// It changes the orientation value in EXIF information only.
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, orientation);
|
|
|
|
mOrientationChanged = false;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t
|
|
MediaEngineGonkVideoSource::ConvertPixelFormatToFOURCC(int aFormat)
|
|
{
|
|
switch (aFormat) {
|
|
case HAL_PIXEL_FORMAT_RGBA_8888:
|
|
return libyuv::FOURCC_BGRA;
|
|
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
|
|
return libyuv::FOURCC_NV21;
|
|
case HAL_PIXEL_FORMAT_YV12:
|
|
return libyuv::FOURCC_YV12;
|
|
default: {
|
|
LOG((" xxxxx Unknown pixel format %d", aFormat));
|
|
MOZ_ASSERT(false, "Unknown pixel format.");
|
|
return libyuv::FOURCC_ANY;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::RotateImage(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) {
|
|
layers::GrallocImage *nativeImage = static_cast<layers::GrallocImage*>(aImage);
|
|
android::sp<GraphicBuffer> graphicBuffer = nativeImage->GetGraphicBuffer();
|
|
void *pMem = nullptr;
|
|
// Bug 1109957 size will be wrong if width or height are odd
|
|
uint32_t size = aWidth * aHeight * 3 / 2;
|
|
MOZ_ASSERT(!(aWidth & 1) && !(aHeight & 1));
|
|
|
|
graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &pMem);
|
|
|
|
uint8_t* srcPtr = static_cast<uint8_t*>(pMem);
|
|
|
|
// Create a video frame and append it to the track.
|
|
RefPtr<layers::PlanarYCbCrImage> image = new GonkCameraImage();
|
|
|
|
uint32_t dstWidth;
|
|
uint32_t dstHeight;
|
|
|
|
if (mRotation == 90 || mRotation == 270) {
|
|
dstWidth = aHeight;
|
|
dstHeight = aWidth;
|
|
} else {
|
|
dstWidth = aWidth;
|
|
dstHeight = aHeight;
|
|
}
|
|
|
|
uint32_t half_width = dstWidth / 2;
|
|
|
|
MOZ_ASSERT(mTextureClientAllocator);
|
|
RefPtr<layers::TextureClient> textureClient
|
|
= mTextureClientAllocator->CreateOrRecycle(gfx::SurfaceFormat::YUV,
|
|
gfx::IntSize(dstWidth, dstHeight),
|
|
layers::BackendSelector::Content,
|
|
layers::TextureFlags::DEFAULT,
|
|
layers::ALLOC_DISALLOW_BUFFERTEXTURECLIENT);
|
|
if (textureClient) {
|
|
android::sp<android::GraphicBuffer> destBuffer =
|
|
static_cast<layers::GrallocTextureData*>(textureClient->GetInternalData())->GetGraphicBuffer();
|
|
|
|
void* destMem = nullptr;
|
|
destBuffer->lock(android::GraphicBuffer::USAGE_SW_WRITE_OFTEN, &destMem);
|
|
uint8_t* dstPtr = static_cast<uint8_t*>(destMem);
|
|
|
|
int32_t yStride = destBuffer->getStride();
|
|
// Align to 16 bytes boundary
|
|
int32_t uvStride = ((yStride / 2) + 15) & ~0x0F;
|
|
|
|
libyuv::ConvertToI420(srcPtr, size,
|
|
dstPtr, yStride,
|
|
dstPtr + (yStride * dstHeight + (uvStride * dstHeight / 2)), uvStride,
|
|
dstPtr + (yStride * dstHeight), uvStride,
|
|
0, 0,
|
|
graphicBuffer->getStride(), aHeight,
|
|
aWidth, aHeight,
|
|
static_cast<libyuv::RotationMode>(mRotation),
|
|
libyuv::FOURCC_NV21);
|
|
destBuffer->unlock();
|
|
|
|
image->AsGrallocImage()->SetData(textureClient, gfx::IntSize(dstWidth, dstHeight));
|
|
} else {
|
|
// Handle out of gralloc case.
|
|
image = mImageContainer->CreatePlanarYCbCrImage();
|
|
uint8_t* dstPtr = image->AsPlanarYCbCrImage()->AllocateAndGetNewBuffer(size);
|
|
|
|
libyuv::ConvertToI420(srcPtr, size,
|
|
dstPtr, dstWidth,
|
|
dstPtr + (dstWidth * dstHeight), half_width,
|
|
dstPtr + (dstWidth * dstHeight * 5 / 4), half_width,
|
|
0, 0,
|
|
graphicBuffer->getStride(), aHeight,
|
|
aWidth, aHeight,
|
|
static_cast<libyuv::RotationMode>(mRotation),
|
|
ConvertPixelFormatToFOURCC(graphicBuffer->getPixelFormat()));
|
|
|
|
const uint8_t lumaBpp = 8;
|
|
const uint8_t chromaBpp = 4;
|
|
|
|
layers::PlanarYCbCrData data;
|
|
data.mYChannel = dstPtr;
|
|
data.mYSize = IntSize(dstWidth, dstHeight);
|
|
data.mYStride = dstWidth * lumaBpp / 8;
|
|
data.mCbCrStride = dstWidth * chromaBpp / 8;
|
|
data.mCbChannel = dstPtr + dstHeight * data.mYStride;
|
|
data.mCrChannel = data.mCbChannel + data.mCbCrStride * (dstHeight / 2);
|
|
data.mCbCrSize = IntSize(dstWidth / 2, dstHeight / 2);
|
|
data.mPicX = 0;
|
|
data.mPicY = 0;
|
|
data.mPicSize = IntSize(dstWidth, dstHeight);
|
|
data.mStereoMode = StereoMode::MONO;
|
|
|
|
image->AsPlanarYCbCrImage()->SetDataNoCopy(data);
|
|
}
|
|
graphicBuffer->unlock();
|
|
|
|
// Implicitly releases last preview image.
|
|
mImage = image.forget();
|
|
}
|
|
|
|
bool
|
|
MediaEngineGonkVideoSource::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) {
|
|
{
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
if (mState == kStopped) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MonitorAutoLock enter(mMonitor);
|
|
// Bug XXX we'd prefer to avoid converting if mRotation == 0, but that causes problems in UpdateImage()
|
|
RotateImage(aImage, aWidth, aHeight);
|
|
if (mRotation != 0 && mRotation != 180) {
|
|
uint32_t temp = aWidth;
|
|
aWidth = aHeight;
|
|
aHeight = temp;
|
|
}
|
|
if (mWidth != static_cast<int>(aWidth) || mHeight != static_cast<int>(aHeight)) {
|
|
mWidth = aWidth;
|
|
mHeight = aHeight;
|
|
LOG(("Video FrameSizeChange: %ux%u", mWidth, mHeight));
|
|
}
|
|
|
|
return true; // return true because we're accepting the frame
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::OnNewMediaBufferFrame(MediaBuffer* aBuffer)
|
|
{
|
|
{
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
if (mState == kStopped) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
MonitorAutoLock enter(mMonitor);
|
|
if (mImage) {
|
|
if (mImage->AsGrallocImage()) {
|
|
// MediaEngineGonkVideoSource expects that GrallocImage is GonkCameraImage.
|
|
// See Bug 938034.
|
|
GonkCameraImage* cameraImage = static_cast<GonkCameraImage*>(mImage.get());
|
|
cameraImage->SetMediaBuffer(aBuffer);
|
|
} else {
|
|
LOG(("mImage is non-GrallocImage"));
|
|
}
|
|
|
|
uint32_t len = mSources.Length();
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
if (mSources[i]) {
|
|
// Duration is 1 here.
|
|
// Ideally, it should be camera timestamp here and the MSG will have
|
|
// enough sample duration without calling NotifyPull() anymore.
|
|
// Unfortunately, clock in gonk camera looks like is a different one
|
|
// comparing to MSG. As result, it causes time inaccurate. (frames be
|
|
// queued in MSG longer and longer as time going by in device like Frame)
|
|
AppendToTrack(mSources[i], mImage, mTrackID, 1);
|
|
}
|
|
}
|
|
if (mImage->AsGrallocImage()) {
|
|
GonkCameraImage* cameraImage = static_cast<GonkCameraImage*>(mImage.get());
|
|
// Clear MediaBuffer immediately, it prevents MediaBuffer is kept in
|
|
// MediaStreamGraph thread.
|
|
cameraImage->ClearMediaBuffer();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|