/* * Copyright (C) 2010 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "CameraSourceTimeLapse" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "OMX_Video.h" #include namespace android { // static CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera( const sp &camera, int32_t cameraId, Size videoSize, int32_t videoFrameRate, const sp& surface, int64_t timeBetweenTimeLapseFrameCaptureUs) { CameraSourceTimeLapse *source = new CameraSourceTimeLapse(camera, cameraId, videoSize, videoFrameRate, surface, timeBetweenTimeLapseFrameCaptureUs); if (source != NULL) { if (source->initCheck() != OK) { delete source; return NULL; } } return source; } CameraSourceTimeLapse::CameraSourceTimeLapse( const sp& camera, int32_t cameraId, Size videoSize, int32_t videoFrameRate, const sp& surface, int64_t timeBetweenTimeLapseFrameCaptureUs) : CameraSource(camera, cameraId, videoSize, videoFrameRate, surface, false), mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs), mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate), mLastTimeLapseFrameRealTimestampUs(0), mSkipCurrentFrame(false) { LOGD("starting time lapse mode: %lld us", mTimeBetweenTimeLapseFrameCaptureUs); mVideoWidth = videoSize.width; mVideoHeight = videoSize.height; if (trySettingPreviewSize(videoSize.width, videoSize.height)) { mUseStillCameraForTimeLapse = false; } else { // TODO: Add a check to see that mTimeBetweenTimeLapseFrameCaptureUs is greater // than the fastest rate at which the still camera can take pictures. mUseStillCameraForTimeLapse = true; CHECK(setPictureSizeToClosestSupported(videoSize.width, videoSize.height)); mNeedCropping = computeCropRectangleOffset(); mMeta->setInt32(kKeyWidth, videoSize.width); mMeta->setInt32(kKeyHeight, videoSize.height); } // Initialize quick stop variables. mQuickStop = false; mForceRead = false; mLastReadBufferCopy = NULL; mStopWaitingForIdleCamera = false; } CameraSourceTimeLapse::~CameraSourceTimeLapse() { } void CameraSourceTimeLapse::startQuickReadReturns() { Mutex::Autolock autoLock(mQuickStopLock); LOGV("Enabling quick read returns"); // Enable quick stop mode. mQuickStop = true; if (mUseStillCameraForTimeLapse) { // wake up the thread right away. mTakePictureCondition.signal(); } else { // Force dataCallbackTimestamp() coming from the video camera to not skip the // next frame as we want read() to get a get a frame right away. mForceRead = true; } } bool CameraSourceTimeLapse::trySettingPreviewSize(int32_t width, int32_t height) { LOGV("trySettingPreviewSize: %dx%d", width, height); int64_t token = IPCThreadState::self()->clearCallingIdentity(); String8 s = mCamera->getParameters(); CameraParameters params(s); Vector supportedSizes; params.getSupportedPreviewSizes(supportedSizes); bool previewSizeSupported = false; for (uint32_t i = 0; i < supportedSizes.size(); ++i) { int32_t pictureWidth = supportedSizes[i].width; int32_t pictureHeight = supportedSizes[i].height; if ((pictureWidth == width) && (pictureHeight == height)) { previewSizeSupported = true; } } bool isSuccessful = false; if (previewSizeSupported) { LOGV("Video size (%d, %d) is a supported preview size", width, height); params.setPreviewSize(width, height); if (mCamera->setParameters(params.flatten()) == OK) { isSuccessful = true; } else { LOGE("Failed to set preview size to %dx%d", width, height); isSuccessful = false; } } IPCThreadState::self()->restoreCallingIdentity(token); return isSuccessful; } bool CameraSourceTimeLapse::setPictureSizeToClosestSupported(int32_t width, int32_t height) { LOGV("setPictureSizeToClosestSupported: %dx%d", width, height); int64_t token = IPCThreadState::self()->clearCallingIdentity(); String8 s = mCamera->getParameters(); IPCThreadState::self()->restoreCallingIdentity(token); CameraParameters params(s); Vector supportedSizes; params.getSupportedPictureSizes(supportedSizes); int32_t minPictureSize = INT_MAX; for (uint32_t i = 0; i < supportedSizes.size(); ++i) { int32_t pictureWidth = supportedSizes[i].width; int32_t pictureHeight = supportedSizes[i].height; if ((pictureWidth >= width) && (pictureHeight >= height)) { int32_t pictureSize = pictureWidth*pictureHeight; if (pictureSize < minPictureSize) { minPictureSize = pictureSize; mPictureWidth = pictureWidth; mPictureHeight = pictureHeight; } } } LOGV("Picture size = (%d, %d)", mPictureWidth, mPictureHeight); return (minPictureSize != INT_MAX); } bool CameraSourceTimeLapse::computeCropRectangleOffset() { if ((mPictureWidth == mVideoWidth) && (mPictureHeight == mVideoHeight)) { return false; } CHECK((mPictureWidth > mVideoWidth) && (mPictureHeight > mVideoHeight)); int32_t widthDifference = mPictureWidth - mVideoWidth; int32_t heightDifference = mPictureHeight - mVideoHeight; mCropRectStartX = widthDifference/2; mCropRectStartY = heightDifference/2; LOGV("setting crop rectangle offset to (%d, %d)", mCropRectStartX, mCropRectStartY); return true; } void CameraSourceTimeLapse::signalBufferReturned(MediaBuffer* buffer) { Mutex::Autolock autoLock(mQuickStopLock); if (mQuickStop && (buffer == mLastReadBufferCopy)) { buffer->setObserver(NULL); buffer->release(); } else { return CameraSource::signalBufferReturned(buffer); } } void createMediaBufferCopy(const MediaBuffer& sourceBuffer, int64_t frameTime, MediaBuffer **newBuffer) { size_t sourceSize = sourceBuffer.size(); void* sourcePointer = sourceBuffer.data(); (*newBuffer) = new MediaBuffer(sourceSize); memcpy((*newBuffer)->data(), sourcePointer, sourceSize); (*newBuffer)->meta_data()->setInt64(kKeyTime, frameTime); } void CameraSourceTimeLapse::fillLastReadBufferCopy(MediaBuffer& sourceBuffer) { int64_t frameTime; CHECK(sourceBuffer.meta_data()->findInt64(kKeyTime, &frameTime)); createMediaBufferCopy(sourceBuffer, frameTime, &mLastReadBufferCopy); mLastReadBufferCopy->add_ref(); mLastReadBufferCopy->setObserver(this); } status_t CameraSourceTimeLapse::read( MediaBuffer **buffer, const ReadOptions *options) { if (mLastReadBufferCopy == NULL) { mLastReadStatus = CameraSource::read(buffer, options); // mQuickStop may have turned to true while read was blocked. Make a copy of // the buffer in that case. Mutex::Autolock autoLock(mQuickStopLock); if (mQuickStop && *buffer) { fillLastReadBufferCopy(**buffer); } return mLastReadStatus; } else { (*buffer) = mLastReadBufferCopy; (*buffer)->add_ref(); return mLastReadStatus; } } // static void *CameraSourceTimeLapse::ThreadTimeLapseWrapper(void *me) { CameraSourceTimeLapse *source = static_cast(me); source->threadTimeLapseEntry(); return NULL; } void CameraSourceTimeLapse::threadTimeLapseEntry() { while (mStarted) { { Mutex::Autolock autoLock(mCameraIdleLock); if (!mCameraIdle) { mCameraIdleCondition.wait(mCameraIdleLock); } CHECK(mCameraIdle); mCameraIdle = false; } // Even if mQuickStop == true we need to take one more picture // as a read() may be blocked, waiting for a frame to get available. // After this takePicture, if mQuickStop == true, we can safely exit // this thread as read() will make a copy of this last frame and keep // returning it in the quick stop mode. Mutex::Autolock autoLock(mQuickStopLock); CHECK_EQ(OK, mCamera->takePicture()); if (mQuickStop) { LOGV("threadTimeLapseEntry: Exiting due to mQuickStop = true"); return; } mTakePictureCondition.waitRelative(mQuickStopLock, mTimeBetweenTimeLapseFrameCaptureUs * 1000); } LOGV("threadTimeLapseEntry: Exiting due to mStarted = false"); } void CameraSourceTimeLapse::startCameraRecording() { if (mUseStillCameraForTimeLapse) { LOGV("start time lapse recording using still camera"); int64_t token = IPCThreadState::self()->clearCallingIdentity(); String8 s = mCamera->getParameters(); CameraParameters params(s); params.setPictureSize(mPictureWidth, mPictureHeight); mCamera->setParameters(params.flatten()); mCameraIdle = true; mStopWaitingForIdleCamera = false; // disable shutter sound and play the recording sound. mCamera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, 0, 0); mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0); IPCThreadState::self()->restoreCallingIdentity(token); // create a thread which takes pictures in a loop pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&mThreadTimeLapse, &attr, ThreadTimeLapseWrapper, this); pthread_attr_destroy(&attr); } else { LOGV("start time lapse recording using video camera"); CHECK_EQ(OK, mCamera->startRecording()); } } void CameraSourceTimeLapse::stopCameraRecording() { if (mUseStillCameraForTimeLapse) { void *dummy; pthread_join(mThreadTimeLapse, &dummy); // Last takePicture may still be underway. Wait for the camera to get // idle. Mutex::Autolock autoLock(mCameraIdleLock); mStopWaitingForIdleCamera = true; if (!mCameraIdle) { mCameraIdleCondition.wait(mCameraIdleLock); } CHECK(mCameraIdle); mCamera->setListener(NULL); // play the recording sound. mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0); } else { mCamera->setListener(NULL); mCamera->stopRecording(); } if (mLastReadBufferCopy) { mLastReadBufferCopy->release(); mLastReadBufferCopy = NULL; } } void CameraSourceTimeLapse::releaseRecordingFrame(const sp& frame) { if (!mUseStillCameraForTimeLapse) { mCamera->releaseRecordingFrame(frame); } } sp CameraSourceTimeLapse::createIMemoryCopy(const sp &source_data) { size_t source_size = source_data->size(); void* source_pointer = source_data->pointer(); sp newMemoryHeap = new MemoryHeapBase(source_size); sp newMemory = new MemoryBase(newMemoryHeap, 0, source_size); memcpy(newMemory->pointer(), source_pointer, source_size); return newMemory; } // Allocates IMemory of final type MemoryBase with the given size. sp allocateIMemory(size_t size) { sp newMemoryHeap = new MemoryHeapBase(size); sp newMemory = new MemoryBase(newMemoryHeap, 0, size); return newMemory; } // static void *CameraSourceTimeLapse::ThreadStartPreviewWrapper(void *me) { CameraSourceTimeLapse *source = static_cast(me); source->threadStartPreview(); return NULL; } void CameraSourceTimeLapse::threadStartPreview() { CHECK_EQ(OK, mCamera->startPreview()); Mutex::Autolock autoLock(mCameraIdleLock); mCameraIdle = true; mCameraIdleCondition.signal(); } void CameraSourceTimeLapse::restartPreview() { // Start this in a different thread, so that the dataCallback can return LOGV("restartPreview"); pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_t threadPreview; pthread_create(&threadPreview, &attr, ThreadStartPreviewWrapper, this); pthread_attr_destroy(&attr); } sp CameraSourceTimeLapse::cropYUVImage(const sp &source_data) { // find the YUV format int32_t srcFormat; CHECK(mMeta->findInt32(kKeyColorFormat, &srcFormat)); YUVImage::YUVFormat yuvFormat; if (srcFormat == OMX_COLOR_FormatYUV420SemiPlanar) { yuvFormat = YUVImage::YUV420SemiPlanar; } else { CHECK_EQ(srcFormat, OMX_COLOR_FormatYUV420Planar); yuvFormat = YUVImage::YUV420Planar; } // allocate memory for cropped image and setup a canvas using it. sp croppedImageMemory = allocateIMemory( YUVImage::bufferSize(yuvFormat, mVideoWidth, mVideoHeight)); YUVImage yuvImageCropped(yuvFormat, mVideoWidth, mVideoHeight, (uint8_t *)croppedImageMemory->pointer()); YUVCanvas yuvCanvasCrop(yuvImageCropped); YUVImage yuvImageSource(yuvFormat, mPictureWidth, mPictureHeight, (uint8_t *)source_data->pointer()); yuvCanvasCrop.CopyImageRect( Rect(mCropRectStartX, mCropRectStartY, mCropRectStartX + mVideoWidth, mCropRectStartY + mVideoHeight), 0, 0, yuvImageSource); return croppedImageMemory; } void CameraSourceTimeLapse::dataCallback(int32_t msgType, const sp &data) { if (msgType == CAMERA_MSG_COMPRESSED_IMAGE) { // takePicture will complete after this callback, so restart preview. restartPreview(); return; } if (msgType != CAMERA_MSG_RAW_IMAGE) { return; } LOGV("dataCallback for timelapse still frame"); CHECK_EQ(true, mUseStillCameraForTimeLapse); int64_t timestampUs; if (mNumFramesReceived == 0) { timestampUs = mStartTimeUs; } else { timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; } if (mNeedCropping) { sp croppedImageData = cropYUVImage(data); dataCallbackTimestamp(timestampUs, msgType, croppedImageData); } else { sp dataCopy = createIMemoryCopy(data); dataCallbackTimestamp(timestampUs, msgType, dataCopy); } } bool CameraSourceTimeLapse::skipCurrentFrame(int64_t timestampUs) { if (mSkipCurrentFrame) { mSkipCurrentFrame = false; return true; } else { return false; } } bool CameraSourceTimeLapse::skipFrameAndModifyTimeStamp(int64_t *timestampUs) { if (!mUseStillCameraForTimeLapse) { if (mLastTimeLapseFrameRealTimestampUs == 0) { // First time lapse frame. Initialize mLastTimeLapseFrameRealTimestampUs // to current time (timestampUs) and save frame data. LOGV("dataCallbackTimestamp timelapse: initial frame"); mLastTimeLapseFrameRealTimestampUs = *timestampUs; return false; } { Mutex::Autolock autoLock(mQuickStopLock); // mForceRead may be set to true by startQuickReadReturns(). In that // case don't skip this frame. if (mForceRead) { LOGV("dataCallbackTimestamp timelapse: forced read"); mForceRead = false; *timestampUs = mLastFrameTimestampUs; return false; } } if (*timestampUs < (mLastTimeLapseFrameRealTimestampUs + mTimeBetweenTimeLapseFrameCaptureUs)) { // Skip all frames from last encoded frame until // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. // Tell the camera to release its recording frame and return. LOGV("dataCallbackTimestamp timelapse: skipping intermediate frame"); return true; } else { // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: // - Reset mLastTimeLapseFrameRealTimestampUs to current time. // - Artificially modify timestampUs to be one frame time (1/framerate) ahead // of the last encoded frame's time stamp. LOGV("dataCallbackTimestamp timelapse: got timelapse frame"); mLastTimeLapseFrameRealTimestampUs = *timestampUs; *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; return false; } } return false; } void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, const sp &data) { if (!mUseStillCameraForTimeLapse) { mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs); } else { Mutex::Autolock autoLock(mCameraIdleLock); // If we are using the still camera and stop() has been called, it may // be waiting for the camera to get idle. In that case return // immediately. Calling CameraSource::dataCallbackTimestamp() will lead // to a deadlock since it tries to access CameraSource::mLock which in // this case is held by CameraSource::stop() currently waiting for the // camera to get idle. And camera will not get idle until this call // returns. if (mStopWaitingForIdleCamera) { return; } } CameraSource::dataCallbackTimestamp(timestampUs, msgType, data); } } // namespace android