/* * 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 "../EmulatedFakeCamera2.h" #include "../EmulatedFakeCamera3.h" #include "JpegCompressor.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) { if (listener == NULL) { ALOGE("%s: NULL listener not allowed!", __FUNCTION__); return BAD_VALUE; } ALOGV("%s: Starting JPEG compression thread", __FUNCTION__); 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; } 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! ALOGV("%s: Compressing start", __FUNCTION__); bool mFoundAux = false; 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; } // Set up error management mJpegErrorInfo = NULL; JpegError error; error.parent = this; mCInfo.err = jpeg_std_error(&error); mCInfo.err->error_exit = jpegErrorHandler; jpeg_create_compress(&mCInfo); if (checkError("Error initializing compression")) return NO_INIT; // Route compressed data straight to output stream buffer JpegDestination jpegDestMgr; jpegDestMgr.parent = this; jpegDestMgr.init_destination = jpegInitDestination; jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer; jpegDestMgr.term_destination = jpegTermDestination; mCInfo.dest = &jpegDestMgr; // Set up compression parameters mCInfo.image_width = mAuxBuffer.width; mCInfo.image_height = mAuxBuffer.height; mCInfo.input_components = 3; mCInfo.in_color_space = JCS_RGB; jpeg_set_defaults(&mCInfo); if (checkError("Error configuring defaults")) return NO_INIT; // Do compression jpeg_start_compress(&mCInfo, TRUE); if (checkError("Error starting compression")) return NO_INIT; size_t rowStride = mAuxBuffer.stride * 3; const size_t kChunkSize = 32; while (mCInfo.next_scanline < mCInfo.image_height) { JSAMPROW chunk[kChunkSize]; for (size_t i = 0; i < kChunkSize; i++) { chunk[i] = (JSAMPROW)(mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride); } jpeg_write_scanlines(&mCInfo, chunk, kChunkSize); if (checkError("Error while compressing")) return NO_INIT; if (exitPending()) { ALOGV("%s: Cancel called, exiting early", __FUNCTION__); return TIMED_OUT; } } jpeg_finish_compress(&mCInfo); if (checkError("Error while finishing compression")) return NO_INIT; // All done ALOGV("%s: Compressing done", __FUNCTION__); 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; } bool JpegCompressor::checkError(const char *msg) { if (mJpegErrorInfo) { char errBuffer[JMSG_LENGTH_MAX]; mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer); ALOGE("%s: %s: %s", __FUNCTION__, msg, errBuffer); mJpegErrorInfo = NULL; return true; } return false; } void JpegCompressor::cleanUp() { jpeg_destroy_compress(&mCInfo); 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(); } void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) { JpegError *error = static_cast(cinfo->err); error->parent->mJpegErrorInfo = cinfo; } void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) { JpegDestination *dest = static_cast(cinfo->dest); ALOGV("%s: Setting destination to %p, size %zu", __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize); dest->next_output_byte = (JOCTET *)(dest->parent->mJpegBuffer.img); dest->free_in_buffer = kMaxJpegSize; } boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr /*cinfo*/) { ALOGE("%s: JPEG destination buffer overflow!", __FUNCTION__); return true; } void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) { ALOGV("%s: Done writing JPEG data. %zu bytes left in buffer", __FUNCTION__, cinfo->dest->free_in_buffer); } JpegCompressor::JpegListener::~JpegListener() {} } // namespace android