mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
668 lines
20 KiB
C++
668 lines
20 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"
|
|
|
|
#define LOG_TAG "MediaEngineGonkVideoSource"
|
|
|
|
#include <utils/Log.h>
|
|
|
|
#include "GrallocImages.h"
|
|
#include "VideoUtils.h"
|
|
#include "ScreenOrientation.h"
|
|
|
|
#include "libyuv.h"
|
|
#include "mtransport/runnable_utils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::gfx;
|
|
|
|
#ifdef PR_LOGGING
|
|
extern PRLogModuleInfo* GetMediaManagerLog();
|
|
#define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
|
|
#define LOGFRAME(msg) PR_LOG(GetMediaManagerLog(), 6, msg)
|
|
#else
|
|
#define LOG(msg)
|
|
#define LOGFRAME(msg)
|
|
#endif
|
|
|
|
// 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,
|
|
StreamTime& aLastEndTime)
|
|
{
|
|
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
|
|
nsRefPtr<layers::Image> image = mImage;
|
|
StreamTime delta = aDesiredTime - aLastEndTime;
|
|
LOGFRAME(("NotifyPull, desired = %ld, delta = %ld %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.
|
|
if (aSource->AppendToTrack(aID, &(segment))) {
|
|
aLastEndTime = aDesiredTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::ChooseCapability(const VideoTrackConstraintsN& aConstraints,
|
|
const MediaEnginePrefs& aPrefs)
|
|
{
|
|
return GuessCapability(aConstraints, aPrefs);
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints,
|
|
const MediaEnginePrefs& aPrefs)
|
|
{
|
|
LOG((__FUNCTION__));
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
if (mState == kReleased && mInitDone) {
|
|
ChooseCapability(aConstraints, aPrefs);
|
|
NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::AllocImpl));
|
|
mCallbackMonitor.Wait();
|
|
if (mState != kAllocated) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Deallocate()
|
|
{
|
|
LOG((__FUNCTION__));
|
|
if (mSources.IsEmpty()) {
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState != kStopped && mState != kAllocated) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// We do not register success callback here
|
|
|
|
NS_DispatchToMainThread(WrapRunnable(nsRefPtr<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;
|
|
}
|
|
|
|
mSources.AppendElement(aStream);
|
|
|
|
aStream->AddTrack(aID, 0, new VideoSegment());
|
|
aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState == kStarted) {
|
|
return NS_OK;
|
|
}
|
|
mTrackID = aID;
|
|
mImageContainer = layers::LayerManager::CreateImageContainer();
|
|
|
|
NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::StartImpl,
|
|
mCapability));
|
|
mCallbackMonitor.Wait();
|
|
if (mState != kStarted) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaEngineGonkVideoSource::Stop(SourceMediaStream* aSource, TrackID aID)
|
|
{
|
|
LOG((__FUNCTION__));
|
|
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(nsRefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::StopImpl));
|
|
|
|
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);
|
|
CopyUTF8toUTF16(deviceName, mDeviceName);
|
|
CopyUTF8toUTF16(deviceName, mUniqueId);
|
|
|
|
mInitDone = true;
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::Shutdown()
|
|
{
|
|
LOG((__FUNCTION__));
|
|
if (!mInitDone) {
|
|
return;
|
|
}
|
|
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
|
|
if (mState == kStarted) {
|
|
while (!mSources.IsEmpty()) {
|
|
Stop(mSources[0], 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);
|
|
}
|
|
mCallbackMonitor.Notify();
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::DeallocImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mCameraControl = nullptr;
|
|
}
|
|
|
|
// The same algorithm from bug 840244
|
|
static int
|
|
GetRotateAmount(ScreenOrientation 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;
|
|
mCameraControl->Start(&config);
|
|
mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, config.mPreviewSize);
|
|
|
|
hal::RegisterScreenConfigurationObserver(this);
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::StopImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
hal::UnregisterScreenConfigurationObserver(this);
|
|
mCameraControl->Stop();
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::OnHardwareStateChange(HardwareState aState)
|
|
{
|
|
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
|
if (aState == CameraControlListener::kHardwareClosed) {
|
|
// When the first CameraControl listener is added, it gets pushed
|
|
// the current state of the camera--normally 'closed'. We only
|
|
// pay attention to that state if we've progressed out of the
|
|
// allocated state.
|
|
if (mState != kAllocated) {
|
|
mState = kReleased;
|
|
mCallbackMonitor.Notify();
|
|
}
|
|
} else {
|
|
// Can't read this except on MainThread (ugh)
|
|
NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
|
|
&MediaEngineGonkVideoSource::GetRotation));
|
|
mState = kStarted;
|
|
mCallbackMonitor.Notify();
|
|
}
|
|
}
|
|
|
|
|
|
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<nsRefPtr<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<nsRefPtr<PhotoCallback>> mCallbacks;
|
|
nsresult mRv;
|
|
};
|
|
|
|
if (aContext == UserContext::kInTakePicture) {
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (mPhotoCallbacks.Length()) {
|
|
NS_DispatchToMainThread(new TakePhotoError(mPhotoCallbacks, aError));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MediaEngineGonkVideoSource::OnTakePictureComplete(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<nsRefPtr<PhotoCallback>>& aCallbacks,
|
|
uint8_t* aData,
|
|
uint32_t aLength,
|
|
const nsAString& aMimeType)
|
|
: mPhotoDataLength(aLength)
|
|
{
|
|
mCallbacks.SwapElements(aCallbacks);
|
|
mPhotoData = (uint8_t*) moz_malloc(aLength);
|
|
memcpy(mPhotoData, aData, mPhotoDataLength);
|
|
mMimeType = aMimeType;
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
nsRefPtr<dom::File> blob =
|
|
dom::File::CreateMemoryFile(nullptr, mPhotoData, mPhotoDataLength, mMimeType);
|
|
uint32_t callbackCounts = mCallbacks.Length();
|
|
for (uint8_t i = 0; i < callbackCounts; i++) {
|
|
nsRefPtr<dom::File> tempBlob = blob;
|
|
mCallbacks[i]->PhotoComplete(tempBlob.forget());
|
|
}
|
|
// PhotoCallback needs to dereference on main thread.
|
|
mCallbacks.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTArray<nsRefPtr<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<android::GraphicBuffer> graphicBuffer = nativeImage->GetGraphicBuffer();
|
|
void *pMem = nullptr;
|
|
uint32_t size = aWidth * aHeight * 3 / 2;
|
|
|
|
graphicBuffer->lock(android::GraphicBuffer::USAGE_SW_READ_MASK, &pMem);
|
|
|
|
uint8_t* srcPtr = static_cast<uint8_t*>(pMem);
|
|
// Create a video frame and append it to the track.
|
|
nsRefPtr<layers::Image> image = mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
|
|
layers::PlanarYCbCrImage* videoImage = static_cast<layers::PlanarYCbCrImage*>(image.get());
|
|
|
|
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;
|
|
uint8_t* dstPtr = videoImage->AllocateAndGetNewBuffer(size);
|
|
libyuv::ConvertToI420(srcPtr, size,
|
|
dstPtr, dstWidth,
|
|
dstPtr + (dstWidth * dstHeight), half_width,
|
|
dstPtr + (dstWidth * dstHeight * 5 / 4), half_width,
|
|
0, 0,
|
|
aWidth, aHeight,
|
|
aWidth, aHeight,
|
|
static_cast<libyuv::RotationMode>(mRotation),
|
|
ConvertPixelFormatToFOURCC(graphicBuffer->getPixelFormat()));
|
|
graphicBuffer->unlock();
|
|
|
|
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 +( dstHeight * data.mCbCrStride / 2);
|
|
data.mCbCrSize = IntSize(dstWidth / 2, dstHeight / 2);
|
|
data.mPicX = 0;
|
|
data.mPicY = 0;
|
|
data.mPicSize = IntSize(dstWidth, dstHeight);
|
|
data.mStereoMode = StereoMode::MONO;
|
|
|
|
videoImage->SetDataNoCopy(data);
|
|
|
|
// implicitly releases last image
|
|
mImage = image.forget();
|
|
|
|
// Push the frame into the MSG with a minimal duration. This will likely
|
|
// mean we'll still get NotifyPull calls which will then return the same
|
|
// frame again with a longer duration. However, this means we won't
|
|
// fail to get the frame in and drop frames.
|
|
|
|
// XXX The timestamp for the frame should be base on the Capture time,
|
|
// not the MSG time, and MSG should never, ever block on a (realtime)
|
|
// video frame (or even really for streaming - audio yes, video probably no).
|
|
uint32_t len = mSources.Length();
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
if (mSources[i]) {
|
|
AppendToTrack(mSources[i], mImage, mTrackID, 1); // shortest possible duration
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
} // namespace mozilla
|