/*
* 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 <utils/Log.h>
#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<JpegError *>(cinfo->err);
error->parent->mJpegErrorInfo = cinfo;
}
void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
JpegDestination *dest = static_cast<JpegDestination *>(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