/* main context */
static gboolean display_frame(gpointer video_decoder)
{
    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
    SpiceGstFrame *gstframe;
    GstCaps *caps;
    gint width, height;
    GstStructure *s;
    GstBuffer *buffer;
    GstMapInfo mapinfo;

    g_mutex_lock(&decoder->queues_mutex);
    decoder->timer_id = 0;
    gstframe = g_queue_pop_head(decoder->display_queue);
    g_mutex_unlock(&decoder->queues_mutex);
    /* If the queue is empty we don't even need to reschedule */
    g_return_val_if_fail(gstframe, G_SOURCE_REMOVE);

    if (!gstframe->sample) {
        spice_warning("got a frame without a sample!");
        goto error;
    }

    caps = gst_sample_get_caps(gstframe->sample);
    if (!caps) {
        spice_warning("GStreamer error: could not get the caps of the sample");
        goto error;
    }

    s = gst_caps_get_structure(caps, 0);
    if (!gst_structure_get_int(s, "width", &width) ||
        !gst_structure_get_int(s, "height", &height)) {
        spice_warning("GStreamer error: could not get the size of the frame");
        goto error;
    }

    buffer = gst_sample_get_buffer(gstframe->sample);
    if (!gst_buffer_map(buffer, &mapinfo, GST_MAP_READ)) {
        spice_warning("GStreamer error: could not map the buffer");
        goto error;
    }

    stream_display_frame(decoder->base.stream, gstframe->frame,
                         width, height, mapinfo.data);
    gst_buffer_unmap(buffer, &mapinfo);

 error:
    free_gst_frame(gstframe);
    schedule_frame(decoder);
    return G_SOURCE_REMOVE;
}
/* main context */
static gboolean mjpeg_decoder_decode_frame(gpointer video_decoder)
{
    MJpegDecoder *decoder = (MJpegDecoder*)video_decoder;
    gboolean back_compat = decoder->base.stream->channel->priv->peer_hdr.major_version == 1;
    JDIMENSION width, height;
    uint8_t *dest;
    uint8_t *lines[4];

    jpeg_read_header(&decoder->mjpeg_cinfo, 1);
    width = decoder->mjpeg_cinfo.image_width;
    height = decoder->mjpeg_cinfo.image_height;
    if (decoder->out_size < width * height * 4) {
        g_free(decoder->out_frame);
        decoder->out_size = width * height * 4;
        decoder->out_frame = g_malloc(decoder->out_size);
    }
    dest = decoder->out_frame;

#ifdef JCS_EXTENSIONS
    // requires jpeg-turbo
    if (back_compat)
        decoder->mjpeg_cinfo.out_color_space = JCS_EXT_RGBX;
    else
        decoder->mjpeg_cinfo.out_color_space = JCS_EXT_BGRX;
#else
#warning "You should consider building with libjpeg-turbo"
    decoder->mjpeg_cinfo.out_color_space = JCS_RGB;
#endif

#ifndef SPICE_QUALITY
    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(&decoder->mjpeg_cinfo);
    /* rec_outbuf_height is the recommended size of the output buffer we
     * pass to libjpeg for optimum performance
     */
    if (decoder->mjpeg_cinfo.rec_outbuf_height > G_N_ELEMENTS(lines)) {
        jpeg_abort_decompress(&decoder->mjpeg_cinfo);
        g_return_val_if_reached(G_SOURCE_REMOVE);
    }

    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 < decoder->mjpeg_cinfo.rec_outbuf_height; j++) {
            lines[j] = dest;
#ifdef JCS_EXTENSIONS
            dest += 4 * width;
#else
            dest += 3 * width;
#endif
        }
        lines_read = jpeg_read_scanlines(&decoder->mjpeg_cinfo, lines,
                                decoder->mjpeg_cinfo.rec_outbuf_height);
#ifndef JCS_EXTENSIONS
        {
            uint8_t *s = lines[0];
            uint32_t *d = SPICE_ALIGNED_CAST(uint32_t *, s);

            if (back_compat) {
                for (unsigned int j = lines_read * width; j > 0; ) {
                    j -= 1; // reverse order, bad for cache?
                    d[j] = s[j * 3 + 0] |
                        s[j * 3 + 1] << 8 |
                        s[j * 3 + 2] << 16;
                }
            } else {
                for (unsigned int j = lines_read * width; j > 0; ) {
                    j -= 1; // reverse order, bad for cache?
                    d[j] = s[j * 3 + 0] << 16 |
                        s[j * 3 + 1] << 8 |
                        s[j * 3 + 2];
                }
            }
        }
#endif
        dest = &(decoder->out_frame[decoder->mjpeg_cinfo.output_scanline * width * 4]);
    }
    jpeg_finish_decompress(&decoder->mjpeg_cinfo);

    /* Display the frame and dispose of it */
    stream_display_frame(decoder->base.stream, decoder->cur_frame,
                         width, height, SPICE_UNKNOWN_STRIDE, decoder->out_frame);
    free_spice_frame(decoder->cur_frame);
    decoder->cur_frame = NULL;
    decoder->timer_id = 0;

    /* Schedule the next frame */
    mjpeg_decoder_schedule(decoder);

    return G_SOURCE_REMOVE;
}