/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "base/basictypes.h" #include "AndroidCaptureProvider.h" #include "nsXULAppAPI.h" #include "AndroidBridge.h" #include "nsStreamUtils.h" #include "nsThreadUtils.h" #include "nsMemory.h" #include "nsRawStructs.h" // The maximum number of frames we keep in our queue. Don't live in the past. #define MAX_FRAMES_QUEUED 10 using namespace mozilla::net; NS_IMPL_THREADSAFE_ISUPPORTS2(AndroidCameraInputStream, nsIInputStream, nsIAsyncInputStream) AndroidCameraInputStream::AndroidCameraInputStream() : mWidth(0), mHeight(0), mCamera(0), mHeaderSent(false), mClosed(true), mFrameSize(0), mMonitor("AndroidCamera.Monitor") { mAvailable = sizeof(nsRawVideoHeader); mFrameQueue = new nsDeque(); } AndroidCameraInputStream::~AndroidCameraInputStream() { // clear the frame queue while (mFrameQueue->GetSize() > 0) { nsMemory::Free(mFrameQueue->PopFront()); } delete mFrameQueue; } NS_IMETHODIMP AndroidCameraInputStream::Init(nsACString& aContentType, nsCaptureParams* aParams) { if (XRE_GetProcessType() != GeckoProcessType_Default) return NS_ERROR_NOT_IMPLEMENTED; mContentType = aContentType; mWidth = aParams->width; mHeight = aParams->height; mCamera = aParams->camera; CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0); if (!impl) return NS_ERROR_OUT_OF_MEMORY; if (impl->Init(mContentType, mCamera, mWidth, mHeight, this)) { mWidth = impl->GetWidth(); mHeight = impl->GetHeight(); mClosed = false; } return NS_OK; } void AndroidCameraInputStream::ReceiveFrame(char* frame, PRUint32 length) { { mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor); if (mFrameQueue->GetSize() > MAX_FRAMES_QUEUED) { nsMemory::Free(mFrameQueue->PopFront()); mAvailable -= mFrameSize; } } mFrameSize = sizeof(nsRawPacketHeader) + length; char* fullFrame = (char*)nsMemory::Alloc(mFrameSize); if (!fullFrame) return; nsRawPacketHeader* header = (nsRawPacketHeader*) fullFrame; header->packetID = 0xFF; header->codecID = 0x595556; // "YUV" // we copy the Y plane, and de-interlace the CrCb PRUint32 yFrameSize = mWidth * mHeight; PRUint32 uvFrameSize = yFrameSize / 4; memcpy(fullFrame + sizeof(nsRawPacketHeader), frame, yFrameSize); char* uFrame = fullFrame + yFrameSize; char* vFrame = fullFrame + yFrameSize + uvFrameSize; char* yFrame = frame + yFrameSize; for (PRUint32 i = 0; i < uvFrameSize; i++) { uFrame[i] = yFrame[2 * i + 1]; vFrame[i] = yFrame[2 * i]; } { mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor); mAvailable += mFrameSize; mFrameQueue->Push((void*)fullFrame); } NotifyListeners(); } NS_IMETHODIMP AndroidCameraInputStream::Available(PRUint32 *aAvailable) { mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor); *aAvailable = mAvailable; return NS_OK; } NS_IMETHODIMP AndroidCameraInputStream::IsNonBlocking(bool *aNonBlock) { *aNonBlock = true; return NS_OK; } NS_IMETHODIMP AndroidCameraInputStream::Read(char *aBuffer, PRUint32 aCount, PRUint32 *aRead) { return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aRead); } NS_IMETHODIMP AndroidCameraInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, PRUint32 aCount, PRUint32 *aRead) { *aRead = 0; nsresult rv; if (mAvailable == 0) return NS_BASE_STREAM_WOULD_BLOCK; if (aCount > mAvailable) aCount = mAvailable; if (!mHeaderSent) { CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0); nsRawVideoHeader header; header.headerPacketID = 0; header.codecID = 0x595556; // "YUV" header.majorVersion = 0; header.minorVersion = 1; header.options = 1 | 1 << 1; // color, 4:2:2 header.alphaChannelBpp = 0; header.lumaChannelBpp = 8; header.chromaChannelBpp = 4; header.colorspace = 1; header.frameWidth = mWidth; header.frameHeight = mHeight; header.aspectNumerator = 1; header.aspectDenominator = 1; header.framerateNumerator = impl->GetFps(); header.framerateDenominator = 1; rv = aWriter(this, aClosure, (const char*)&header, 0, sizeof(nsRawVideoHeader), aRead); if (NS_FAILED(rv)) return NS_OK; mHeaderSent = true; aCount -= sizeof(nsRawVideoHeader); mAvailable -= sizeof(nsRawVideoHeader); } { mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor); while ((mAvailable > 0) && (aCount >= mFrameSize)) { PRUint32 readThisTime = 0; char* frame = (char*)mFrameQueue->PopFront(); rv = aWriter(this, aClosure, (const char*)frame, *aRead, mFrameSize, &readThisTime); if (readThisTime != mFrameSize) { mFrameQueue->PushFront((void*)frame); return NS_OK; } // nsRawReader does a copy when calling VideoData::Create() nsMemory::Free(frame); if (NS_FAILED(rv)) return NS_OK; aCount -= readThisTime; mAvailable -= readThisTime; *aRead += readThisTime; } } return NS_OK; } NS_IMETHODIMP AndroidCameraInputStream::Close() { return CloseWithStatus(NS_OK); } /** * must be called on the main (java) thread */ void AndroidCameraInputStream::doClose() { NS_ASSERTION(!mClosed, "Camera is already closed"); CameraStreamImpl *impl = CameraStreamImpl::GetInstance(0); impl->Close(); mClosed = true; } void AndroidCameraInputStream::NotifyListeners() { mozilla::ReentrantMonitorAutoEnter autoMonitor(mMonitor); if (mCallback && (mAvailable > sizeof(nsRawVideoHeader))) { nsCOMPtr callback; if (mCallbackTarget) { NS_NewInputStreamReadyEvent(getter_AddRefs(callback), mCallback, mCallbackTarget); } else { callback = mCallback; } NS_ASSERTION(callback, "Shouldn't fail to make the callback!"); // Null the callback first because OnInputStreamReady may reenter AsyncWait mCallback = nsnull; mCallbackTarget = nsnull; callback->OnInputStreamReady(this); } } NS_IMETHODIMP AndroidCameraInputStream::AsyncWait(nsIInputStreamCallback *aCallback, PRUint32 aFlags, PRUint32 aRequestedCount, nsIEventTarget *aTarget) { if (aFlags != 0) return NS_ERROR_NOT_IMPLEMENTED; if (mCallback || mCallbackTarget) return NS_ERROR_UNEXPECTED; mCallbackTarget = aTarget; mCallback = aCallback; // What we are being asked for may be present already NotifyListeners(); return NS_OK; } NS_IMETHODIMP AndroidCameraInputStream::CloseWithStatus(PRUint32 status) { AndroidCameraInputStream::doClose(); return NS_OK; } /** * AndroidCaptureProvider implementation */ NS_IMPL_THREADSAFE_ISUPPORTS0(AndroidCaptureProvider) AndroidCaptureProvider* AndroidCaptureProvider::sInstance = NULL; AndroidCaptureProvider::AndroidCaptureProvider() { } AndroidCaptureProvider::~AndroidCaptureProvider() { AndroidCaptureProvider::sInstance = NULL; } nsresult AndroidCaptureProvider::Init(nsACString& aContentType, nsCaptureParams* aParams, nsIInputStream** aStream) { NS_ENSURE_ARG_POINTER(aParams); NS_ASSERTION(aParams->frameLimit == 0 || aParams->timeLimit == 0, "Cannot set both a frame limit and a time limit!"); nsRefPtr stream; if (aContentType.EqualsLiteral("video/x-raw-yuv")) { stream = new AndroidCameraInputStream(); if (stream) { nsresult rv = stream->Init(aContentType, aParams); if (NS_FAILED(rv)) return rv; } else return NS_ERROR_OUT_OF_MEMORY; } else { NS_NOTREACHED("Should not have asked Android for this type!"); } return CallQueryInterface(stream, aStream); } already_AddRefed GetAndroidCaptureProvider() { if (!AndroidCaptureProvider::sInstance) { AndroidCaptureProvider::sInstance = new AndroidCaptureProvider(); } AndroidCaptureProvider::sInstance->AddRef(); return AndroidCaptureProvider::sInstance; }