summaryrefslogtreecommitdiffstats
path: root/client/mjpeg_decoder.cpp
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2010-04-07 10:42:57 +0200
committerAlexander Larsson <alexl@redhat.com>2010-04-08 11:30:18 +0200
commit5059c304be3aeae6bf8c8b201f844afa77cf1323 (patch)
treead1ca8cb21f5f2f06aae6145347e8e1da86770cd /client/mjpeg_decoder.cpp
parented568302ad62db7eb8cc52961484c227a401f14e (diff)
downloadspice-5059c304be3aeae6bf8c8b201f844afa77cf1323.tar.gz
spice-5059c304be3aeae6bf8c8b201f844afa77cf1323.tar.xz
spice-5059c304be3aeae6bf8c8b201f844afa77cf1323.zip
Use libjpeg to decode mjpegs, not ffmpeg
This is pretty straightforward, although there are two weird issues. The current encoder has two bugs in the yuv conversion. First of all it switches red and blue, due to something of an endianness issue. We keep this behavior by switching red and blue. Maybe we want to change this in the new protocol version since switching this may cause jpeg compression to be worse. Secondly, the old coder/decoder did rgb to/from yuv420 wrongly for jpeg, not using the "full scale" version of Y that is used in jpeg, but the other one where y goes from 16 to 235. (See jpeg/jfif reference on http://en.wikipedia.org/wiki/YCbCr for details.) The new decoder uses the full range in order to get better quality, which means old encoders will show slightly darker images. This completely removes all ffmpeg usage in the client
Diffstat (limited to 'client/mjpeg_decoder.cpp')
-rw-r--r--client/mjpeg_decoder.cpp236
1 files changed, 236 insertions, 0 deletions
diff --git a/client/mjpeg_decoder.cpp b/client/mjpeg_decoder.cpp
new file mode 100644
index 00000000..21804fb4
--- /dev/null
+++ b/client/mjpeg_decoder.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2010 Red Hat, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. 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) :
+ _data(NULL)
+ , _data_size(0)
+ , _data_start(0)
+ , _data_end(0)
+ , _extra_skip(0)
+ , _width(width)
+ , _height(height)
+ , _stride(stride)
+ , _frame(frame)
+ , _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()
+{
+ 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;
+ for (x = 0; x < _width; x++) {
+ /* This switches the order of red and blue.
+ This is not accidental, but for backwards
+ compatibility, since a bug in the old
+ ffmpeg-using mjpeg code got these switched
+ due to endianness issues. */
+ c = s[0] | s[1] << 8 | s[2] << 16;
+ s += 3;
+ *row++ = c;
+ }
+}
+
+bool MJpegDecoder::decode_data(uint8_t *data, size_t length)
+{
+ uint8_t *new_data;
+ size_t data_len;
+ int res;
+ bool got_picture;
+
+ 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_size - _data_end < length) {
+ /* Can't fit in tail */
+
+ 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;
+
+ _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;
+ }
+
+ _data_start = _jsrc.next_input_byte - _data;
+ _data_end = _data_start + _jsrc.bytes_in_buffer;
+
+ return got_picture;
+}