/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2010 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "common.h"
#include "debug.h"
#include "utils.h"
#include "mjpeg_decoder.h"
#if !defined(jpeg_boolean)
#define jpeg_boolean boolean
#endif
enum {
STATE_READ_HEADER,
STATE_START_DECOMPRESS,
STATE_READ_SCANLINES,
STATE_FINISH_DECOMPRESS
};
extern "C" {
static void init_source(j_decompress_ptr cinfo)
{
}
static jpeg_boolean fill_input_buffer(j_decompress_ptr cinfo)
{
return FALSE;
}
void mjpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
MJpegDecoder *decoder = (MJpegDecoder *)cinfo;
if (num_bytes > 0) {
if (cinfo->src->bytes_in_buffer >= (size_t)num_bytes) {
cinfo->src->next_input_byte += (size_t) num_bytes;
cinfo->src->bytes_in_buffer -= (size_t) num_bytes;
} else {
decoder->_extra_skip = num_bytes - cinfo->src->bytes_in_buffer;
cinfo->src->bytes_in_buffer = 0;
}
}
}
static void term_source (j_decompress_ptr cinfo)
{
return;
}
}
MJpegDecoder::MJpegDecoder(int width, int height,
int stride,
uint8_t *frame,
bool back_compat) :
_data(NULL)
, _data_size(0)
, _data_start(0)
, _data_end(0)
, _extra_skip(0)
, _width(width)
, _height(height)
, _stride(stride)
, _frame(frame)
, _back_compat(back_compat)
, _y(0)
, _state(0)
{
memset(&_cinfo, 0, sizeof(_cinfo));
_cinfo.err = jpeg_std_error (&_jerr);
jpeg_create_decompress (&_cinfo);
_cinfo.src = &_jsrc;
_cinfo.src->init_source = init_source;
_cinfo.src->fill_input_buffer = fill_input_buffer;
_cinfo.src->skip_input_data = mjpeg_skip_input_data;
_cinfo.src->resync_to_restart = jpeg_resync_to_restart;
_cinfo.src->term_source = term_source;
_scanline = new uint8_t[width * 3];
}
MJpegDecoder::~MJpegDecoder()
{
jpeg_destroy_decompress(&_cinfo);
delete [] _scanline;
if (_data) {
delete [] _data;
}
}
void MJpegDecoder::convert_scanline(void)
{
uint32_t *row;
uint32_t c;
uint8_t *s;
unsigned x;
ASSERT(_width % 2 == 0);
ASSERT(_height % 2 == 0);
row = (uint32_t *)(_frame + _y * _stride);
s = _scanline;
if (_back_compat) {
/* We need to check for the old major and for backwards compat
a) swap r and b (done)
b) to-yuv with right values and then from-yuv with old wrong values (TODO)
*/
for (x = 0; x < _width; x++) {
c = s[2] << 16 | s[1] << 8 | s[0];
s += 3;
*row++ = c;
}
} else {
for (x = 0; x < _width; x++) {
c = s[0] << 16 | s[1] << 8 | s[2];
s += 3;
*row++ = c;
}
}
}
void MJpegDecoder::append_data(uint8_t *data, size_t length)
{
uint8_t *new_data;
size_t data_len;
if (length == 0) {
return;
}
if (_data_size - _data_end < length) {
/* Can't fits in tail, need to make space */
data_len = _data_end - _data_start;
if (_data_size - data_len < length) {
/* Can't fit at all, grow a bit */
_data_size = _data_size + length * 2;
new_data = new uint8_t[_data_size];
memcpy (new_data, _data + _data_start, data_len);
delete [] _data;
_data = new_data;
} else {
/* Just needs to compact */
memmove (_data, _data + _data_start, data_len);
}
_data_start = 0;
_data_end = data_len;
}
memcpy (_data + _data_end, data, length);
_data_end += length;
}
bool MJpegDecoder::decode_data(uint8_t *data, size_t length)
{
bool got_picture;
int res;
got_picture = false;
if (_extra_skip > 0) {
if (_extra_skip >= length) {
_extra_skip -= length;
return false;
} else {
data += _extra_skip;
length -= _extra_skip;
_extra_skip = 0;
}
}
if (_data_end - _data_start == 0) {
/* No current data, pass in without copy */
_jsrc.next_input_byte = data;
_jsrc.bytes_in_buffer = length;
} else {
/* Need to combine the new and old data */
append_data(data, length);
_jsrc.next_input_byte = _data + _data_start;
_jsrc.bytes_in_buffer = _data_end - _data_start;
}
switch (_state) {
case STATE_READ_HEADER:
res = jpeg_read_header(&_cinfo, TRUE);
if (res == JPEG_SUSPENDED) {
break;
}
_cinfo.do_fancy_upsampling = FALSE;
_cinfo.do_block_smoothing = FALSE;
_cinfo.out_color_space = JCS_RGB;
PANIC_ON(_cinfo.image_width != _width);
PANIC_ON(_cinfo.image_height != _height);
_state = STATE_START_DECOMPRESS;
/* fall through */
case STATE_START_DECOMPRESS:
res = jpeg_start_decompress (&_cinfo);
if (!res) {
break;
}
_state = STATE_READ_SCANLINES;
/* fall through */
case STATE_READ_SCANLINES:
res = 0;
while (_y < _height) {
res = jpeg_read_scanlines(&_cinfo, &_scanline, 1);
if (res == 0) {
break;
}
convert_scanline();
_y++;
}
if (res == 0) {
break;
}
_state = STATE_FINISH_DECOMPRESS;
/* fall through */
case STATE_FINISH_DECOMPRESS:
res = jpeg_finish_decompress (&_cinfo);
if (!res) {
break;
}
_y = 0;
_state = STATE_READ_HEADER;
got_picture = true;
break;
}
if (_jsrc.next_input_byte == data) {
/* We read directly from the user, store remaining data in
buffer for next time */
size_t read_size = _jsrc.next_input_byte - data;
append_data(data + read_size, length - read_size);
} else {
_data_start = _jsrc.next_input_byte - _data;
_data_end = _data_start + _jsrc.bytes_in_buffer;
}
return got_picture;
}