[Spice-devel] [spice v15 01/21] mjpeg: Use src_area as the authoritative source for the frame dimensions

Francois Gouget fgouget at codeweavers.com
Tue May 31 18:48:32 UTC 2016


On Tue, 31 May 2016, Christophe Fergeau wrote:

> On Thu, May 26, 2016 at 05:15:35PM +0200, Francois Gouget wrote:
> > Video frames correspond to QXL_DRAW_COPY operations where the frame area
> > is defined by the SpiceCopy.src_area field.
> > 
> > Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
> > ---
> >  server/mjpeg-encoder.c | 14 ++++++--------
> >  1 file changed, 6 insertions(+), 8 deletions(-)
> > 
> > diff --git a/server/mjpeg-encoder.c b/server/mjpeg-encoder.c
> > index e3646db..7dcea50 100644
> > --- a/server/mjpeg-encoder.c
> > +++ b/server/mjpeg-encoder.c
> > @@ -706,7 +706,7 @@ static void mjpeg_encoder_adjust_fps(MJpegEncoder *encoder, uint64_t now)
> >   */
> >  static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
> >                                       SpiceBitmapFmt format,
> > -                                     int width, int height,
> > +                                     const SpiceRect *src,
> >                                       uint8_t **dest, size_t *dest_len,
> >                                       uint32_t frame_mm_time)
> >  {
> > @@ -777,10 +777,12 @@ static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
> >          return VIDEO_ENCODER_FRAME_UNSUPPORTED;
> >      }
> >  
> > +    encoder->cinfo.image_width = src->right - src->left;
> > +    encoder->cinfo.image_height = src->bottom - src->top;
> 
> I don't think this is correct in the !SIZED_STREAM case. dcc-send.c has:
> 
>     if (drawable->sized_stream) {
>         if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_SIZED_STREAM)) {
>             SpiceRect *src_rect = &drawable->red_drawable->u.copy.src_area;
> 
>             width = src_rect->right - src_rect->left;
>             height = src_rect->bottom - src_rect->top;
>         } else {
>             return FALSE;
>         }
>     } else {
>         width = stream->width;
>         height = stream->height;
>     }
> 
> so if the client has SPICE_DISPLAY_CAP_SIZED_STREAM, we can use src_rect.
> However, in the !CAP_SIZED_STREAM case, we are not using the size of the current bitmap
> during encoding, but rather the initial size of the stream. Ie I'm worried that
> with this simplification, the stream we are sending is not of the right resolution in
> the !CAP_SIZED_STREAM case.


There are four parameters to consider:
* The stream's initial width / height.

* The stream's initial dest_area.

* The width / height of the RedDrawable.u.copy.src_area.
  This value can only be sent to CAP_SIZED_STREAM clients.

* The RedDrawable.bbox.
  This value can only be sent to CAP_SIZED_STREAM clients.
  In theory this could be different from the SpiceCopy.src_area in 
  which case the client will perform the scaling. This could happen if 
  the X server / QXL driver exposes some xv like scaling capabilities to 
  the application.
  But in practice I have never seen it be different from 
  SpiceCopy.src_area.


So assuming a !CAP_SIZED_STREAM client, an initial stream size of 
300x200 and a current src_area of 320x220, telling the video encoder to 
encode a 300x200 area is wrong. It will lead to a 20 pixel band to the 
right and bottom not getting refreshed, resulting in artifacts.

The question is whether that can happen. In both the old and new code 
the answer hinges on whether drawable->sized_stream is a reliable 
indicator of a frame with a size different from the stream width / 
height.


To answer that question it's necessary to figure out how 
is_next_stream_frame() determines whether drawable->stream or 
drawable->sized_stream will eventually be set in 
red_marshall_stream_data().

But first note that only one of the calls to is_next_stream_frame() in 
stream_trace_update() and stream_maintenance() matter. The reason is 
that the one based on the ItemTrace structure corresponds to the case 
where the stream has not been created yet. It's there to validate each 
would-be new stream frame until we have seen enough good candidates to 
decide to create the stream. Since the stream does not exist yet we can 
ignore these calls.

As a result all the calls we care about have container_candidate_allowed 
== TRUE. The reason for this is that there is one Stream object but 
potentially multiple clients, some of which may have CAP_SIZED_STREAM, 
and some of which may not. So is_next_stream_frame() just checks whether 
the current frame is acceptable for CAP_SIZED_STREAM clients and it's up 
to red_marshall_stream_data() to reject frames that are unsuitable for 
!CAP_SIZED_STREAM clients.

So it turns out that for our purposes is_next_stream_frame() 
distinguishes between regular and extra large frames only based on 
RedDrawable.bbox.

It does not check whether the src_area width / height matches the 
initial stream width / height. So in theory red_marshall_stream_data() 
could get drawable->sized_stream == NULL in that case. However that 
would mean that the src_area and bbox have different sizes which as I 
mentioned before does not seem to happen.

It should still be fixed though. I see two ways to do that:

1) Add the missing check to is_next_stream_frame().
   Pros:
   - The check is done only once no matter how many clients are 
     connected.
   Cons:
   - The check is far from where it matters one should just trust 
     sized_stream.

2) Add the missing check to red_marshall_stream_data().
   Pros:
   - The check is done in the only place where it matters.
   - Drawable.sized_stream becomes useless so it can be removed which 
     simplifies the code.
   Cons:
   - The (computationally simple) check gets done for each client (which 
     99.9% of the time means once).

Option 2 is more invasive but I think it will be nicer in the end.


-- 
Francois Gouget <fgouget at codeweavers.com>


More information about the Spice-devel mailing list