В последнее время я работал над простой программой для демонстрации экрана.

На самом деле программа работает на TCP protocol и использует Desktop duplication API - отличный сервис, который поддерживает очень быстрый захват экрана, а также предоставляет информацию о MovedRegions (областях, которые только изменили свои положение на экране, но все еще существует) и UpdatedRegions (измененные области).

Дублирование рабочего стола имеет два важных свойства: 2-байтовые массивы , массив для previouspixels и NewPixels массив. Каждые 4 байта представляют пиксель в форме RGBA , поэтому, например, если мой экран имеет разрешение 1920 x 1080, размер буфера составляет 1920 x 1080 * 4.

Ниже приведены важные моменты моей стратегии

  1. В исходном состоянии (первый раз) отправляю весь буфер пикселей (в моем случае это 1920 x 1080 * 3) - альфа-составляющая на экранах всегда 255 :)
  2. С этого момента я перебираю UpdatedRegions (это массив прямоугольников) и отправляю границы регионов и Xo'r пиксели в нем примерно так:

        writer.Position = 0;
        var n = frame._newPixels;
        var w = 1920 * 4; //frame boundaries.
        var p = frame._previousPixels;
        foreach (var region in frame.UpdatedRegions)
            {
                writer.WriteInt(region.Top);
                writer.WriteInt(region.Height);
                writer.WriteInt(region.Left);
                writer.WriteInt(region.Width);
                for (int y = region.Top, yOffset = y * w; y < region.Bottom; y++, yOffset += w)
                {
                    for (int x = region.Left, xOffset = x * 4, i = yOffset + xOffset; x < region.Right; x++, i += 4)
                    {
                        writer.WriteByte(n[i] ^ p[i]); //'n' is the newpixels buffer and 'p' is the previous.xoring for differences.
                        writer.WriteByte(n[i+1] ^ p[i+1]);
                        writer.WriteByte(n[i + 2] ^ p[i + 2]);
    
                    }
                }
            }
    
  3. Я сжимаю буфер с помощью оболочки lz4, написанной на C # (см. lz4.NET@github). Затем я записываю данные в NetworkStream.
  4. Я объединяю области на приемной стороне, чтобы получить обновленное изображение - сегодня это не наша проблема :)

Writer - это экземпляр класса QuickBinaryWriter, который я написал (просто для повторного использования того же буфера снова).

    public class QuickBinaryWriter
{
    private readonly byte[] _buffer;
    private int _position;

    public QuickBinaryWriter(byte[] buffer)
    {
        _buffer = buffer;
    }

    public int Position
    {
        get { return _position; }
        set { _position = value; }
    }

    public void WriteByte(byte value)
    {
        _buffer[_position++] = value;
    }


    public void WriteInt(int value)
    {

        byte[] arr = BitConverter.GetBytes(value);
        for (int i = 0; i < arr.Length; i++)
            WriteByte(arr[i]);
    }

}

По многим параметрам я видел, что отправленные данные действительно огромны, и иногда для обновления одного кадра данные могут достигать 200 КБ (после сжатия!). Давайте будем честными: 200 КБ - это действительно ничто, но если я хочу плавно транслировать экран и иметь возможность смотреть с высокой скоростью, мне придется немного поработать над этим - чтобы минимизировать сетевой трафик и использование полосы пропускания .

Я ищу предложения и творческие идеи для повышения эффективности программы - в основном данные, отправленные по сетевой части (путем их упаковки другими способами или любой другой идеей), я буду признателен за любую помощь и идеи. Спасибо.

18
Slashy 22 Дек 2015 в 20:33

2 ответа

Лучший ответ

Для вашего экрана с разрешением 1920 x 1080 и 4-байтовым цветом вам нужно примерно 8 МБ на кадр. При 20 FPS у вас 160 МБ / с. Так что увеличение с 8 МБ до 200 КБ (4 МБ / с при 20 кадрах в секунду) - большое улучшение.

Я хотел бы привлечь ваше внимание к некоторым аспектам, на которых, я не уверен, вы сосредоточены, и, надеюсь, это поможет.

  1. Чем сильнее вы сжимаете изображение на экране, тем больше ему потребуется обработки.
  2. На самом деле вам нужно сосредоточиться на механизмах сжатия, разработанных для серий непрерывно меняющихся изображений, подобных видеокодекам (хотя и без звука). Например: H.264
  3. Помните, что вам нужно использовать какой-то протокол реального времени для передачи ваших данных. Идея заключается в том, что если один из ваших кадров доходит до конечной машины с задержкой, вы можете также отбросить следующие несколько кадров, чтобы сыграть в догонялки. Иначе вы будете постоянно отстать, что, я сомневаюсь, понравится пользователям.
  4. Вы всегда можете пожертвовать качеством ради производительности. Самый простой такой механизм, который вы видите в аналогичных технологиях (например, удаленный рабочий стол MS, VNC и т. Д.), - это отправка 8-битного цвета (каждый из 2 битов ARGB) вместо 3-байтового цвета, который вы используете.
  5. Другой способ улучшить вашу ситуацию - сосредоточиться на определенном прямоугольнике на экране, который вы хотите транслировать, вместо того, чтобы транслировать весь рабочий стол. Это уменьшит размер самой рамки.
  6. Другой способ - масштабировать изображение на экране до меньшего размера перед передачей, а затем масштабировать его до нормального состояния перед отображением.
  7. После отправки начального экрана вы всегда можете отправить разницу между newpixels и previouspixels. Излишне говорить, что исходный экран и экран различий будут сжаты / распакованы LZ4. Время от времени вы должны отправлять полный массив вместо diff, если вы используете какой-либо алгоритм с потерями для сжатия diff.
  8. Есть ли у UpdatedRegions области перекрытия? Можно ли оптимизировать это, чтобы не отправлять дублирующуюся информацию о пикселях?

Вышеупомянутые идеи можно применять друг к другу, чтобы улучшить взаимодействие с пользователем. В конечном итоге это зависит от специфики вашего приложения и конечных пользователей.

РЕДАКТИРОВАТЬ:

18
Vikhram 31 Дек 2015 в 12:42

Slashy ,

Поскольку вы используете кадры с высоким разрешением и хотите иметь хорошую частоту кадров, вы, вероятно, будете смотреть на кодировку H.264. Я поработал с вещательным видео HD / SDI, которое полностью зависит от H.264, и немного перехожу на H.265. Большинство библиотек, используемых в вещании, написаны на C ++ для скорости.

Я бы посоветовал посмотреть что-то вроде этого https://msdn.microsoft.com/en-us/library/windows/desktop/dd797816 (v = vs.85) .aspx

1
Aaron Thomas 24 Дек 2015 в 17:51