[Spice-devel] [client v14 22/29] spice-gtk: Enable adding alternative video decoders
Pavel Grunt
pgrunt at redhat.com
Fri May 6 09:53:14 UTC 2016
Hi,
On Wed, 2016-05-04 at 11:44 +0200, Francois Gouget wrote:
> This replaces the original channel-display-mjpeg API with a VideoDecoder
> base class which can be reimplemented by other decoders.
> Furthermore this moves the MJPEG-specific state information from the
> display_stream struct to a derived class of VideoDecoder.
>
> Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
I would just change the prefix from 'spice-gtk' to 'channel-display' or 'video-
streaming' (same applies to other patches)...
Thanks,
Pavel
Acked-by: Pavel Grunt <pgrunt at redhat.com>
> ---
> src/channel-display-mjpeg.c | 130 +++++++++++++++++++++++++++++------------
> ---
> src/channel-display-priv.h | 53 +++++++++++++-----
> src/channel-display.c | 63 ++++++++++-----------
> 3 files changed, 151 insertions(+), 95 deletions(-)
>
> diff --git a/src/channel-display-mjpeg.c b/src/channel-display-mjpeg.c
> index 5ea3371..c7e1c6f 100644
> --- a/src/channel-display-mjpeg.c
> +++ b/src/channel-display-mjpeg.c
> @@ -23,12 +23,33 @@
>
> #include "channel-display-priv.h"
>
> +
> +/* MJpeg decoder implementation */
> +
> +typedef struct MJpegDecoder {
> + VideoDecoder base;
> +
> + /* ---------- The builtin mjpeg decoder ---------- */
> +
> + SpiceMsgIn *frame_msg;
> + struct jpeg_source_mgr mjpeg_src;
> + struct jpeg_decompress_struct mjpeg_cinfo;
> + struct jpeg_error_mgr mjpeg_jerr;
> +
> + /* ---------- Output frame data ---------- */
> +
> + uint8_t *out_frame;
> +} MJpegDecoder;
> +
> +
> +/* ---------- The JPEG library callbacks ---------- */
> +
> static void mjpeg_src_init(struct jpeg_decompress_struct *cinfo)
> {
> - display_stream *st = SPICE_CONTAINEROF(cinfo->src, display_stream,
> mjpeg_src);
> - uint8_t *data;
> + MJpegDecoder *decoder = SPICE_CONTAINEROF(cinfo->src, MJpegDecoder,
> mjpeg_src);
>
> - cinfo->src->bytes_in_buffer = stream_get_current_frame(st, &data);
> + uint8_t *data;
> + cinfo->src->bytes_in_buffer = spice_msg_in_frame_data(decoder->frame_msg,
> &data);
> cinfo->src->next_input_byte = data;
> }
>
> @@ -49,68 +70,57 @@ static void mjpeg_src_term(struct jpeg_decompress_struct
> *cinfo)
> /* nothing */
> }
>
> -G_GNUC_INTERNAL
> -void stream_mjpeg_init(display_stream *st)
> -{
> - st->mjpeg_cinfo.err = jpeg_std_error(&st->mjpeg_jerr);
> - jpeg_create_decompress(&st->mjpeg_cinfo);
> -
> - st->mjpeg_src.init_source = mjpeg_src_init;
> - st->mjpeg_src.fill_input_buffer = mjpeg_src_fill;
> - st->mjpeg_src.skip_input_data = mjpeg_src_skip;
> - st->mjpeg_src.resync_to_restart = jpeg_resync_to_restart;
> - st->mjpeg_src.term_source = mjpeg_src_term;
> - st->mjpeg_cinfo.src = &st->mjpeg_src;
> -}
>
> -G_GNUC_INTERNAL
> -void stream_mjpeg_data(display_stream *st)
> +/* ---------- VideoDecoder's public API ---------- */
> +
> +static uint8_t* mjpeg_decoder_decode_frame(VideoDecoder *video_decoder,
> + SpiceMsgIn *frame_msg)
> {
> - gboolean back_compat = st->channel->priv->peer_hdr.major_version == 1;
> + MJpegDecoder *decoder = (MJpegDecoder*)video_decoder;
> + gboolean back_compat = decoder->base.stream->channel->priv-
> >peer_hdr.major_version == 1;
> int width;
> int height;
> uint8_t *dest;
> uint8_t *lines[4];
>
> - stream_get_dimensions(st, &width, &height);
> - dest = g_malloc0(width * height * 4);
> + decoder->frame_msg = frame_msg;
> + stream_get_dimensions(decoder->base.stream, frame_msg, &width, &height);
> + g_free(decoder->out_frame);
> + dest = decoder->out_frame = g_malloc0(width * height * 4);
>
> - g_free(st->out_frame);
> - st->out_frame = dest;
> -
> - jpeg_read_header(&st->mjpeg_cinfo, 1);
> + jpeg_read_header(&decoder->mjpeg_cinfo, 1);
> #ifdef JCS_EXTENSIONS
> // requires jpeg-turbo
> if (back_compat)
> - st->mjpeg_cinfo.out_color_space = JCS_EXT_RGBX;
> + decoder->mjpeg_cinfo.out_color_space = JCS_EXT_RGBX;
> else
> - st->mjpeg_cinfo.out_color_space = JCS_EXT_BGRX;
> + decoder->mjpeg_cinfo.out_color_space = JCS_EXT_BGRX;
> #else
> #warning "You should consider building with libjpeg-turbo"
> - st->mjpeg_cinfo.out_color_space = JCS_RGB;
> + decoder->mjpeg_cinfo.out_color_space = JCS_RGB;
> #endif
>
> #ifndef SPICE_QUALITY
> - st->mjpeg_cinfo.dct_method = JDCT_IFAST;
> - st->mjpeg_cinfo.do_fancy_upsampling = FALSE;
> - st->mjpeg_cinfo.do_block_smoothing = FALSE;
> - st->mjpeg_cinfo.dither_mode = JDITHER_ORDERED;
> + decoder->mjpeg_cinfo.dct_method = JDCT_IFAST;
> + decoder->mjpeg_cinfo.do_fancy_upsampling = FALSE;
> + decoder->mjpeg_cinfo.do_block_smoothing = FALSE;
> + decoder->mjpeg_cinfo.dither_mode = JDITHER_ORDERED;
> #endif
> // TODO: in theory should check cinfo.output_height match with our height
> - jpeg_start_decompress(&st->mjpeg_cinfo);
> + jpeg_start_decompress(&decoder->mjpeg_cinfo);
> /* rec_outbuf_height is the recommended size of the output buffer we
> * pass to libjpeg for optimum performance
> */
> - if (st->mjpeg_cinfo.rec_outbuf_height > G_N_ELEMENTS(lines)) {
> - jpeg_abort_decompress(&st->mjpeg_cinfo);
> - g_return_if_reached();
> + if (decoder->mjpeg_cinfo.rec_outbuf_height > G_N_ELEMENTS(lines)) {
> + jpeg_abort_decompress(&decoder->mjpeg_cinfo);
> + g_return_val_if_reached(NULL);
> }
>
> - while (st->mjpeg_cinfo.output_scanline < st->mjpeg_cinfo.output_height) {
> + while (decoder->mjpeg_cinfo.output_scanline < decoder-
> >mjpeg_cinfo.output_height) {
> /* only used when JCS_EXTENSIONS is undefined */
> G_GNUC_UNUSED unsigned int lines_read;
>
> - for (unsigned int j = 0; j < st->mjpeg_cinfo.rec_outbuf_height; j++)
> {
> + for (unsigned int j = 0; j < decoder->mjpeg_cinfo.rec_outbuf_height;
> j++) {
> lines[j] = dest;
> #ifdef JCS_EXTENSIONS
> dest += 4 * width;
> @@ -118,8 +128,8 @@ void stream_mjpeg_data(display_stream *st)
> dest += 3 * width;
> #endif
> }
> - lines_read = jpeg_read_scanlines(&st->mjpeg_cinfo, lines,
> - st->mjpeg_cinfo.rec_outbuf_height);
> + lines_read = jpeg_read_scanlines(&decoder->mjpeg_cinfo, lines,
> + decoder->mjpeg_cinfo.rec_outbuf_height);
> #ifndef JCS_EXTENSIONS
> {
> uint8_t *s = lines[0];
> @@ -142,14 +152,44 @@ void stream_mjpeg_data(display_stream *st)
> }
> }
> #endif
> - dest = &st->out_frame[st->mjpeg_cinfo.output_scanline * width * 4];
> + dest = &(decoder->out_frame[decoder->mjpeg_cinfo.output_scanline *
> width * 4]);
> }
> - jpeg_finish_decompress(&st->mjpeg_cinfo);
> + jpeg_finish_decompress(&decoder->mjpeg_cinfo);
> +
> + return decoder->out_frame;
> +}
> +
> +static void mjpeg_decoder_destroy(VideoDecoder* video_decoder)
> +{
> + MJpegDecoder *decoder = (MJpegDecoder*)video_decoder;
> + jpeg_destroy_decompress(&decoder->mjpeg_cinfo);
> + g_free(decoder->out_frame);
> + free(decoder);
> }
>
> G_GNUC_INTERNAL
> -void stream_mjpeg_cleanup(display_stream *st)
> +VideoDecoder* create_mjpeg_decoder(int codec_type, display_stream *stream)
> {
> - jpeg_destroy_decompress(&st->mjpeg_cinfo);
> - g_clear_pointer(&st->out_frame, g_free);
> + g_return_val_if_fail(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG, NULL);
> +
> + MJpegDecoder *decoder = spice_new0(MJpegDecoder, 1);
> +
> + decoder->base.destroy = mjpeg_decoder_destroy;
> + decoder->base.decode_frame = mjpeg_decoder_decode_frame;
> + decoder->base.codec_type = codec_type;
> + decoder->base.stream = stream;
> +
> + decoder->mjpeg_cinfo.err = jpeg_std_error(&decoder->mjpeg_jerr);
> + jpeg_create_decompress(&decoder->mjpeg_cinfo);
> +
> + decoder->mjpeg_src.init_source = mjpeg_src_init;
> + decoder->mjpeg_src.fill_input_buffer = mjpeg_src_fill;
> + decoder->mjpeg_src.skip_input_data = mjpeg_src_skip;
> + decoder->mjpeg_src.resync_to_restart = jpeg_resync_to_restart;
> + decoder->mjpeg_src.term_source = mjpeg_src_term;
> + decoder->mjpeg_cinfo.src = &decoder->mjpeg_src;
> +
> + /* All the other fields are initialized to zero by spice_new0(). */
> +
> + return (VideoDecoder*)decoder;
> }
> diff --git a/src/channel-display-priv.h b/src/channel-display-priv.h
> index f92477b..7f1c520 100644
> --- a/src/channel-display-priv.h
> +++ b/src/channel-display-priv.h
> @@ -34,6 +34,39 @@
>
> G_BEGIN_DECLS
>
> +typedef struct display_stream display_stream;
> +
> +typedef struct VideoDecoder VideoDecoder;
> +struct VideoDecoder {
> + /* Releases the video decoder's resources */
> + void (*destroy)(VideoDecoder *decoder);
> +
> + /* Decompresses the specified frame.
> + *
> + * @decoder: The video decoder.
> + * @frame_msg: The Spice message containing the compressed frame.
> + * @return: A pointer to the buffer holding the decoded frame. This
> + * buffer will be invalidated by the next call to
> + * decode_frame().
> + */
> + uint8_t* (*decode_frame)(VideoDecoder *decoder, SpiceMsgIn *frame_msg);
> +
> + /* The format of the encoded video. */
> + int codec_type;
> +
> + /* The associated display stream. */
> + display_stream *stream;
> +};
> +
> +
> +/* Instantiates the video decoder for the specified codec.
> + *
> + * @codec_type: The format of the video.
> + * @stream: The associated video stream.
> + * @return: A pointer to a structure implementing the VideoDecoder
> methods.
> + */
> +VideoDecoder* create_mjpeg_decoder(int codec_type, display_stream *stream);
> +
>
> typedef struct display_surface {
> guint32 surface_id;
> @@ -53,24 +86,18 @@ typedef struct drops_sequence_stats {
> uint32_t duration;
> } drops_sequence_stats;
>
> -typedef struct display_stream {
> +struct display_stream {
> SpiceMsgIn *msg_create;
> SpiceMsgIn *msg_clip;
> - SpiceMsgIn *msg_data;
>
> /* from messages */
> display_surface *surface;
> SpiceClip *clip;
> QRegion region;
> int have_region;
> - int codec;
>
> - /* mjpeg decoder */
> - struct jpeg_source_mgr mjpeg_src;
> - struct jpeg_decompress_struct mjpeg_cinfo;
> - struct jpeg_error_mgr mjpeg_jerr;
> + VideoDecoder *video_decoder;
>
> - uint8_t *out_frame;
> GQueue *msgq;
> guint timeout;
> SpiceChannel *channel;
> @@ -97,15 +124,11 @@ typedef struct display_stream {
> uint32_t report_num_frames;
> uint32_t report_num_drops;
> uint32_t report_drops_seq_len;
> -} display_stream;
> +};
>
> -void stream_get_dimensions(display_stream *st, int *width, int *height);
> -uint32_t stream_get_current_frame(display_stream *st, uint8_t **data);
> +void stream_get_dimensions(display_stream *st, SpiceMsgIn *frame_msg, int
> *width, int *height);
> +uint32_t spice_msg_in_frame_data(SpiceMsgIn *frame_msg, uint8_t **data);
>
> -/* channel-display-mjpeg.c */
> -void stream_mjpeg_init(display_stream *st);
> -void stream_mjpeg_data(display_stream *st);
> -void stream_mjpeg_cleanup(display_stream *st);
>
> G_END_DECLS
>
> diff --git a/src/channel-display.c b/src/channel-display.c
> index b6c79e0..c681a4e 100644
> --- a/src/channel-display.c
> +++ b/src/channel-display.c
> @@ -1086,7 +1086,6 @@ static void display_handle_stream_create(SpiceChannel
> *channel, SpiceMsgIn *in)
> st->msg_create = in;
> spice_msg_in_ref(in);
> st->clip = &op->clip;
> - st->codec = op->codec_type;
> st->surface = find_surface(c, op->surface_id);
> st->msgq = g_queue_new();
> st->channel = channel;
> @@ -1095,10 +1094,15 @@ static void display_handle_stream_create(SpiceChannel
> *channel, SpiceMsgIn *in)
> region_init(&st->region);
> display_update_stream_region(st);
>
> - switch (st->codec) {
> + switch (op->codec_type) {
> case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> - stream_mjpeg_init(st);
> + st->video_decoder = create_mjpeg_decoder(op->codec_type, st);
> break;
> + default:
> + st->video_decoder = NULL;
> + }
> + if (st->video_decoder == NULL) {
> + spice_printerr("could not create a video decoder for codec %d", op-
> >codec_type);
> }
> }
>
> @@ -1141,15 +1145,15 @@ static gboolean display_stream_schedule(display_stream
> *st)
> return FALSE;
> }
>
> -static SpiceRect *stream_get_dest(display_stream *st)
> +static SpiceRect *stream_get_dest(display_stream *st, SpiceMsgIn *frame_msg)
> {
> - if (st->msg_data == NULL ||
> - spice_msg_in_type(st->msg_data) !=
> SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
> + if (frame_msg == NULL ||
> + spice_msg_in_type(frame_msg) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED)
> {
> SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st-
> >msg_create);
>
> return &info->dest;
> } else {
> - SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st-
> >msg_data);
> + SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(frame_msg);
>
> return &op->dest;
> }
> @@ -1164,21 +1168,16 @@ static uint32_t stream_get_flags(display_stream *st)
> }
>
> G_GNUC_INTERNAL
> -uint32_t stream_get_current_frame(display_stream *st, uint8_t **data)
> +uint32_t spice_msg_in_frame_data(SpiceMsgIn *frame_msg, uint8_t **data)
> {
> - if (st->msg_data == NULL) {
> - *data = NULL;
> - return 0;
> - }
> -
> - switch (spice_msg_in_type(st->msg_data)) {
> + switch (spice_msg_in_type(frame_msg)) {
> case SPICE_MSG_DISPLAY_STREAM_DATA: {
> - SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(st->msg_data);
> + SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(frame_msg);
> *data = op->data;
> return op->data_size;
> }
> case SPICE_MSG_DISPLAY_STREAM_DATA_SIZED: {
> - SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st-
> >msg_data);
> + SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(frame_msg);
> *data = op->data;
> return op->data_size;
> }
> @@ -1189,19 +1188,19 @@ uint32_t stream_get_current_frame(display_stream *st,
> uint8_t **data)
> }
>
> G_GNUC_INTERNAL
> -void stream_get_dimensions(display_stream *st, int *width, int *height)
> +void stream_get_dimensions(display_stream *st, SpiceMsgIn *frame_msg, int
> *width, int *height)
> {
> g_return_if_fail(width != NULL);
> g_return_if_fail(height != NULL);
>
> - if (st->msg_data == NULL ||
> - spice_msg_in_type(st->msg_data) !=
> SPICE_MSG_DISPLAY_STREAM_DATA_SIZED) {
> + if (frame_msg == NULL ||
> + spice_msg_in_type(frame_msg) != SPICE_MSG_DISPLAY_STREAM_DATA_SIZED)
> {
> SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st-
> >msg_create);
>
> *width = info->stream_width;
> *height = info->stream_height;
> } else {
> - SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(st-
> >msg_data);
> + SpiceMsgDisplayStreamDataSized *op = spice_msg_in_parsed(frame_msg);
>
> *width = op->width;
> *height = op->height;
> @@ -1219,24 +1218,21 @@ static gboolean display_stream_render(display_stream
> *st)
>
> g_return_val_if_fail(in != NULL, FALSE);
>
> - st->msg_data = in;
> - switch (st->codec) {
> - case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> - stream_mjpeg_data(st);
> - break;
> + uint8_t *out_frame = NULL;
> + if (st->video_decoder) {
> + out_frame = st->video_decoder->decode_frame(st->video_decoder,
> in);
> }
> -
> - if (st->out_frame) {
> + if (out_frame) {
> int width;
> int height;
> SpiceRect *dest;
> uint8_t *data;
> int stride;
>
> - stream_get_dimensions(st, &width, &height);
> - dest = stream_get_dest(st);
> + stream_get_dimensions(st, in, &width, &height);
> + dest = stream_get_dest(st, in);
>
> - data = st->out_frame;
> + data = out_frame;
> stride = width * sizeof(uint32_t);
> if (!(stream_get_flags(st) & SPICE_STREAM_FLAGS_TOP_DOWN)) {
> data += stride * (height - 1);
> @@ -1259,7 +1255,6 @@ static gboolean display_stream_render(display_stream
> *st)
> dest->bottom - dest->top);
> }
>
> - st->msg_data = NULL;
> spice_msg_in_unref(in);
>
> in = g_queue_peek_head(st->msgq);
> @@ -1568,10 +1563,8 @@ static void destroy_stream(SpiceChannel *channel, int
> id)
>
> g_array_free(st->drops_seqs_stats_arr, TRUE);
>
> - switch (st->codec) {
> - case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> - stream_mjpeg_cleanup(st);
> - break;
> + if (st->video_decoder) {
> + st->video_decoder->destroy(st->video_decoder);
> }
>
> if (st->msg_clip)
More information about the Spice-devel
mailing list