/* * 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 a class EmulatedFakeCameraDevice that encapsulates * fake camera device. */ #define LOG_NDEBUG 0 #define LOG_TAG "EmulatedCamera_FakeDevice" #include #include "EmulatedFakeCamera.h" #include "EmulatedFakeCameraDevice.h" #undef min #undef max #include namespace android { static const double kCheckXSpeed = 0.00000000096; static const double kCheckYSpeed = 0.00000000032; static const double kSquareXSpeed = 0.000000000096; static const double kSquareYSpeed = 0.000000000160; static const nsecs_t kSquareColorChangeIntervalNs = seconds(5); EmulatedFakeCameraDevice::EmulatedFakeCameraDevice(EmulatedFakeCamera* camera_hal) : EmulatedCameraDevice(camera_hal), mBlackYUV(kBlack32), mWhiteYUV(kWhite32), mRedYUV(kRed8), mGreenYUV(kGreen8), mBlueYUV(kBlue8), mSquareColor(&mRedYUV), mLastRedrawn(0), mLastColorChange(0), mCheckX(0), mCheckY(0), mSquareX(0), mSquareY(0), mSquareXSpeed(kSquareXSpeed), mSquareYSpeed(kSquareYSpeed) #if EFCD_ROTATE_FRAME , mLastRotatedAt(0), mCurrentFrameType(0), mCurrentColor(&mWhiteYUV) #endif // EFCD_ROTATE_FRAME { // Makes the image with the original exposure compensation darker. // So the effects of changing the exposure compensation can be seen. mBlackYUV.Y = mBlackYUV.Y / 2; mWhiteYUV.Y = mWhiteYUV.Y / 2; mRedYUV.Y = mRedYUV.Y / 2; mGreenYUV.Y = mGreenYUV.Y / 2; mBlueYUV.Y = mBlueYUV.Y / 2; } EmulatedFakeCameraDevice::~EmulatedFakeCameraDevice() { } /**************************************************************************** * Emulated camera device abstract interface implementation. ***************************************************************************/ status_t EmulatedFakeCameraDevice::connectDevice() { ALOGV("%s", __FUNCTION__); Mutex::Autolock locker(&mObjectLock); if (!isInitialized()) { ALOGE("%s: Fake camera device is not initialized.", __FUNCTION__); return EINVAL; } if (isConnected()) { ALOGW("%s: Fake camera device is already connected.", __FUNCTION__); return NO_ERROR; } /* There is no device to connect to. */ mState = ECDS_CONNECTED; return NO_ERROR; } status_t EmulatedFakeCameraDevice::disconnectDevice() { ALOGV("%s", __FUNCTION__); Mutex::Autolock locker(&mObjectLock); if (!isConnected()) { ALOGW("%s: Fake camera device is already disconnected.", __FUNCTION__); return NO_ERROR; } if (isStarted()) { ALOGE("%s: Cannot disconnect from the started device.", __FUNCTION__); return EINVAL; } /* There is no device to disconnect from. */ mState = ECDS_INITIALIZED; return NO_ERROR; } status_t EmulatedFakeCameraDevice::startDevice(int width, int height, uint32_t pix_fmt) { ALOGV("%s", __FUNCTION__); Mutex::Autolock locker(&mObjectLock); if (!isConnected()) { ALOGE("%s: Fake camera device is not connected.", __FUNCTION__); return EINVAL; } if (isStarted()) { ALOGE("%s: Fake camera device is already started.", __FUNCTION__); return EINVAL; } /* Initialize the base class. */ const status_t res = EmulatedCameraDevice::commonStartDevice(width, height, pix_fmt); if (res == NO_ERROR) { /* Calculate U/V panes inside the framebuffer. */ switch (mPixelFormat) { case V4L2_PIX_FMT_YVU420: mFrameVOffset = mYStride * mFrameHeight; mFrameUOffset = mFrameVOffset + mUVStride * (mFrameHeight / 2); mUVStep = 1; break; case V4L2_PIX_FMT_YUV420: mFrameUOffset = mYStride * mFrameHeight; mFrameVOffset = mFrameUOffset + mUVStride * (mFrameHeight / 2); mUVStep = 1; break; case V4L2_PIX_FMT_NV21: /* Interleaved UV pane, V first. */ mFrameVOffset = mYStride * mFrameHeight; mFrameUOffset = mFrameVOffset + 1; mUVStep = 2; break; case V4L2_PIX_FMT_NV12: /* Interleaved UV pane, U first. */ mFrameUOffset = mYStride * mFrameHeight; mFrameVOffset = mFrameUOffset + 1; mUVStep = 2; break; default: ALOGE("%s: Unknown pixel format %.4s", __FUNCTION__, reinterpret_cast(&mPixelFormat)); return EINVAL; } mLastRedrawn = systemTime(SYSTEM_TIME_MONOTONIC); mLastColorChange = mLastRedrawn; /* Number of items in a single row inside U/V panes. */ mUVInRow = (width / 2) * mUVStep; mState = ECDS_STARTED; } else { ALOGE("%s: commonStartDevice failed", __FUNCTION__); } return res; } status_t EmulatedFakeCameraDevice::stopDevice() { ALOGV("%s", __FUNCTION__); Mutex::Autolock locker(&mObjectLock); if (!isStarted()) { ALOGW("%s: Fake camera device is not started.", __FUNCTION__); return NO_ERROR; } EmulatedCameraDevice::commonStopDevice(); mState = ECDS_CONNECTED; return NO_ERROR; } /**************************************************************************** * Worker thread management overrides. ***************************************************************************/ bool EmulatedFakeCameraDevice::produceFrame(void* buffer, int64_t* timestamp) { #if EFCD_ROTATE_FRAME const int frame_type = rotateFrame(); switch (frame_type) { case 0: drawCheckerboard(buffer); break; case 1: drawStripes(buffer); break; case 2: drawSolid(buffer, mCurrentColor); break; } #else drawCheckerboard(buffer); #endif // EFCD_ROTATE_FRAME if (timestamp != nullptr) { *timestamp = 0L; } return true; } /**************************************************************************** * Fake camera device private API ***************************************************************************/ void EmulatedFakeCameraDevice::drawCheckerboard(void* buffer) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); nsecs_t elapsed = now - mLastRedrawn; uint8_t* currentFrame = reinterpret_cast(buffer); uint8_t* frameU = currentFrame + mFrameUOffset; uint8_t* frameV = currentFrame + mFrameVOffset; const int size = std::min(mFrameWidth, mFrameHeight) / 10; bool black = true; if (size == 0) { // When this happens, it happens at a very high rate, // so don't log any messages and just return. return; } mCheckX += kCheckXSpeed * elapsed; mCheckY += kCheckYSpeed * elapsed; // Allow the X and Y values to transition across two checkerboard boxes // before resetting it back. This allows for the gray to black transition. // Note that this is in screen size independent coordinates so that frames // will look similar regardless of resolution if (mCheckX > 2.0) { mCheckX -= 2.0; } if (mCheckY > 2.0) { mCheckY -= 2.0; } // Are we in the gray or black zone? if (mCheckX >= 1.0) black = false; if (mCheckY >= 1.0) black = !black; int county = static_cast(mCheckY * size) % size; int checkxremainder = static_cast(mCheckX * size) % size; YUVPixel adjustedWhite = YUVPixel(mWhiteYUV); changeWhiteBalance(adjustedWhite.Y, adjustedWhite.U, adjustedWhite.V); adjustedWhite.Y = changeExposure(adjustedWhite.Y); YUVPixel adjustedBlack = YUVPixel(mBlackYUV); adjustedBlack.Y = changeExposure(adjustedBlack.Y); for(int y = 0; y < mFrameHeight; y++) { int countx = checkxremainder; bool current = black; uint8_t* Y = currentFrame + mYStride * y; uint8_t* U = frameU + mUVStride * (y / 2); uint8_t* V = frameV + mUVStride * (y / 2); for(int x = 0; x < mFrameWidth; x += 2) { if (current) { adjustedBlack.get(Y, U, V); } else { adjustedWhite.get(Y, U, V); } Y[1] = *Y; Y += 2; U += mUVStep; V += mUVStep; countx += 2; if(countx >= size) { countx = 0; current = !current; } } if(county++ >= size) { county = 0; black = !black; } } /* Run the square. */ const int squareSize = std::min(mFrameWidth, mFrameHeight) / 4; mSquareX += mSquareXSpeed * elapsed; mSquareY += mSquareYSpeed * elapsed; int squareX = mSquareX * mFrameWidth; int squareY = mSquareY * mFrameHeight; if (squareX + squareSize > mFrameWidth) { mSquareXSpeed = -mSquareXSpeed; double relativeWidth = static_cast(squareSize) / mFrameWidth; mSquareX -= 2.0 * (mSquareX + relativeWidth - 1.0); squareX = mSquareX * mFrameWidth; } else if (squareX < 0) { mSquareXSpeed = -mSquareXSpeed; mSquareX = -mSquareX; squareX = mSquareX * mFrameWidth; } if (squareY + squareSize > mFrameHeight) { mSquareYSpeed = -mSquareYSpeed; double relativeHeight = static_cast(squareSize) / mFrameHeight; mSquareY -= 2.0 * (mSquareY + relativeHeight - 1.0); squareY = mSquareY * mFrameHeight; } else if (squareY < 0) { mSquareYSpeed = -mSquareYSpeed; mSquareY = -mSquareY; squareY = mSquareY * mFrameHeight; } if (now - mLastColorChange > kSquareColorChangeIntervalNs) { mLastColorChange = now; mSquareColor = mSquareColor == &mRedYUV ? &mGreenYUV : &mRedYUV; } drawSquare(buffer, squareX, squareY, squareSize, mSquareColor); mLastRedrawn = now; } void EmulatedFakeCameraDevice::drawSquare(void* buffer, int x, int y, int size, const YUVPixel* color) { uint8_t* currentFrame = reinterpret_cast(buffer); uint8_t* frameU = currentFrame + mFrameUOffset; uint8_t* frameV = currentFrame + mFrameVOffset; const int square_xstop = std::min(mFrameWidth, x + size); const int square_ystop = std::min(mFrameHeight, y + size); uint8_t* Y_pos = currentFrame + y * mYStride + x; YUVPixel adjustedColor = *color; changeWhiteBalance(adjustedColor.Y, adjustedColor.U, adjustedColor.V); // Draw the square. for (; y < square_ystop; y++) { const int iUV = (y / 2) * mUVStride + (x / 2) * mUVStep; uint8_t* sqU = frameU + iUV; uint8_t* sqV = frameV + iUV; uint8_t* sqY = Y_pos; for (int i = x; i < square_xstop; i += 2) { adjustedColor.get(sqY, sqU, sqV); *sqY = changeExposure(*sqY); sqY[1] = *sqY; sqY += 2; sqU += mUVStep; sqV += mUVStep; } Y_pos += mYStride; } } #if EFCD_ROTATE_FRAME void EmulatedFakeCameraDevice::drawSolid(void* buffer, YUVPixel* color) { YUVPixel adjustedColor = *color; changeWhiteBalance(adjustedColor.Y, adjustedColor.U, adjustedColor.V); /* All Ys are the same, will fill any alignment padding but that's OK */ memset(mCurrentFrame, changeExposure(adjustedColor.Y), mFrameHeight * mYStride); /* Fill U, and V panes. */ for (int y = 0; y < mFrameHeight / 2; ++y) { uint8_t* U = mFrameU + y * mUVStride; uint8_t* V = mFrameV + y * mUVStride; for (int x = 0; x < mFrameWidth / 2; ++x, U += mUVStep, V += mUVStep) { *U = color->U; *V = color->V; } } } void EmulatedFakeCameraDevice::drawStripes(void* buffer) { /* Divide frame into 4 stripes. */ const int change_color_at = mFrameHeight / 4; const int each_in_row = mUVInRow / mUVStep; uint8_t* pY = mCurrentFrame; for (int y = 0; y < mFrameHeight; y++, pY += mYStride) { /* Select the color. */ YUVPixel* color; const int color_index = y / change_color_at; if (color_index == 0) { /* White stripe on top. */ color = &mWhiteYUV; } else if (color_index == 1) { /* Then the red stripe. */ color = &mRedYUV; } else if (color_index == 2) { /* Then the green stripe. */ color = &mGreenYUV; } else { /* And the blue stripe at the bottom. */ color = &mBlueYUV; } changeWhiteBalance(color->Y, color->U, color->V); /* All Ys at the row are the same. */ memset(pY, changeExposure(color->Y), mFrameWidth); /* Offset of the current row inside U/V panes. */ const int uv_off = (y / 2) * mUVStride; /* Fill U, and V panes. */ uint8_t* U = mFrameU + uv_off; uint8_t* V = mFrameV + uv_off; for (int k = 0; k < each_in_row; k++, U += mUVStep, V += mUVStep) { *U = color->U; *V = color->V; } } } int EmulatedFakeCameraDevice::rotateFrame() { if ((systemTime(SYSTEM_TIME_MONOTONIC) - mLastRotatedAt) >= mRotateFreq) { mLastRotatedAt = systemTime(SYSTEM_TIME_MONOTONIC); mCurrentFrameType++; if (mCurrentFrameType > 2) { mCurrentFrameType = 0; } if (mCurrentFrameType == 2) { ALOGD("********** Rotated to the SOLID COLOR frame **********"); /* Solid color: lets rotate color too. */ if (mCurrentColor == &mWhiteYUV) { ALOGD("----- Painting a solid RED frame -----"); mCurrentColor = &mRedYUV; } else if (mCurrentColor == &mRedYUV) { ALOGD("----- Painting a solid GREEN frame -----"); mCurrentColor = &mGreenYUV; } else if (mCurrentColor == &mGreenYUV) { ALOGD("----- Painting a solid BLUE frame -----"); mCurrentColor = &mBlueYUV; } else { /* Back to white. */ ALOGD("----- Painting a solid WHITE frame -----"); mCurrentColor = &mWhiteYUV; } } else if (mCurrentFrameType == 0) { ALOGD("********** Rotated to the CHECKERBOARD frame **********"); } else if (mCurrentFrameType == 1) { ALOGD("********** Rotated to the STRIPED frame **********"); } } return mCurrentFrameType; } #endif // EFCD_ROTATE_FRAME }; /* namespace android */