/* -*- 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 #include "android/log.h" #include "cutils/properties.h" #include "base/basictypes.h" #include "GonkCaptureProvider.h" #include "nsXULAppAPI.h" #include "nsStreamUtils.h" #include "nsThreadUtils.h" #include "nsRawStructs.h" #include "prinit.h" #define USE_GS2_LIBCAMERA #define CameraHardwareInterface CameraHardwareInterface_SGS2 #define HAL_openCameraHardware HAL_openCameraHardware_SGS2 #include "camera/CameraHardwareInterface.h" #undef CameraHardwareInterface #undef USE_GS2_LIBCAMERA #undef HAL_openCameraHardware #undef ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H // Needed to prevent a redefinition of 'struct android::image_rect_struct'... #define image_rect_type image_rect_type2 #define image_rect_struct image_rect_struct2 #define USE_MAGURO_LIBCAMERA #define CameraHardwareInterface CameraHardwareInterface_MAGURO #define HAL_openCameraHardware HAL_openCameraHardware_MAGURO #include "camera/CameraHardwareInterface.h" #undef CameraHardwareInterface #undef USE_MAGURO_LIBCAMERA #undef HAL_openCameraHardware #undef ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H #undef image_rect_type #undef image_rect_struct #define image_rect_type image_rect_type3 #define image_rect_struct image_rect_struct3 #define CameraHardwareInterface CameraHardwareInterface_DEFAULT #include "camera/CameraHardwareInterface.h" #undef CameraHardwareInterface using namespace android; using namespace mozilla; class CameraHardwareInterface { public: enum Type { CAMERA_SGS2, CAMERA_MAGURO, CAMERA_DEFAULT }; static Type getType() { char propValue[PROPERTY_VALUE_MAX]; property_get("ro.product.board", propValue, NULL); if (!strcmp(propValue, "GT-I9100")) return CAMERA_SGS2; if (!strcmp(propValue, "msm7627a_sku1") || !strcmp(propValue, "MSM7627A_SKU3")) return CAMERA_MAGURO; printf_stderr("CameraHardwareInterface : unsupported camera for device %s\n", propValue); return CAMERA_DEFAULT; } static CameraHardwareInterface* openCamera(PRUint32 aCamera); virtual ~CameraHardwareInterface() { }; virtual bool ok(); virtual void enableMsgType(int32_t msgType); virtual void disableMsgType(int32_t msgType); virtual bool msgTypeEnabled(int32_t msgType); virtual void setCallbacks(notify_callback notify_cb, data_callback data_cb, data_callback_timestamp data_cb_timestamp, void* user); virtual status_t startPreview(); virtual void stopPreview(); virtual void release(); virtual status_t setParameters(const CameraParameters& params); virtual CameraParameters getParameters() const; protected: CameraHardwareInterface(PRUint32 aCamera = 0) { }; }; // Intentionally not trying to dlclose() this handle. That's playing // Russian roulette with security bugs. static void* sCameraLib; static PRCallOnceType sInitCameraLib; static PRStatus InitCameraLib() { sCameraLib = dlopen("/system/lib/libcamera.so", RTLD_LAZY); // We might fail to open the camera lib. That's OK. return PR_SUCCESS; } static void* GetCameraLibHandle() { PR_CallOnce(&sInitCameraLib, InitCameraLib); return sCameraLib; } template class CameraImpl : public CameraHardwareInterface { public: typedef sp (*HAL_openCameraHardware_DEFAULT)(int); typedef sp (*HAL_openCameraHardware_SGS2)(int); typedef sp (*HAL_openCameraHardware_MAGURO)(int, int); CameraImpl(PRUint32 aCamera = 0) : mOk(false), mCamera(nsnull) { void* cameraLib = GetCameraLibHandle(); if (!cameraLib) { printf_stderr("CameraImpl: Failed to dlopen() camera library."); return; } void *hal = dlsym(cameraLib, "HAL_openCameraHardware"); HAL_openCameraHardware_DEFAULT funct0; HAL_openCameraHardware_SGS2 funct1; HAL_openCameraHardware_MAGURO funct2; switch(getType()) { case CAMERA_SGS2: funct1 = reinterpret_cast (hal); mCamera = funct1(aCamera); break; case CAMERA_MAGURO: funct2 = reinterpret_cast (hal); mCamera = funct2(aCamera, 1); break; case CAMERA_DEFAULT: funct0 = reinterpret_cast (hal); mCamera = funct0(aCamera); break; } mOk = mCamera != nsnull; if (!mOk) { printf_stderr("CameraImpl: HAL_openCameraHardware() returned NULL (no camera interface)."); } } bool ok() { return mOk; }; void enableMsgType(int32_t msgType) { mCamera->enableMsgType(msgType); }; void disableMsgType(int32_t msgType) { mCamera->disableMsgType(msgType); }; bool msgTypeEnabled(int32_t msgType) { return mCamera->msgTypeEnabled(msgType); }; void setCallbacks(notify_callback notify_cb, data_callback data_cb, data_callback_timestamp data_cb_timestamp, void* user) { mCamera->setCallbacks(notify_cb, data_cb, data_cb_timestamp, user); }; status_t startPreview() { return mCamera->startPreview(); }; void stopPreview() { mCamera->stopPreview(); }; void release() { return mCamera->release(); }; status_t setParameters(const CameraParameters& params) { return mCamera->setParameters(params); } CameraParameters getParameters() const { return mCamera->getParameters(); }; protected: bool mOk; sp mCamera; }; CameraHardwareInterface* CameraHardwareInterface::openCamera(PRUint32 aCamera) { nsAutoPtr instance; switch(getType()) { case CAMERA_SGS2: instance = new CameraImpl(aCamera); break; case CAMERA_MAGURO: instance = new CameraImpl(aCamera); break; case CAMERA_DEFAULT: instance = new CameraImpl(aCamera); break; } if (!instance->ok()) { return nsnull; } return instance.forget(); }; // The maximum number of frames we keep in our queue. Don't live in the past. #define MAX_FRAMES_QUEUED 5 NS_IMPL_THREADSAFE_ISUPPORTS2(GonkCameraInputStream, nsIInputStream, nsIAsyncInputStream) GonkCameraInputStream::GonkCameraInputStream() : mAvailable(sizeof(nsRawVideoHeader)), mWidth(0), mHeight(0), mFps(30), mCamera(0), mHeaderSent(false), mClosed(true), mIs420p(false), mFrameSize(0), mMonitor("GonkCamera.Monitor") { } GonkCameraInputStream::~GonkCameraInputStream() { // clear the frame queue while (mFrameQueue.GetSize() > 0) { free(mFrameQueue.PopFront()); } // no need to close Close() since the stream is opened here : // http://mxr.mozilla.org/mozilla-central/source/netwerk/base/src/nsBaseChannel.cpp#239 // and this leads to http://mxr.mozilla.org/mozilla-central/source/netwerk/base/src/nsBaseChannel.cpp#259 // that creates and input pump with closeWhenDone == true // the pump cleans up properly at http://mxr.mozilla.org/mozilla-central/source/netwerk/base/src/nsInputStreamPump.cpp#565 } void GonkCameraInputStream::DataCallback(int32_t aMsgType, const sp& aDataPtr, void *aUser) { GonkCameraInputStream* stream = (GonkCameraInputStream*)(aUser); stream->ReceiveFrame((char*)aDataPtr->pointer(), aDataPtr->size()); } PRUint32 GonkCameraInputStream::getNumberOfCameras() { typedef int (*HAL_getNumberOfCamerasFunct)(void); void* cameraLib = GetCameraLibHandle(); if (!cameraLib) return 0; void *hal = dlsym(cameraLib, "HAL_getNumberOfCameras"); if (nsnull == hal) return 0; HAL_getNumberOfCamerasFunct funct = reinterpret_cast (hal); return funct(); } NS_IMETHODIMP GonkCameraInputStream::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; PRUint32 maxNumCameras = getNumberOfCameras(); if (maxNumCameras == 0) return NS_ERROR_FAILURE; if (mCamera >= maxNumCameras) mCamera = 0; mHardware = CameraHardwareInterface::openCamera(mCamera); if (!mHardware) return NS_ERROR_FAILURE; mHardware->setCallbacks(NULL, GonkCameraInputStream::DataCallback, NULL, this); mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); CameraParameters params = mHardware->getParameters(); printf_stderr("Preview format : %s\n", params.get(params.KEY_SUPPORTED_PREVIEW_FORMATS)); Vector previewSizes; params.getSupportedPreviewSizes(previewSizes); // find the available preview size closest to the requested size. PRUint32 minSizeDelta = PR_UINT32_MAX; PRUint32 bestWidth = mWidth; PRUint32 bestHeight = mHeight; for (PRUint32 i = 0; i < previewSizes.size(); i++) { Size size = previewSizes[i]; PRUint32 delta = abs(size.width * size.height - mWidth * mHeight); if (delta < minSizeDelta) { minSizeDelta = delta; bestWidth = size.width; bestHeight = size.height; } } mWidth = bestWidth; mHeight = bestHeight; params.setPreviewSize(mWidth, mHeight); // try to set preferred image format params.setPreviewFormat("yuv420p"); params.setPreviewFrameRate(mFps); mHardware->setParameters(params); params = mHardware->getParameters(); mFps = params.getPreviewFrameRate(); mIs420p = !strcmp(params.getPreviewFormat(), "yuv420p"); mHardware->startPreview(); mClosed = false; return NS_OK; } void GonkCameraInputStream::ReceiveFrame(char* frame, PRUint32 length) { { ReentrantMonitorAutoEnter enter(mMonitor); if (mFrameQueue.GetSize() > MAX_FRAMES_QUEUED) { free(mFrameQueue.PopFront()); mAvailable -= mFrameSize; } } mFrameSize = sizeof(nsRawPacketHeader) + length; char* fullFrame = (char*)moz_malloc(mFrameSize); if (!fullFrame) return; nsRawPacketHeader* header = reinterpret_cast (fullFrame); header->packetID = 0xFF; header->codecID = RAW_ID; if (mIs420p) { memcpy(fullFrame + sizeof(nsRawPacketHeader), frame, length); } else { // 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 + sizeof(nsRawPacketHeader) + yFrameSize; char* vFrame = fullFrame + sizeof(nsRawPacketHeader) + yFrameSize + uvFrameSize; const char* yFrame = frame + yFrameSize; for (PRUint32 i = 0; i < uvFrameSize; i++) { uFrame[i] = yFrame[2 * i + 1]; vFrame[i] = yFrame[2 * i]; } } { ReentrantMonitorAutoEnter enter(mMonitor); mAvailable += mFrameSize; mFrameQueue.Push((void*)fullFrame); } NotifyListeners(); } NS_IMETHODIMP GonkCameraInputStream::Available(PRUint32 *aAvailable) { ReentrantMonitorAutoEnter enter(mMonitor); *aAvailable = mAvailable; return NS_OK; } NS_IMETHODIMP GonkCameraInputStream::IsNonBlocking(bool *aNonBlock) { *aNonBlock = true; return NS_OK; } NS_IMETHODIMP GonkCameraInputStream::Read(char *aBuffer, PRUint32 aCount, PRUint32 *aRead NS_OUTPARAM) { return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aRead); } NS_IMETHODIMP GonkCameraInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, PRUint32 aCount, PRUint32 *aRead NS_OUTPARAM) { *aRead = 0; nsresult rv; if (mAvailable == 0) return NS_BASE_STREAM_WOULD_BLOCK; if (aCount > mAvailable) aCount = mAvailable; if (!mHeaderSent) { nsRawVideoHeader header; header.headerPacketID = 0; header.codecID = RAW_ID; header.majorVersion = 0; header.minorVersion = 1; header.options = 1 | 1 << 1; // color, 4:2:0 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 = mFps; 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); } { ReentrantMonitorAutoEnter enter(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() free(frame); if (NS_FAILED(rv)) return NS_OK; aCount -= readThisTime; mAvailable -= readThisTime; *aRead += readThisTime; } } return NS_OK; } NS_IMETHODIMP GonkCameraInputStream::Close() { return CloseWithStatus(NS_OK); } void GonkCameraInputStream::doClose() { ReentrantMonitorAutoEnter enter(mMonitor); if (mClosed) return; mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS); mHardware->stopPreview(); mHardware->release(); delete mHardware; mClosed = true; } void GonkCameraInputStream::NotifyListeners() { ReentrantMonitorAutoEnter enter(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 GonkCameraInputStream::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 GonkCameraInputStream::CloseWithStatus(PRUint32 status) { GonkCameraInputStream::doClose(); return NS_OK; } /** * GonkCaptureProvider implementation */ NS_IMPL_ISUPPORTS0(GonkCaptureProvider) GonkCaptureProvider* GonkCaptureProvider::sInstance = NULL; GonkCaptureProvider::GonkCaptureProvider() { } GonkCaptureProvider::~GonkCaptureProvider() { GonkCaptureProvider::sInstance = NULL; } nsresult GonkCaptureProvider::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 GonkCameraInputStream(); 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 Gonk for this type!"); } return CallQueryInterface(stream, aStream); } already_AddRefed GetGonkCaptureProvider() { if (!GonkCaptureProvider::sInstance) { GonkCaptureProvider::sInstance = new GonkCaptureProvider(); } GonkCaptureProvider::sInstance->AddRef(); return GonkCaptureProvider::sInstance; }