/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Contains implementation of an abstract class EmulatedCameraDevice that defines * functionality expected from an emulated physical camera device: * - Obtaining and setting camera parameters * - Capturing frames * - Streaming video * - etc. */ #define LOG_NDEBUG 0 #define LOG_TAG "EmulatedCamera_Device" #include #include #include #include "Alignment.h" #include "EmulatedCamera.h" #include "EmulatedCameraDevice.h" #undef min #undef max #include namespace android { const float GAMMA_CORRECTION = 2.2f; EmulatedCameraDevice::EmulatedCameraDevice(EmulatedCamera* camera_hal) : mObjectLock(), mCameraHAL(camera_hal), mExposureCompensation(1.0f), mWhiteBalanceScale(NULL), mSupportedWhiteBalanceScale(), mState(ECDS_CONSTRUCTED), mTriggerAutoFocus(false) { } EmulatedCameraDevice::~EmulatedCameraDevice() { ALOGV("EmulatedCameraDevice destructor"); for (size_t i = 0; i < mSupportedWhiteBalanceScale.size(); ++i) { if (mSupportedWhiteBalanceScale.valueAt(i) != NULL) { delete[] mSupportedWhiteBalanceScale.valueAt(i); } } } /**************************************************************************** * Emulated camera device public API ***************************************************************************/ status_t EmulatedCameraDevice::Initialize() { if (isInitialized()) { ALOGW("%s: Emulated camera device is already initialized: mState = %d", __FUNCTION__, mState); return NO_ERROR; } mState = ECDS_INITIALIZED; return NO_ERROR; } status_t EmulatedCameraDevice::startDeliveringFrames(bool one_burst) { ALOGV("%s", __FUNCTION__); if (!isStarted()) { ALOGE("%s: Device is not started", __FUNCTION__); return EINVAL; } /* Frames will be delivered from the thread routine. */ const status_t res = startWorkerThread(one_burst); ALOGE_IF(res != NO_ERROR, "%s: startWorkerThread failed", __FUNCTION__); return res; } status_t EmulatedCameraDevice::stopDeliveringFrames() { ALOGV("%s", __FUNCTION__); if (!isStarted()) { ALOGW("%s: Device is not started", __FUNCTION__); return NO_ERROR; } const status_t res = stopWorkerThread(); ALOGE_IF(res != NO_ERROR, "%s: stopWorkerThread failed", __FUNCTION__); return res; } status_t EmulatedCameraDevice::setPreviewFrameRate(int framesPerSecond) { if (framesPerSecond <= 0) { return EINVAL; } mFramesPerSecond = framesPerSecond; return NO_ERROR; } void EmulatedCameraDevice::setExposureCompensation(const float ev) { ALOGV("%s", __FUNCTION__); if (!isStarted()) { ALOGW("%s: Fake camera device is not started.", __FUNCTION__); } mExposureCompensation = std::pow(2.0f, ev / GAMMA_CORRECTION); ALOGV("New exposure compensation is %f", mExposureCompensation); } void EmulatedCameraDevice::initializeWhiteBalanceModes(const char* mode, const float r_scale, const float b_scale) { ALOGV("%s with %s, %f, %f", __FUNCTION__, mode, r_scale, b_scale); float* value = new float[3]; value[0] = r_scale; value[1] = 1.0f; value[2] = b_scale; mSupportedWhiteBalanceScale.add(String8(mode), value); } void EmulatedCameraDevice::setWhiteBalanceMode(const char* mode) { ALOGV("%s with white balance %s", __FUNCTION__, mode); mWhiteBalanceScale = mSupportedWhiteBalanceScale.valueFor(String8(mode)); } /* Computes the pixel value after adjusting the white balance to the current * one. The input the y, u, v channel of the pixel and the adjusted value will * be stored in place. The adjustment is done in RGB space. */ void EmulatedCameraDevice::changeWhiteBalance(uint8_t& y, uint8_t& u, uint8_t& v) const { float r_scale = mWhiteBalanceScale[0]; float b_scale = mWhiteBalanceScale[2]; int r = static_cast(YUV2R(y, u, v)) / r_scale; int g = YUV2G(y, u, v); int b = static_cast(YUV2B(y, u, v)) / b_scale; y = RGB2Y(r, g, b); u = RGB2U(r, g, b); v = RGB2V(r, g, b); } void EmulatedCameraDevice::checkAutoFocusTrigger() { // The expected value is a reference so we need it to be a variable bool expectedTrigger = true; if (mTriggerAutoFocus.compare_exchange_strong(expectedTrigger, false)) { // If the compare exchange returns true then the value was the expected // 'true' and was successfully set to 'false'. So that means it's time // to trigger an auto-focus event and that we have disabled that trigger // so it won't happen until another request is received. mCameraHAL->autoFocusComplete(); } } status_t EmulatedCameraDevice::getCurrentFrameImpl(const uint8_t* source, uint8_t* dest, uint32_t pixelFormat) const { if (pixelFormat == mPixelFormat) { memcpy(dest, source, mFrameBufferSize); return NO_ERROR; } else if (pixelFormat == V4L2_PIX_FMT_YUV420 && mPixelFormat == V4L2_PIX_FMT_YVU420) { // Convert from YV12 to YUV420 without alignment const int ySize = mYStride * mFrameHeight; const int uvSize = mUVStride * (mFrameHeight / 2); if (mYStride == mFrameWidth) { // Copy Y straight up memcpy(dest, source, ySize); } else { // Strip alignment for (int y = 0; y < mFrameHeight; ++y) { memcpy(dest + y * mFrameWidth, source + y * mYStride, mFrameWidth); } } if (mUVStride == mFrameWidth / 2) { // Swap U and V memcpy(dest + ySize, source + ySize + uvSize, uvSize); memcpy(dest + ySize + uvSize, source + ySize, uvSize); } else { // Strip alignment uint8_t* uvDest = dest + mFrameWidth * mFrameHeight; const uint8_t* uvSource = source + ySize + uvSize; for (int i = 0; i < 2; ++i) { for (int y = 0; y < mFrameHeight / 2; ++y) { memcpy(uvDest + y * (mFrameWidth / 2), uvSource + y * mUVStride, mFrameWidth / 2); } uvDest += (mFrameHeight / 2) * (mFrameWidth / 2); uvSource -= uvSize; } } return NO_ERROR; } ALOGE("%s: Invalid pixel format conversion [%.4s to %.4s] requested", __FUNCTION__, reinterpret_cast(&mPixelFormat), reinterpret_cast(&pixelFormat)); return EINVAL; } status_t EmulatedCameraDevice::getCurrentFrame(void* buffer, uint32_t pixelFormat, int64_t* timestamp) { if (!isStarted()) { ALOGE("%s: Device is not started", __FUNCTION__); return EINVAL; } if (buffer == nullptr) { ALOGE("%s: Invalid buffer provided", __FUNCTION__); return EINVAL; } FrameLock lock(*this); const void* source = mCameraThread->getPrimaryBuffer(); if (source == nullptr) { ALOGE("%s: No framebuffer", __FUNCTION__); return EINVAL; } if (timestamp != nullptr) { *timestamp = mCameraThread->getPrimaryTimestamp(); } return getCurrentFrameImpl(reinterpret_cast(source), reinterpret_cast(buffer), pixelFormat); } status_t EmulatedCameraDevice::getCurrentPreviewFrame(void* buffer, int64_t* timestamp) { if (!isStarted()) { ALOGE("%s: Device is not started", __FUNCTION__); return EINVAL; } if (buffer == nullptr) { ALOGE("%s: Invalid buffer provided", __FUNCTION__); return EINVAL; } FrameLock lock(*this); const void* currentFrame = mCameraThread->getPrimaryBuffer(); if (currentFrame == nullptr) { ALOGE("%s: No framebuffer", __FUNCTION__); return EINVAL; } if (timestamp != nullptr) { *timestamp = mCameraThread->getPrimaryTimestamp(); } /* In emulation the framebuffer is never RGB. */ switch (mPixelFormat) { case V4L2_PIX_FMT_YVU420: YV12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight); return NO_ERROR; case V4L2_PIX_FMT_YUV420: YU12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight); return NO_ERROR; case V4L2_PIX_FMT_NV21: NV21ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight); return NO_ERROR; case V4L2_PIX_FMT_NV12: NV12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight); return NO_ERROR; default: ALOGE("%s: Unknown pixel format %.4s", __FUNCTION__, reinterpret_cast(&mPixelFormat)); return EINVAL; } } const void* EmulatedCameraDevice::getCurrentFrame() { if (mCameraThread.get()) { return mCameraThread->getPrimaryBuffer(); } return nullptr; } EmulatedCameraDevice::FrameLock::FrameLock(EmulatedCameraDevice& cameraDevice) : mCameraDevice(cameraDevice) { mCameraDevice.lockCurrentFrame(); } EmulatedCameraDevice::FrameLock::~FrameLock() { mCameraDevice.unlockCurrentFrame(); } status_t EmulatedCameraDevice::setAutoFocus() { mTriggerAutoFocus = true; return NO_ERROR; } status_t EmulatedCameraDevice::cancelAutoFocus() { mTriggerAutoFocus = false; return NO_ERROR; } bool EmulatedCameraDevice::requestRestart(int width, int height, uint32_t pixelFormat, bool takingPicture, bool oneBurst) { if (mCameraThread.get() == nullptr) { ALOGE("%s: No thread alive to perform the restart, is preview on?", __FUNCTION__); return false; } mCameraThread->requestRestart(width, height, pixelFormat, takingPicture, oneBurst); return true; } /**************************************************************************** * Emulated camera device private API ***************************************************************************/ status_t EmulatedCameraDevice::commonStartDevice(int width, int height, uint32_t pix_fmt) { /* Validate pixel format, and calculate framebuffer size at the same time. */ switch (pix_fmt) { case V4L2_PIX_FMT_YVU420: case V4L2_PIX_FMT_YUV420: // For these pixel formats the strides have to be aligned to 16 byte // boundaries as per the format specification // https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12 mYStride = align(width, 16); mUVStride = align(mYStride / 2, 16); // The second term should use half the height but since there are // two planes the multiplication with two cancels that out mFrameBufferSize = mYStride * height + mUVStride * height; break; case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV12: mYStride = width; // Because of interleaving the UV stride is the same as the Y stride // since it covers two pixels, one U and one V. mUVStride = mYStride; // Since the U/V stride covers both U and V we don't multiply by two mFrameBufferSize = mYStride * height + mUVStride * (height / 2); break; default: ALOGE("%s: Unknown pixel format %.4s", __FUNCTION__, reinterpret_cast(&pix_fmt)); return EINVAL; } /* Cache framebuffer info. */ mFrameWidth = width; mFrameHeight = height; mPixelFormat = pix_fmt; mTotalPixels = width * height; /* Allocate framebuffer. */ mFrameBuffers[0].resize(mFrameBufferSize); mFrameBuffers[1].resize(mFrameBufferSize); ALOGV("%s: Allocated %zu bytes for %d pixels in %.4s[%dx%d] frame", __FUNCTION__, mFrameBufferSize, mTotalPixels, reinterpret_cast(&mPixelFormat), mFrameWidth, mFrameHeight); return NO_ERROR; } void EmulatedCameraDevice::commonStopDevice() { mFrameWidth = mFrameHeight = mTotalPixels = 0; mPixelFormat = 0; mFrameBuffers[0].clear(); mFrameBuffers[1].clear(); // No need to keep all that memory allocated if the camera isn't running mFrameBuffers[0].shrink_to_fit(); mFrameBuffers[1].shrink_to_fit(); } /**************************************************************************** * Worker thread management. ***************************************************************************/ status_t EmulatedCameraDevice::startWorkerThread(bool one_burst) { ALOGV("%s", __FUNCTION__); if (!isInitialized()) { ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__); return EINVAL; } mCameraThread = new CameraThread(this, staticProduceFrame, this); if (mCameraThread == NULL) { ALOGE("%s: Unable to instantiate CameraThread object", __FUNCTION__); return ENOMEM; } status_t res = mCameraThread->startThread(one_burst); if (res != NO_ERROR) { ALOGE("%s: Unable to start CameraThread: %s", __FUNCTION__, strerror(res)); return res; } return res; } status_t EmulatedCameraDevice::stopWorkerThread() { ALOGV("%s", __FUNCTION__); if (!isInitialized()) { ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__); return EINVAL; } status_t res = mCameraThread->stopThread(); if (res != NO_ERROR) { ALOGE("%s: Unable to stop CameraThread", __FUNCTION__); return res; } res = mCameraThread->joinThread(); if (res != NO_ERROR) { ALOGE("%s: Unable to join CameraThread", __FUNCTION__); return res; } // Destroy the thread as well mCameraThread.clear(); return res; } EmulatedCameraDevice::CameraThread::CameraThread(EmulatedCameraDevice* dev, ProduceFrameFunc producer, void* producerOpaque) : WorkerThread("Camera_CameraThread", dev, dev->mCameraHAL), mCurFrameTimestamp(0), mProducerFunc(producer), mProducerOpaque(producerOpaque), mRestartWidth(0), mRestartHeight(0), mRestartPixelFormat(0), mRestartOneBurst(false), mRestartTakingPicture(false), mRestartRequested(false) { } const void* EmulatedCameraDevice::CameraThread::getPrimaryBuffer() const { if (mFrameProducer.get()) { return mFrameProducer->getPrimaryBuffer(); } return nullptr; } int64_t EmulatedCameraDevice::CameraThread::getPrimaryTimestamp() const { if (mFrameProducer.get()) { return mFrameProducer->getPrimaryTimestamp(); } return 0L; } void EmulatedCameraDevice::CameraThread::lockPrimaryBuffer() { mFrameProducer->lockPrimaryBuffer(); } void EmulatedCameraDevice::CameraThread::unlockPrimaryBuffer() { mFrameProducer->unlockPrimaryBuffer(); } bool EmulatedCameraDevice::CameraThread::waitForFrameOrTimeout(nsecs_t timeout) { // Keep waiting until the frame producer indicates that a frame is available // This does introduce some unnecessary latency to the first frame delivery // but avoids a lot of thread synchronization. do { // We don't have any specific fd we want to select so we pass in -1 // timeout is in nanoseconds but Select expects microseconds Mutex::Autolock lock(mRunningMutex); mRunningCondition.waitRelative(mRunningMutex, timeout); if (!mRunning) { ALOGV("%s: CameraThread has been terminated.", __FUNCTION__); return false; } // Set a short timeout in case there is no frame available and we are // going to loop. This way we ensure a sleep but keep a decent latency timeout = milliseconds(5); } while (!mFrameProducer->hasFrame()); return true; } bool EmulatedCameraDevice::CameraThread::inWorkerThread() { /* Wait till FPS timeout expires, or thread exit message is received. */ nsecs_t wakeAt = mCurFrameTimestamp + 1000000000.0 / mCameraDevice->mFramesPerSecond; nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); nsecs_t timeout = std::max(0, wakeAt - now); if (!waitForFrameOrTimeout(timeout)) { return false; } /* Check if a restart and potentially apply the requested changes */ if (!checkRestartRequest()) { return false; } /* Check if an auto-focus event needs to be triggered */ mCameraDevice->checkAutoFocusTrigger(); mCurFrameTimestamp = systemTime(SYSTEM_TIME_MONOTONIC); mCameraHAL->onNextFrameAvailable(mCurFrameTimestamp, mCameraDevice); return true; } status_t EmulatedCameraDevice::CameraThread::onThreadStart() { void* primaryBuffer = mCameraDevice->getPrimaryBuffer(); void* secondaryBuffer = mCameraDevice->getSecondaryBuffer(); mFrameProducer = new FrameProducer(mCameraDevice, mProducerFunc, mProducerOpaque, primaryBuffer, secondaryBuffer); if (mFrameProducer.get() == nullptr) { ALOGE("%s: Could not instantiate FrameProducer object", __FUNCTION__); return ENOMEM; } return mFrameProducer->startThread(mOneBurst); } void EmulatedCameraDevice::CameraThread::onThreadExit() { if (mFrameProducer.get()) { if (mFrameProducer->stopThread() == NO_ERROR) { mFrameProducer->joinThread(); mFrameProducer.clear(); } } } EmulatedCameraDevice::CameraThread::FrameProducer::FrameProducer( EmulatedCameraDevice* dev, ProduceFrameFunc producer, void* opaque, void* primaryBuffer, void* secondaryBuffer) : WorkerThread("Camera_FrameProducer", dev, dev->mCameraHAL), mProducer(producer), mOpaque(opaque), mPrimaryBuffer(primaryBuffer), mSecondaryBuffer(secondaryBuffer), mPrimaryTimestamp(0L), mSecondaryTimestamp(0L), mLastFrame(0), mHasFrame(false) { } const void* EmulatedCameraDevice::CameraThread::FrameProducer::getPrimaryBuffer() const { return mPrimaryBuffer; } int64_t EmulatedCameraDevice::CameraThread::FrameProducer::getPrimaryTimestamp() const { return mPrimaryTimestamp; } void EmulatedCameraDevice::CameraThread::FrameProducer::lockPrimaryBuffer() { mBufferMutex.lock(); } void EmulatedCameraDevice::CameraThread::FrameProducer::unlockPrimaryBuffer() { mBufferMutex.unlock(); } void EmulatedCameraDevice::CameraThread::requestRestart(int width, int height, uint32_t pixelFormat, bool takingPicture, bool oneBurst) { Mutex::Autolock lock(mRequestMutex); mRestartWidth = width; mRestartHeight = height; mRestartPixelFormat = pixelFormat; mRestartTakingPicture = takingPicture; mRestartOneBurst = oneBurst; mRestartRequested = true; } bool EmulatedCameraDevice::CameraThread::FrameProducer::hasFrame() const { return mHasFrame; } bool EmulatedCameraDevice::CameraThread::checkRestartRequest() { Mutex::Autolock lock(mRequestMutex); if (mRestartRequested) { mRestartRequested = false; status_t res = mFrameProducer->stopThread(); if (res != NO_ERROR) { ALOGE("%s: Could not stop frame producer thread", __FUNCTION__); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } res = mFrameProducer->joinThread(); if (res != NO_ERROR) { ALOGE("%s: Could not join frame producer thread", __FUNCTION__); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } mFrameProducer.clear(); res = mCameraDevice->stopDevice(); if (res != NO_ERROR) { ALOGE("%s: Could not stop device", __FUNCTION__); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } res = mCameraDevice->startDevice(mRestartWidth, mRestartHeight, mRestartPixelFormat); if (res != NO_ERROR) { ALOGE("%s: Could not start device", __FUNCTION__); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } if (mRestartTakingPicture) { mCameraHAL->setTakingPicture(true); } mOneBurst = mRestartOneBurst; // Pretend like this a thread start, performs the remaining setup if (onThreadStart() != NO_ERROR) { mCameraDevice->stopDevice(); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } // Now wait for the frame producer to start producing before we proceed return waitForFrameOrTimeout(0); } return true; } bool EmulatedCameraDevice::CameraThread::FrameProducer::inWorkerThread() { nsecs_t nextFrame = mLastFrame + 1000000000 / mCameraDevice->mFramesPerSecond; nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); nsecs_t timeout = std::max(0, nextFrame - now); { Mutex::Autolock lock(mRunningMutex); mRunningCondition.waitRelative(mRunningMutex, timeout); if (!mRunning) { ALOGV("%s: FrameProducer has been terminated.", __FUNCTION__); return false; } } // Produce one frame and place it in the secondary buffer mLastFrame = systemTime(SYSTEM_TIME_MONOTONIC); if (!mProducer(mOpaque, mSecondaryBuffer, &mSecondaryTimestamp)) { ALOGE("FrameProducer could not produce frame, exiting thread"); mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED); return false; } { // Switch buffers now that the secondary buffer is ready Mutex::Autolock lock(mBufferMutex); std::swap(mPrimaryBuffer, mSecondaryBuffer); std::swap(mPrimaryTimestamp, mSecondaryTimestamp); } mHasFrame = true; return true; } void EmulatedCameraDevice::lockCurrentFrame() { mCameraThread->lockPrimaryBuffer(); } void EmulatedCameraDevice::unlockCurrentFrame() { mCameraThread->unlockPrimaryBuffer(); } }; /* namespace android */