/* * Copyright (C) 2012 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 "EmulatedCamera2_JpegCompressor" #include #include "gralloc_cb.h" #include "JpegCompressor.h" #include "../EmulatedFakeCamera2.h" #include "../EmulatedFakeCamera3.h" #include "../Exif.h" #include "../Thumbnail.h" #include "hardware/camera3.h" namespace android { JpegCompressor::JpegCompressor(): Thread(false), mIsBusy(false), mSynchronous(false), mBuffers(NULL), mListener(NULL) { } JpegCompressor::~JpegCompressor() { Mutex::Autolock lock(mMutex); } status_t JpegCompressor::reserve() { Mutex::Autolock busyLock(mBusyMutex); if (mIsBusy) { ALOGE("%s: Already processing a buffer!", __FUNCTION__); return INVALID_OPERATION; } mIsBusy = true; return OK; } status_t JpegCompressor::start(Buffers *buffers, JpegListener *listener, CameraMetadata* settings) { if (listener == NULL) { ALOGE("%s: NULL listener not allowed!", __FUNCTION__); return BAD_VALUE; } Mutex::Autolock lock(mMutex); { Mutex::Autolock busyLock(mBusyMutex); if (!mIsBusy) { ALOGE("Called start without reserve() first!"); return INVALID_OPERATION; } mSynchronous = false; mBuffers = buffers; mListener = listener; if (settings) { mSettings = *settings; } } status_t res; res = run("EmulatedFakeCamera2::JpegCompressor"); if (res != OK) { ALOGE("%s: Unable to start up compression thread: %s (%d)", __FUNCTION__, strerror(-res), res); delete mBuffers; } return res; } status_t JpegCompressor::compressSynchronous(Buffers *buffers) { status_t res; Mutex::Autolock lock(mMutex); { Mutex::Autolock busyLock(mBusyMutex); if (mIsBusy) { ALOGE("%s: Already processing a buffer!", __FUNCTION__); return INVALID_OPERATION; } mIsBusy = true; mSynchronous = true; mBuffers = buffers; } res = compress(); cleanUp(); return res; } status_t JpegCompressor::cancel() { requestExitAndWait(); return OK; } status_t JpegCompressor::readyToRun() { return OK; } bool JpegCompressor::threadLoop() { status_t res; ALOGV("%s: Starting compression thread", __FUNCTION__); res = compress(); mListener->onJpegDone(mJpegBuffer, res == OK); cleanUp(); return false; } status_t JpegCompressor::compress() { // Find source and target buffers. Assumes only one buffer matches // each condition! bool mFoundAux = false; int thumbWidth = 0, thumbHeight = 0; unsigned char thumbJpegQuality = 90; unsigned char jpegQuality = 90; camera_metadata_entry_t entry; for (size_t i = 0; i < mBuffers->size(); i++) { const StreamBuffer &b = (*mBuffers)[i]; if (b.format == HAL_PIXEL_FORMAT_BLOB) { mJpegBuffer = b; mFoundJpeg = true; } else if (b.streamId <= 0) { mAuxBuffer = b; mFoundAux = true; } if (mFoundJpeg && mFoundAux) break; } if (!mFoundJpeg || !mFoundAux) { ALOGE("%s: Unable to find buffers for JPEG source/destination", __FUNCTION__); return BAD_VALUE; } // Create EXIF data and compress thumbnail ExifData* exifData = createExifData(mSettings, mAuxBuffer.width, mAuxBuffer.height); entry = mSettings.find(ANDROID_JPEG_THUMBNAIL_SIZE); if (entry.count > 0) { thumbWidth = entry.data.i32[0]; thumbHeight = entry.data.i32[1]; } entry = mSettings.find(ANDROID_JPEG_THUMBNAIL_QUALITY); if (entry.count > 0) { thumbJpegQuality = entry.data.u8[0]; } if (thumbWidth > 0 && thumbHeight > 0) { createThumbnail(static_cast(mAuxBuffer.img), mAuxBuffer.width, mAuxBuffer.height, thumbWidth, thumbHeight, thumbJpegQuality, exifData); } // Compress the image entry = mSettings.find(ANDROID_JPEG_QUALITY); if (entry.count > 0) { jpegQuality = entry.data.u8[0]; } NV21JpegCompressor nV21JpegCompressor; nV21JpegCompressor.compressRawImage((void*)mAuxBuffer.img, mAuxBuffer.width, mAuxBuffer.height, jpegQuality, exifData); nV21JpegCompressor.getCompressedImage((void*)mJpegBuffer.img); // Refer to /hardware/libhardware/include/hardware/camera3.h // Transport header for compressed JPEG buffers in output streams. camera3_jpeg_blob_t jpeg_blob; cb_handle_t *cb = (cb_handle_t *)(*mJpegBuffer.buffer); jpeg_blob.jpeg_blob_id = CAMERA3_JPEG_BLOB_ID; jpeg_blob.jpeg_size = nV21JpegCompressor.getCompressedSize(); memcpy(mJpegBuffer.img + cb->width - sizeof(camera3_jpeg_blob_t), &jpeg_blob, sizeof(camera3_jpeg_blob_t)); freeExifData(exifData); return OK; } bool JpegCompressor::isBusy() { Mutex::Autolock busyLock(mBusyMutex); return mIsBusy; } bool JpegCompressor::isStreamInUse(uint32_t id) { Mutex::Autolock lock(mBusyMutex); if (mBuffers && mIsBusy) { for (size_t i = 0; i < mBuffers->size(); i++) { if ( (*mBuffers)[i].streamId == (int)id ) return true; } } return false; } bool JpegCompressor::waitForDone(nsecs_t timeout) { Mutex::Autolock lock(mBusyMutex); while (mIsBusy) { status_t res = mDone.waitRelative(mBusyMutex, timeout); if (res != OK) return false; } return true; } void JpegCompressor::cleanUp() { Mutex::Autolock lock(mBusyMutex); if (mFoundAux) { if (mAuxBuffer.streamId == 0) { delete[] mAuxBuffer.img; } else if (!mSynchronous) { mListener->onJpegInputDone(mAuxBuffer); } } if (!mSynchronous) { delete mBuffers; } mBuffers = NULL; mIsBusy = false; mDone.signal(); } JpegCompressor::JpegListener::~JpegListener() { } } // namespace android