С# Экранная потоковая программа

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

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

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

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

  • В начальном состоянии (в первый раз) я посылаю весь буфер пикселя (в моем случае это 1920 x 1080 * 3) - на экранах всегда присутствует альфа-компонент:)
  • Отныне я перебираю обновленные области (это массив прямоугольников), и я посылаю области границ и 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]);
    
                    }
                }
            }
    
  • я Сжатие буфера с использованием lz4-оболочки, написанной в С# (см. [email protected]). Затем я записываю данные в NetworkStream.
  • Я объединять области в стороне получателя, чтобы получить обновленное изображение - сегодня это не наша проблема:)

'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 КБ (после сжатия!). Давайте будем честными - 200kb действительно ничего, но если я хочу плавно перетекать на экран и быть в состоянии смотреть с высокой скоростью Fps, мне придется немного поработать над этим - до минимизировать сетевой трафик и использование полосы пропускания.

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

Ответы

Ответ 1

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

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

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

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

EDIT:

Ответ 2

Slashy,

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

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