3
\$\begingroup\$

I've been developing a small screen sharing project for the last months, it's a desktop application, pct protocol based. I'm going to show the logic and the code and I hope to get reviews. I would like to hear every suggestion.

First of all, I'm using a service called Desktop Duplication API. Here's the C# implementation.

Using this, allows me a rapid access to the screen buffer and access the dirty regions of the screen(Rectangle which gets updated and changed) so I don't have to handle 2 shots (previous and current bitmaps) and scan them for differences.

After using the desktop API for a while, I noticed that the DirtyRegions aren't exactly pure DirtyRegions. These rectangles contained way more data than they supposed to have. (that's probably related to the desktop refreshing.. I'm not sure why). For example, if I just opened a small video, and few pixels moved in the video sometimes it would even give me the entire video player bounds and even more!

I noticed that it had a massive effect on the network traffic size so I decided to write a small algorithm to find the exact changed bounds for each region itself. (Actually I rely on the DirtyRegions property, I don't have to scan the whole bitmap area, but each region scan is necessary to prevent sending extra unnecessary data.)

After I found a change, I just crop(using the changed rectangle bounds) from the current desktop bitmap (the full one), compress it as jpeg, and send.

This is the Differences finding code:

 private unsafe Rectangle GetBoundingBoxForChanges(Bitmap _prevBitmap, Bitmap _newBitmap)
    {


        BitmapData bmNewData = _newBitmap.LockBits(
            new Rectangle(0, 0, _newBitmap.Width, _newBitmap.Height),
            ImageLockMode.ReadOnly, _newBitmap.PixelFormat);
        BitmapData bmPrevData = _prevBitmap.LockBits(
            new Rectangle(0, 0, _prevBitmap.Width, _prevBitmap.Height),
            ImageLockMode.ReadOnly, _prevBitmap.PixelFormat);

        var strideNew = bmNewData.Width;
        var scanNew0 = bmNewData.Scan0;
        var scanPrev0 = bmPrevData.Scan0;

            foreach (var region in frame.UpdatedRegions)
            {
                var width = region.Width;
                var height = region.Height;
                var left = width;
                var right = 0;
                var top = height;
                var bottom = 0;

                var pNew = (int*)scanNew0.ToPointer();
                var pPrev = (int*)scanPrev0.ToPointer();
                for (var y = 0; y < height; ++y)
                {
                    // For pixels up to the current bound (left to right)
                    //
                    for (var x = 0; x < left; ++x)
                    {
                        // Use pointer arithmetic to index the
                        //  next pixel in this row.
                        //
                        if ((pNew + x)[0] != (pPrev + x)[0])
                        {
                            // Found a change.
                            //
                            if (x < left)
                            {
                                left = x;
                            }
                            if (x > right)
                            {
                                right = x;
                            }
                            if (y < top)
                            {
                                top = y;
                            }
                            if (y > bottom)
                            {
                                bottom = y;
                            }
                        }
                    }

                    // Move the pointers to the next row.
                    //
                    pNew += strideNew;
                    pPrev += strideNew;
                }


            // Second Pass - The first pass found at
            //    least one different pixel and has set
            //    the left & top bounds. In addition, the
            //    right & bottom bounds have been initialized.
            //    Adapt the number of pixels scanned from right
            //    to left so we only scan up to the current bound.
            //    In addition, there is no need to scan past
            //    the top bound.
            //

            // Set the pointers to the first element of the
            //    bottom row.
            //
            pNew = (int*)(void*)scanNew0;
            pPrev = (int*)(void*)scanPrev0;
            pNew += (_newBitmap.Height - 1) * strideNew;
            pPrev += (_prevBitmap.Height - 1) * strideNew;

            // For each row (bottom to top)
            //
            for (int y = _newBitmap.Height - 1; y > top; y--)
            {
                // For each column (right to left)
                //
                for (int x = _newBitmap.Width - 1; x > right; x--)
                {
                    // Use pointer arithmetic to index the
                    //    next pixel in this row.
                    //
                    if ((pNew + x)[0] != (pPrev + x)[0])
                    {
                        // Found a change.
                        //
                        if (x > right)
                        {
                            right = x;
                        }
                        if (y > bottom)
                        {
                            bottom = y;
                        }
                    }


                    pNew -= strideNew;
                    pPrev -= strideNew;
                }
            }
                var diffImgWidth = right - left + 1;
                var diffImgHeight = bottom - top + 1;
                if (diffImgHeight < 0 || diffImgWidth < 0)
                {
                    // Nothing changed
                    continue;

                }

                // Return the bounding box.
                //

        _newBitmap.UnlockBits(bmNewData);
        _prevBitmap.UnlockBits(bmPrevData);
                Rectangle rect= new Rectangle(left, top, diffImgWidth, diffImgHeight);
                return rect;
            }

In the second side, the client receives these blocks and merges them on the initial big bitmap -- and this is how the updated screen displayed.

I did many benchamrks, for the differences processing part, the .jpeg compressing, the processing of the bitmap in the client side(and the drawing to the screen part), all these operation were extremely quick, running this project in my LAN network was very very fast.

This all lead me to one simple conclusion, the main bottleneck here is the network part. When there's no massive change on the screen there won't even be a network traffic, but when there are big changes on the screen, each frame size (after the jpeg compression) could get to 150Kb!

I'm looking for more ways or techniques to improve the efficiency of the project since it's kind of a real time project, it has to work quite fast.

Things I already do in order to achieve this:

  • Reusing buffer and bitmap object in purpose of prevent their allocation.
  • Disabling Windows Aero effects in the server screen (which gets captured).
  • Scanning for differences and sending them only.

I'm looking for ways to reduce the network traffic (mostly).

I've been suggested to move to UDP and didn't actually understand why and how (is it promised faster transmission of the data? and doesn't it have a buffer size limit? last time I tried I was unable to send larger buffer than 65k).

I'm not about to reinvent the wheel or something. This is only a small project I develop for myself.

\$\endgroup\$
1
\$\begingroup\$

Main loop

The last part of the main loop doesn't look right to me.

_newBitmap.UnlockBits(bmNewData);
_prevBitmap.UnlockBits(bmPrevData);
Rectangle rect = new Rectangle(left, top, diffImgWidth, diffImgHeight);
return rect;

Releasing the resources inside the loop might be dangerous becasue if you change your algorithm you might release them prematurely and run into exception.

Consider using a try/finally which more clearly shows what you are doing:

BitmapData bmNewData = _newBitmap.LockBits(...);
BitmapData bmPrevData = _prevBitmap.LockBits(...);

try
{
    for (...)
    {
        Rectangle rect = new Rectangle(left, top, diffImgWidth, diffImgHeight);
        return rect;
    }
}
finally
{
    _newBitmap.UnlockBits(bmNewData);
    _prevBitmap.UnlockBits(bmPrevData);

}

this also guarantees that the resources will be released in case of an exception.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.