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