| /* |
| * 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 <log/log.h> |
| #include "EmulatedFakeCamera.h" |
| #include "EmulatedFakeCameraDevice.h" |
| |
| #undef min |
| #undef max |
| #include <algorithm> |
| |
| 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<const char*>(&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<uint8_t*>(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<int>(mCheckY * size) % size; |
| int checkxremainder = static_cast<int>(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<double>(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<double>(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<uint8_t*>(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 */ |