1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_NDEBUG 0
18 #define LOG_TAG "EmulatedCamera2_JpegCompressor"
19
20 #include <utils/Log.h>
21 #include <ui/GraphicBufferMapper.h>
22
23 #include "JpegCompressor.h"
24 #include "../EmulatedFakeCamera2.h"
25
26 namespace android {
27
JpegCompressor(EmulatedFakeCamera2 * parent)28 JpegCompressor::JpegCompressor(EmulatedFakeCamera2 *parent):
29 Thread(false),
30 mIsBusy(false),
31 mParent(parent),
32 mBuffers(NULL),
33 mCaptureTime(0) {
34 }
35
~JpegCompressor()36 JpegCompressor::~JpegCompressor() {
37 Mutex::Autolock lock(mMutex);
38 }
39
start(Buffers * buffers,nsecs_t captureTime)40 status_t JpegCompressor::start(Buffers *buffers,
41 nsecs_t captureTime) {
42 Mutex::Autolock lock(mMutex);
43 {
44 Mutex::Autolock busyLock(mBusyMutex);
45
46 if (mIsBusy) {
47 ALOGE("%s: Already processing a buffer!", __FUNCTION__);
48 return INVALID_OPERATION;
49 }
50
51 mIsBusy = true;
52
53 mBuffers = buffers;
54 mCaptureTime = captureTime;
55 }
56
57 status_t res;
58 res = run("EmulatedFakeCamera2::JpegCompressor");
59 if (res != OK) {
60 ALOGE("%s: Unable to start up compression thread: %s (%d)",
61 __FUNCTION__, strerror(-res), res);
62 delete mBuffers;
63 }
64 return res;
65 }
66
cancel()67 status_t JpegCompressor::cancel() {
68 requestExitAndWait();
69 return OK;
70 }
71
readyToRun()72 status_t JpegCompressor::readyToRun() {
73 return OK;
74 }
75
threadLoop()76 bool JpegCompressor::threadLoop() {
77 Mutex::Autolock lock(mMutex);
78 ALOGV("%s: Starting compression thread", __FUNCTION__);
79
80 // Find source and target buffers. Assumes only one buffer matches
81 // each condition!
82
83 bool foundJpeg = false, mFoundAux = false;
84 for (size_t i = 0; i < mBuffers->size(); i++) {
85 const StreamBuffer &b = (*mBuffers)[i];
86 if (b.format == HAL_PIXEL_FORMAT_BLOB) {
87 mJpegBuffer = b;
88 mFoundJpeg = true;
89 } else if (b.streamId <= 0) {
90 mAuxBuffer = b;
91 mFoundAux = true;
92 }
93 if (mFoundJpeg && mFoundAux) break;
94 }
95 if (!mFoundJpeg || !mFoundAux) {
96 ALOGE("%s: Unable to find buffers for JPEG source/destination",
97 __FUNCTION__);
98 cleanUp();
99 return false;
100 }
101
102 // Set up error management
103
104 mJpegErrorInfo = NULL;
105 JpegError error;
106 error.parent = this;
107
108 mCInfo.err = jpeg_std_error(&error);
109 mCInfo.err->error_exit = jpegErrorHandler;
110
111 jpeg_create_compress(&mCInfo);
112 if (checkError("Error initializing compression")) return false;
113
114 // Route compressed data straight to output stream buffer
115
116 JpegDestination jpegDestMgr;
117 jpegDestMgr.parent = this;
118 jpegDestMgr.init_destination = jpegInitDestination;
119 jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
120 jpegDestMgr.term_destination = jpegTermDestination;
121
122 mCInfo.dest = &jpegDestMgr;
123
124 // Set up compression parameters
125
126 mCInfo.image_width = mAuxBuffer.width;
127 mCInfo.image_height = mAuxBuffer.height;
128 mCInfo.input_components = 3;
129 mCInfo.in_color_space = JCS_RGB;
130
131 jpeg_set_defaults(&mCInfo);
132 if (checkError("Error configuring defaults")) return false;
133
134 // Do compression
135
136 jpeg_start_compress(&mCInfo, TRUE);
137 if (checkError("Error starting compression")) return false;
138
139 size_t rowStride = mAuxBuffer.stride * 3;
140 const size_t kChunkSize = 32;
141 while (mCInfo.next_scanline < mCInfo.image_height) {
142 JSAMPROW chunk[kChunkSize];
143 for (size_t i = 0 ; i < kChunkSize; i++) {
144 chunk[i] = (JSAMPROW)
145 (mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
146 }
147 jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
148 if (checkError("Error while compressing")) return false;
149 if (exitPending()) {
150 ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
151 cleanUp();
152 return false;
153 }
154 }
155
156 jpeg_finish_compress(&mCInfo);
157 if (checkError("Error while finishing compression")) return false;
158
159 // Write to JPEG output stream
160
161 ALOGV("%s: Compression complete, pushing to stream %d", __FUNCTION__,
162 mJpegBuffer.streamId);
163
164 GraphicBufferMapper::get().unlock(*(mJpegBuffer.buffer));
165 status_t res;
166 const Stream &s = mParent->getStreamInfo(mJpegBuffer.streamId);
167 res = s.ops->enqueue_buffer(s.ops, mCaptureTime, mJpegBuffer.buffer);
168 if (res != OK) {
169 ALOGE("%s: Error queueing compressed image buffer %p: %s (%d)",
170 __FUNCTION__, mJpegBuffer.buffer, strerror(-res), res);
171 mParent->signalError();
172 }
173
174 // All done
175
176 cleanUp();
177
178 return false;
179 }
180
isBusy()181 bool JpegCompressor::isBusy() {
182 Mutex::Autolock busyLock(mBusyMutex);
183 return mIsBusy;
184 }
185
isStreamInUse(uint32_t id)186 bool JpegCompressor::isStreamInUse(uint32_t id) {
187 Mutex::Autolock lock(mBusyMutex);
188
189 if (mBuffers && mIsBusy) {
190 for (size_t i = 0; i < mBuffers->size(); i++) {
191 if ( (*mBuffers)[i].streamId == (int)id ) return true;
192 }
193 }
194 return false;
195 }
196
waitForDone(nsecs_t timeout)197 bool JpegCompressor::waitForDone(nsecs_t timeout) {
198 Mutex::Autolock lock(mBusyMutex);
199 status_t res = OK;
200 if (mIsBusy) {
201 res = mDone.waitRelative(mBusyMutex, timeout);
202 }
203 return (res == OK);
204 }
205
checkError(const char * msg)206 bool JpegCompressor::checkError(const char *msg) {
207 if (mJpegErrorInfo) {
208 char errBuffer[JMSG_LENGTH_MAX];
209 mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
210 ALOGE("%s: %s: %s",
211 __FUNCTION__, msg, errBuffer);
212 cleanUp();
213 mJpegErrorInfo = NULL;
214 return true;
215 }
216 return false;
217 }
218
cleanUp()219 void JpegCompressor::cleanUp() {
220 status_t res;
221 jpeg_destroy_compress(&mCInfo);
222 Mutex::Autolock lock(mBusyMutex);
223
224 if (mFoundAux) {
225 if (mAuxBuffer.streamId == 0) {
226 delete[] mAuxBuffer.img;
227 } else {
228 GraphicBufferMapper::get().unlock(*(mAuxBuffer.buffer));
229 const ReprocessStream &s =
230 mParent->getReprocessStreamInfo(-mAuxBuffer.streamId);
231 res = s.ops->release_buffer(s.ops, mAuxBuffer.buffer);
232 if (res != OK) {
233 ALOGE("Error releasing reprocess buffer %p: %s (%d)",
234 mAuxBuffer.buffer, strerror(-res), res);
235 mParent->signalError();
236 }
237 }
238 }
239 delete mBuffers;
240 mBuffers = NULL;
241
242 mIsBusy = false;
243 mDone.signal();
244 }
245
jpegErrorHandler(j_common_ptr cinfo)246 void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
247 JpegError *error = static_cast<JpegError*>(cinfo->err);
248 error->parent->mJpegErrorInfo = cinfo;
249 }
250
jpegInitDestination(j_compress_ptr cinfo)251 void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
252 JpegDestination *dest= static_cast<JpegDestination*>(cinfo->dest);
253 ALOGV("%s: Setting destination to %p, size %d",
254 __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize);
255 dest->next_output_byte = (JOCTET*)(dest->parent->mJpegBuffer.img);
256 dest->free_in_buffer = kMaxJpegSize;
257 }
258
jpegEmptyOutputBuffer(j_compress_ptr cinfo)259 boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr cinfo) {
260 ALOGE("%s: JPEG destination buffer overflow!",
261 __FUNCTION__);
262 return true;
263 }
264
jpegTermDestination(j_compress_ptr cinfo)265 void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
266 ALOGV("%s: Done writing JPEG data. %d bytes left in buffer",
267 __FUNCTION__, cinfo->dest->free_in_buffer);
268 }
269
270 } // namespace android
271