blob: dcf0aa27e667baf29e2fec7495d2626317f6f07a [file] [log] [blame]
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//#define ALOG_NDEBUG 0
#define ALOG_NIDEBUG 0
#define LOG_TAG "QCameraMjpegDecode"
#include <utils/Log.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
extern "C" {
#include "jpeg_buffer.h"
#include "jpeg_common.h"
#include "jpegd.h"
}
#include "QCameraMjpegDecode.h"
/* TBDJ: Can be removed */
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
// Abstract the return type of the function to be run as a thread
#define OS_THREAD_FUNC_RET_T void *
// Abstract the argument type to the thread function
#define OS_THREAD_FUNC_ARG_T void *
// Helpful constants for return values in the thread functions
#define OS_THREAD_FUNC_RET_SUCCEEDED (OS_THREAD_FUNC_RET_T)0
#define OS_THREAD_FUNC_RET_FAILED (OS_THREAD_FUNC_RET_T)1
// Abstract the function modifier placed in the beginning of the thread
// declaration (empty for Linux)
#define OS_THREAD_FUNC_MODIFIER
#define os_mutex_init(a) pthread_mutex_init(a, NULL)
#define os_cond_init(a) pthread_cond_init(a, NULL)
#define os_mutex_lock pthread_mutex_lock
#define os_mutex_unlock pthread_mutex_unlock
#define os_cond_wait pthread_cond_wait
#define os_cond_signal pthread_cond_signal
const char event_to_string[4][14] =
{
"EVENT_DONE",
"EVENT_WARNING",
"EVENT_ERROR",
};
typedef struct
{
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t preference;
uint32_t abort_time;
uint16_t back_to_back_count;
/* TBDJ: Is this required */
int32_t rotation;
/* TBDJ: Is this required */
jpeg_rectangle_t region;
/* TBDJ: Is this required */
jpegd_scale_type_t scale_factor;
uint32_t hw_rotation;
char* inputMjpegBuffer;
int inputMjpegBufferSize;
char* outputYptr;
char* outputUVptr;
} test_args_t;
typedef struct
{
int tid;
pthread_t thread;
jpegd_obj_t decoder;
uint8_t decoding;
uint8_t decode_success;
pthread_mutex_t mutex;
pthread_cond_t cond;
test_args_t *p_args;
jpegd_output_buf_t *p_whole_output_buf;
} thread_ctrl_blk_t;
OS_THREAD_FUNC_RET_T OS_THREAD_FUNC_MODIFIER decoder_test(OS_THREAD_FUNC_ARG_T p_thread_args);
void decoder_event_handler(void *p_user_data,
jpeg_event_t event,
void *p_arg);
int decoder_output_handler(void *p_user_data,
jpegd_output_buf_t *p_output_buffer,
uint32_t first_row_id,
uint8_t is_last_buffer);
uint32_t decoder_input_req_handler(void *p_user_data,
jpeg_buffer_t buffer,
uint32_t start_offset,
uint32_t length);
static void* insertHuffmanTable(void *p, int size);
static int mjpegd_timer_start(timespec *p_timer);
static int mjpegd_timer_get_elapsed(timespec *p_timer, int *elapsed_in_ms, uint8_t reset_start);
static int mjpegd_cond_timedwait(pthread_cond_t *p_cond, pthread_mutex_t *p_mutex, uint32_t ms);
// Global variables
/* TBDJ: can be removed */
thread_ctrl_blk_t *thread_ctrl_blks = NULL;
/*
* This function initializes the mjpeg decoder and returns the object
*/
MJPEGD_ERR mjpegDecoderInit(void** mjpegd_obj)
{
test_args_t* mjpegd;
ALOGD("%s: E", __func__);
mjpegd = (test_args_t *)malloc(sizeof(test_args_t));
if(!mjpegd)
return MJPEGD_INSUFFICIENT_MEM;
memset(mjpegd, 0, sizeof(test_args_t));
/* Defaults */
/* Due to current limitation, s/w decoder is selected always */
mjpegd->preference = JPEG_DECODER_PREF_HW_ACCELERATED_PREFERRED;
mjpegd->back_to_back_count = 1;
mjpegd->rotation = 0;
mjpegd->hw_rotation = 0;
mjpegd->scale_factor = (jpegd_scale_type_t)1;
/* TBDJ: can be removed */
mjpegd->width = 640;
mjpegd->height = 480;
mjpegd->abort_time = 0;
*mjpegd_obj = (void *)mjpegd;
ALOGD("%s: X", __func__);
return MJPEGD_NO_ERROR;
}
MJPEGD_ERR mjpegDecode(
void* mjpegd_obj,
char* inputMjpegBuffer,
int inputMjpegBufferSize,
char* outputYptr,
char* outputUVptr,
int outputFormat)
{
int rc, c, i;
test_args_t* mjpegd;
test_args_t test_args;
ALOGD("%s: E", __func__);
/* store input arguments in the context */
mjpegd = (test_args_t*) mjpegd_obj;
mjpegd->inputMjpegBuffer = inputMjpegBuffer;
mjpegd->inputMjpegBufferSize = inputMjpegBufferSize;
mjpegd->outputYptr = outputYptr;
mjpegd->outputUVptr = outputUVptr;
mjpegd->format = outputFormat;
/* TBDJ: can be removed */
memcpy(&test_args, mjpegd, sizeof(test_args_t));
// check the formats
if (((test_args.format == YCRCBLP_H1V2) || (test_args.format == YCBCRLP_H1V2) ||
(test_args.format == YCRCBLP_H1V1) || (test_args.format == YCBCRLP_H1V1)) &&
!(test_args.preference == JPEG_DECODER_PREF_HW_ACCELERATED_ONLY)) {
ALOGE("%s:These formats are not supported by SW format %d", __func__, test_args.format);
return 1;
}
// Create thread control blocks
thread_ctrl_blks = (thread_ctrl_blk_t *)malloc( sizeof(thread_ctrl_blk_t));
if (!thread_ctrl_blks)
{
ALOGE("%s: decoder_test failed: insufficient memory in creating thread control blocks", __func__);
return 1;
}
memset(thread_ctrl_blks, 0, sizeof(thread_ctrl_blk_t));
// Initialize the blocks and kick off the threads
thread_ctrl_blks[i].tid = i;
thread_ctrl_blks[i].p_args = &test_args;
os_mutex_init(&thread_ctrl_blks[i].mutex);
os_cond_init(&thread_ctrl_blks[i].cond);
rc = (int)decoder_test(&thread_ctrl_blks[i]);
if (!rc)
ALOGD("%s: decoder_test finished successfully ", __func__);
else
ALOGE("%s: decoder_test failed",__func__);
ALOGD("%s: X rc: %d", __func__, rc);
return rc;
}
OS_THREAD_FUNC_RET_T OS_THREAD_FUNC_MODIFIER decoder_test(OS_THREAD_FUNC_ARG_T arg)
{
int rc, i;
jpegd_obj_t decoder;
jpegd_src_t source;
jpegd_dst_t dest;
jpegd_cfg_t config;
jpeg_hdr_t header;
jpegd_output_buf_t p_output_buffers;
uint32_t output_buffers_count = 1; // currently only 1 buffer a time is supported
uint8_t use_pmem = true;
timespec os_timer;
thread_ctrl_blk_t *p_thread_arg = (thread_ctrl_blk_t *)arg;
test_args_t *p_args = p_thread_arg->p_args;
uint32_t output_width;
uint32_t output_height;
uint32_t total_time = 0;
ALOGD("%s: E", __func__);
// Determine whether pmem should be used (useful for pc environment testing where
// pmem is not available)
if ((jpegd_preference_t)p_args->preference == JPEG_DECODER_PREF_SOFTWARE_PREFERRED ||
(jpegd_preference_t)p_args->preference == JPEG_DECODER_PREF_SOFTWARE_ONLY) {
use_pmem = false;
}
if (((jpegd_preference_t)p_args->preference !=
JPEG_DECODER_PREF_HW_ACCELERATED_ONLY) &&
((jpegd_preference_t)p_args->scale_factor > 0)) {
ALOGI("%s: Setting scale factor to 1x", __func__);
}
ALOGD("%s: before jpegd_init p_thread_arg: %p", __func__, p_thread_arg);
// Initialize decoder
rc = jpegd_init(&decoder,
&decoder_event_handler,
&decoder_output_handler,
p_thread_arg);
if (JPEG_FAILED(rc)) {
ALOGE("%s: decoder_test: jpegd_init failed", __func__);
goto fail;
}
p_thread_arg->decoder = decoder;
// Set source information
source.p_input_req_handler = &decoder_input_req_handler;
source.total_length = p_args->inputMjpegBufferSize & 0xffffffff;
rc = jpeg_buffer_init(&source.buffers[0]);
if (JPEG_SUCCEEDED(rc)) {
/* TBDJ: why buffer [1] */
rc = jpeg_buffer_init(&source.buffers[1]);
}
if (JPEG_SUCCEEDED(rc)) {
#if 1
rc = jpeg_buffer_allocate(source.buffers[0], 0xA000, use_pmem);
#else
rc = jpeg_buffer_use_external_buffer(source.buffers[0],
(uint8_t *)p_args->inputMjpegBuffer,
p_args->inputMjpegBufferSize,
0);
#endif
ALOGD("%s: source.buffers[0]:%p compressed buffer ptr = %p", __func__,
source.buffers[0], p_args->inputMjpegBuffer);
}
if (JPEG_SUCCEEDED(rc)) {
#if 1
rc = jpeg_buffer_allocate(source.buffers[1], 0xA000, use_pmem);
#else
rc = jpeg_buffer_use_external_buffer(source.buffers[1],
(uint8_t *)p_args->inputMjpegBuffer,
p_args->inputMjpegBufferSize,
0);
#endif
ALOGD("%s: source.buffers[1]:%p compressed buffer ptr = %p", __func__,
source.buffers[1], p_args->inputMjpegBuffer);
}
if (JPEG_FAILED(rc)) {
jpeg_buffer_destroy(&source.buffers[0]);
jpeg_buffer_destroy(&source.buffers[1]);
goto fail;
}
ALOGI("%s: *** Starting back-to-back decoding of %d frame(s)***\n",
__func__, p_args->back_to_back_count);
// Loop to perform n back-to-back decoding (to the same output file)
for(i = 0; i < p_args->back_to_back_count; i++) {
if(mjpegd_timer_start(&os_timer) < 0) {
ALOGE("%s: failed to get start time", __func__);
}
/* TBDJ: Every frame? */
ALOGD("%s: before jpegd_set_source source.p_arg:%p", __func__, source.p_arg);
rc = jpegd_set_source(decoder, &source);
if (JPEG_FAILED(rc))
{
ALOGE("%s: jpegd_set_source failed", __func__);
goto fail;
}
rc = jpegd_read_header(decoder, &header);
if (JPEG_FAILED(rc))
{
ALOGE("%s: jpegd_read_header failed", __func__);
goto fail;
}
p_args->width = header.main.width;
p_args->height = header.main.height;
ALOGD("%s: main dimension: (%dx%d) subsampling: (%d)", __func__,
header.main.width, header.main.height, (int)header.main.subsampling);
// main image decoding:
// Set destination information
dest.width = (p_args->width) ? (p_args->width) : header.main.width;
dest.height = (p_args->height) ? (p_args->height) : header.main.height;
dest.output_format = (jpeg_color_format_t) p_args->format;
dest.region = p_args->region;
// if region is defined, re-assign the output width/height
output_width = dest.width;
output_height = dest.height;
if (p_args->region.right || p_args->region.bottom)
{
if (0 == p_args->rotation || 180 == p_args->rotation)
{
output_width = MIN((dest.width),
(uint32_t)(dest.region.right - dest.region.left + 1));
output_height = MIN((dest.height),
(uint32_t)(dest.region.bottom - dest.region.top + 1));
}
// Swap output width/height for 90/270 rotation cases
else if (90 == p_args->rotation || 270 == p_args->rotation)
{
output_height = MIN((dest.height),
(uint32_t)(dest.region.right - dest.region.left + 1));
output_width = MIN((dest.width),
(uint32_t)(dest.region.bottom - dest.region.top + 1));
}
// Unsupported rotation cases
else
{
goto fail;
}
}
if (dest.output_format == YCRCBLP_H2V2 || dest.output_format == YCBCRLP_H2V2 ||
dest.output_format == YCRCBLP_H2V1 || dest.output_format == YCBCRLP_H2V1 ||
dest.output_format == YCRCBLP_H1V2 || dest.output_format == YCBCRLP_H1V2 ||
dest.output_format == YCRCBLP_H1V1 || dest.output_format == YCBCRLP_H1V1) {
jpeg_buffer_init(&p_output_buffers.data.yuv.luma_buf);
jpeg_buffer_init(&p_output_buffers.data.yuv.chroma_buf);
} else {
jpeg_buffer_init(&p_output_buffers.data.rgb.rgb_buf);
}
{
// Assign 0 to tile width and height
// to indicate that no tiling is requested.
p_output_buffers.tile_width = 0;
p_output_buffers.tile_height = 0;
}
p_output_buffers.is_in_q = 0;
switch (dest.output_format)
{
case YCRCBLP_H2V2:
case YCBCRLP_H2V2:
// case YCRCBLP_H2V1:
// case YCBCRLP_H2V1:
// case YCRCBLP_H1V2:
// case YCBCRLP_H1V2:
// case YCRCBLP_H1V1:
// case YCBCRLP_H1V1:
jpeg_buffer_use_external_buffer(
p_output_buffers.data.yuv.luma_buf,
(uint8_t*)p_args->outputYptr,
p_args->width * p_args->height * SQUARE(p_args->scale_factor),
0);
jpeg_buffer_use_external_buffer(
p_output_buffers.data.yuv.chroma_buf,
(uint8_t*)p_args->outputUVptr,
p_args->width * p_args->height / 2 * SQUARE(p_args->scale_factor),
0);
break;
default:
ALOGE("%s: decoder_test: unsupported output format", __func__);
goto fail;
}
// Set up configuration
memset(&config, 0, sizeof(jpegd_cfg_t));
config.preference = (jpegd_preference_t) p_args->preference;
config.decode_from = JPEGD_DECODE_FROM_AUTO;
config.rotation = p_args->rotation;
config.scale_factor = p_args->scale_factor;
config.hw_rotation = p_args->hw_rotation;
dest.back_to_back_count = p_args->back_to_back_count;
// Start decoding
p_thread_arg->decoding = true;
rc = jpegd_start(decoder, &config, &dest, &p_output_buffers, output_buffers_count);
dest.back_to_back_count--;
if(JPEG_FAILED(rc)) {
ALOGE("%s: decoder_test: jpegd_start failed (rc=%d)\n",
__func__, rc);
goto fail;
}
ALOGD("%s: decoder_test: jpegd_start succeeded", __func__);
// Do abort
if (p_args->abort_time) {
os_mutex_lock(&p_thread_arg->mutex);
while (p_thread_arg->decoding)
{
rc = mjpegd_cond_timedwait(&p_thread_arg->cond, &p_thread_arg->mutex, p_args->abort_time);
if (rc == JPEGERR_ETIMEDOUT)
{
// Do abort
os_mutex_unlock(&p_thread_arg->mutex);
rc = jpegd_abort(decoder);
if (rc)
{
ALOGE("%s: decoder_test: jpegd_abort failed: %d", __func__, rc);
goto fail;
}
break;
}
}
if (p_thread_arg->decoding)
os_mutex_unlock(&p_thread_arg->mutex);
} else {
// Wait until decoding is done or stopped due to error
os_mutex_lock(&p_thread_arg->mutex);
while (p_thread_arg->decoding)
{
os_cond_wait(&p_thread_arg->cond, &p_thread_arg->mutex);
}
os_mutex_unlock(&p_thread_arg->mutex);
}
int diff;
// Display the time elapsed
if (mjpegd_timer_get_elapsed(&os_timer, &diff, 0) < 0) {
ALOGE("%s: decoder_test: failed to get elapsed time", __func__);
} else {
if(p_args->abort_time) {
if(p_thread_arg->decoding) {
ALOGI("%s: decoder_test: decoding aborted successfully after %d ms", __func__, diff);
goto buffer_clean_up;
}
else
{
ALOGI("%s: decoder_test: decoding stopped before abort is issued. "
"decode time: %d ms", __func__, diff);
}
}
else {
if(p_thread_arg->decode_success) {
total_time += diff;
ALOGI("%s: decode time: %d ms (%d frame(s), total=%dms, avg=%dms/frame)",
__func__, diff, i+1, total_time, total_time/(i+1));
}
else
{
fprintf(stderr, "decoder_test: decode failed\n");
}
}
}
}
if(p_thread_arg->decode_success) {
ALOGD("%s: Frame(s) = %d, Total Time = %dms, Avg. decode time = %dms/frame)\n",
__func__, p_args->back_to_back_count, total_time, total_time/p_args->back_to_back_count);
}
buffer_clean_up:
// Clean up decoder and allocate buffers
jpeg_buffer_destroy(&source.buffers[0]);
jpeg_buffer_destroy(&source.buffers[1]);
switch (dest.output_format)
{
case YCRCBLP_H2V2:
case YCBCRLP_H2V2:
case YCRCBLP_H2V1:
case YCBCRLP_H2V1:
case YCRCBLP_H1V2:
case YCBCRLP_H1V2:
case YCRCBLP_H1V1:
case YCBCRLP_H1V1:
jpeg_buffer_destroy(&p_output_buffers.data.yuv.luma_buf);
jpeg_buffer_destroy(&p_output_buffers.data.yuv.chroma_buf);
break;
default:
break;
}
jpegd_destroy(&decoder);
if (!p_thread_arg->decode_success)
{
goto fail;
}
ALOGD("%s: X", __func__);
return OS_THREAD_FUNC_RET_SUCCEEDED;
fail:
ALOGD("%s: X", __func__);
return OS_THREAD_FUNC_RET_FAILED;
}
void decoder_event_handler(void *p_user_data,
jpeg_event_t event,
void *p_arg)
{
thread_ctrl_blk_t *p_thread_arg = (thread_ctrl_blk_t *)p_user_data;
ALOGD("%s: E", __func__);
ALOGD("%s: Event: %s\n", __func__, event_to_string[event]);
if (event == JPEG_EVENT_DONE)
{
p_thread_arg->decode_success = true;
ALOGD("%s: decode_success: %d\n", __func__, p_thread_arg->decode_success);
}
// If it is not a warning event, decoder has stopped; Signal
// main thread to clean up
if (event != JPEG_EVENT_WARNING)
{
os_mutex_lock(&p_thread_arg->mutex);
p_thread_arg->decoding = false;
os_cond_signal(&p_thread_arg->cond);
os_mutex_unlock(&p_thread_arg->mutex);
}
ALOGD("%s: X", __func__);
}
// consumes the output buffer.
/*TBDJ: Can be removed. Is this related to tiling */
int decoder_output_handler(void *p_user_data,
jpegd_output_buf_t *p_output_buffer,
uint32_t first_row_id,
uint8_t is_last_buffer)
{
uint8_t* whole_output_buf_ptr, *tiling_buf_ptr;
ALOGD("%s: E", __func__);
thread_ctrl_blk_t *p_thread_arg = (thread_ctrl_blk_t *)p_user_data;
jpeg_buffer_get_addr(p_thread_arg->p_whole_output_buf->data.rgb.rgb_buf, &whole_output_buf_ptr);
jpeg_buffer_get_addr(p_output_buffer->data.rgb.rgb_buf, &tiling_buf_ptr);
if (p_output_buffer->tile_height != 1)
return JPEGERR_EUNSUPPORTED;
// testing purpose only
// This is to simulate that the user needs to bail out when error happens
// in the middle of decoding
//if (first_row_id == 162)
// return JPEGERR_EFAILED;
// do not enqueue any buffer if it reaches the last buffer
if (!is_last_buffer)
{
jpegd_enqueue_output_buf(p_thread_arg->decoder, p_output_buffer, 1);
}
ALOGD("%s: X", __func__);
return JPEGERR_SUCCESS;
}
// p_reader->p_input_req_handler(p_reader->decoder,
// p_reader->p_input_buf,
// p_reader->next_byte_offset,
// MAX_BYTES_TO_FETCH);
uint32_t decoder_input_req_handler(void *p_user_data,
jpeg_buffer_t buffer,
uint32_t start_offset,
uint32_t length)
{
uint32_t buf_size;
uint8_t *buf_ptr;
int bytes_to_read, bytes_read, rc;
thread_ctrl_blk_t *p_thread_arg = (thread_ctrl_blk_t *)p_user_data;
thread_ctrl_blk_t *thread_ctrl_blk = (thread_ctrl_blk_t *)p_user_data;
test_args_t* mjpegd = (test_args_t*) thread_ctrl_blk->p_args;
ALOGD("%s: E", __func__);
jpeg_buffer_get_max_size(buffer, &buf_size);
jpeg_buffer_get_addr(buffer, &buf_ptr);
bytes_to_read = (length < buf_size) ? length : buf_size;
bytes_read = 0;
ALOGD("%s: buf_ptr = %p, start_offset = %d, length = %d buf_size = %d bytes_to_read = %d", __func__, buf_ptr, start_offset, length, buf_size, bytes_to_read);
if (bytes_to_read)
{
/* TBDJ: Should avoid this Mem copy */
#if 1
memcpy(buf_ptr, (char *)mjpegd->inputMjpegBuffer + start_offset, bytes_to_read);
#else
if(JPEGERR_SUCCESS != jpeg_buffer_set_start_offset(buffer, start_offset))
ALOGE("%s: jpeg_buffer_set_start_offset failed", __func__);
#endif
bytes_read = bytes_to_read;
}
ALOGD("%s: X", __func__);
return bytes_read;
}
static int mjpegd_timer_start(timespec *p_timer)
{
if (!p_timer)
return JPEGERR_ENULLPTR;
if (clock_gettime(CLOCK_REALTIME, p_timer))
return JPEGERR_EFAILED;
return JPEGERR_SUCCESS;
}
static int mjpegd_timer_get_elapsed(timespec *p_timer, int *elapsed_in_ms, uint8_t reset_start)
{
timespec now;
long diff;
int rc = mjpegd_timer_start(&now);
if (JPEG_FAILED(rc))
return rc;
diff = (long)(now.tv_sec - p_timer->tv_sec) * 1000;
diff += (long)(now.tv_nsec - p_timer->tv_nsec) / 1000000;
*elapsed_in_ms = (int)diff;
if (reset_start)
*p_timer = now;
return JPEGERR_SUCCESS;
}
int mjpegd_cond_timedwait(pthread_cond_t *p_cond, pthread_mutex_t *p_mutex, uint32_t ms)
{
struct timespec ts;
int rc = clock_gettime(CLOCK_REALTIME, &ts);
if (rc < 0) return rc;
if (ms >= 1000) {
ts.tv_sec += (ms/1000);
ts.tv_nsec += ((ms%1000) * 1000000);
} else {
ts.tv_nsec += (ms * 1000000);
}
rc = pthread_cond_timedwait(p_cond, p_mutex, &ts);
if (rc == ETIMEDOUT)
{
rc = JPEGERR_ETIMEDOUT;
}
return rc;
}