/*
* 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 "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 foundJpeg = false, 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<const unsigned char*>(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() {
status_t res;
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