Обнаружение пропадания кадров во время захвата экрана - PullRequest
0 голосов
/ 18 октября 2019

Я использую SharpDx для захвата экрана (от 1 до 60 кадров в секунду). Некоторые кадры прозрачны и в конечном итоге обрабатываются и сохраняются кодом.

Есть ли какой-нибудь простой / быстрый способ обнаружить пропадание этих кадров без необходимости открывать сгенерированное растровое изображение и искать значения альфа?

Вот что я использую (сохраняет захват как изображение):

try
{
    //Try to get duplicated frame within given time.
    _duplicatedOutput.AcquireNextFrame(MinimumDelay, out var duplicateFrameInformation, out var screenResource);

    //Copy resource into memory that can be accessed by the CPU. 
    using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
        _device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, new ResourceRegion(Left, Top, 0, Left + Width, Top + Height, 1), _screenTexture, 0);

    //Get the desktop capture texture.
    var mapSource = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None); //, out var stream);

    #region Get image data

    var bitmap = new System.Drawing.Bitmap(Width, Height, PixelFormat.Format32bppArgb);
    var boundsRect = new System.Drawing.Rectangle(0, 0, Width, Height);

    //Copy pixels from screen capture Texture to GDI bitmap.
    var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
    var sourcePtr = mapSource.DataPointer;
    var destPtr = mapDest.Scan0;

    for (var y = 0; y < Height; y++)
    {
        //Copy a single line 
        Utilities.CopyMemory(destPtr, sourcePtr, Width * 4);

        //Advance pointers
        sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
        destPtr = IntPtr.Add(destPtr, mapDest.Stride);
    }

    //Release source and dest locks
    bitmap.UnlockBits(mapDest);

    //Bitmap is saved in here!!!

    #endregion

    _device.ImmediateContext.UnmapSubresource(_screenTexture, 0);

    screenResource.Dispose();
    _duplicatedOutput.ReleaseFrame();
}
catch (SharpDXException e)
{
    if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
        throw;
}

Это модифицированная версия с этой .

У меня также есть этоверсия (сохраняет захват в виде пиксельного массива):

//Get the desktop capture texture.
var data = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None, out var stream);

var bytes = new byte[stream.Length];

//BGRA32 is 4 bytes.
for (var height = 0; height < Height; height++)
{
    stream.Position = height * data.RowPitch;
    Marshal.Copy(new IntPtr(stream.DataPointer.ToInt64() + height * data.RowPitch), bytes, height * Width * 4, Width * 4);
}

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

В любом случае, проблема в том, что некоторые захваченные кадры полностью прозрачны и бесполезны для меня. Мне нужно как-то вообще избегать их сохранения.

При захвате в виде массива пикселей я могу просто проверить массив bytes, чтобы узнать, является ли 4-й элемент 255 или 0. При сохранении в качестве изображения я мог быиспользуйте bitmap.GetPixel(0,0).A, чтобы узнать, содержит ли изображение содержимое.

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

Есть ли способ узнать, правильно ли был снят кадр?

Ответы [ 2 ]

0 голосов
/ 20 октября 2019

Чтобы игнорировать пропадание кадров, я использую этот код (пока он работает как положено):

//Try to get the duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(1000, out var duplicateFrameInformation, out var screenResource);

//Somehow, it was not possible to retrieve the resource.
if (screenResource == null || duplicateFrameInformation.AccumulatedFrames == 0)
{
    //Mark the frame as dropped.
    frame.WasDropped = true;
    FrameList.Add(frame);

    screenResource?.Dispose();
    _duplicatedOutput.ReleaseFrame();
    return;
}

Я просто проверяю, является ли screenResource не нулевым, и еслиэто кадры накопленные.

0 голосов
/ 18 октября 2019

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

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

integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);

while(true){
  if(DateTime.Now >= dueTime){
    //insert code here

    //Update next dueTime
    dueTime = DateTime.Now.AddMillisconds(interval);
  }
  else{
    //Just yield to not tax out the CPU
    Thread.Sleep(1);
  }
}

Всего два примечания:

  • , это было разработано для работы в отдельном потоке. Вы должны запустить его как таковой или адаптировать Thread.Sleep () к выбранному вами варианту многозадачности.
  • DateTime.Now не подходит для таких небольших таймфреймов (2 цифры мс). Обычно возвращаемое значение будет обновляться только каждые 18 мс или около того. Это на самом деле меняется со временем. 60 FPS ставит вас около 16 мс. Вы должны использовать секундомер или что-то подобное.
...