С# Экранная потоковая программа
В последнее время я работал над простой программой для совместного использования экрана.
На самом деле программа работает с 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