mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
There was an error in how this was encoded in 0.4, which we need to handle. There is still some issues with the old streams as the luminocity handling in 0.4 was not correct.
275 lines
6.8 KiB
C++
275 lines
6.8 KiB
C++
/* -*- 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef WIN32
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include "debug.h"
|
|
#include "utils.h"
|
|
#include "mjpeg_decoder.h"
|
|
|
|
enum {
|
|
STATE_READ_HEADER,
|
|
STATE_START_DECOMPRESS,
|
|
STATE_READ_SCANLINES,
|
|
STATE_FINISH_DECOMPRESS
|
|
};
|
|
|
|
extern "C" {
|
|
|
|
static void init_source(j_decompress_ptr cinfo)
|
|
{
|
|
}
|
|
|
|
static 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;
|
|
int 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;
|
|
}
|