0

我的应用程序使用 RTSPClientSharp 库从相机流式传输,当解码帧准备好时,会引发一个 OnFramesReceived 事件。我在同一个事件中将解码帧转换为位图,这是一个阻塞调用,耗时超过 100 毫秒,导致帧速率降至 10 FPS。

为了解决这个问题,我使用了此处的任务队列代码,该代码使用 Task.ContinueWith.UnWrap 将 ProcessFrame 事件(具有将解码帧转换为位图的代码)排队。我的目标是按照收到帧的顺序依次执行 ProcessFrame 调用。使用任务队列解决了阻塞呼叫的问题,现在我可以每秒处理 30 帧。

但是,我现在遇到了内存问题,如果我的应用程序运行时间更长,内存使用量会逐渐增加。ANTS 内存分析器说(检查屏幕截图) ContinuationResultFrom 任务是 Gen2 中最大的类。

更新 我想包括的一些事实,我有 10 个这样的摄像头连接到我的应用程序,每个摄像头都有自己的摄像头类实例。我正在使用具有超线程和 32GB RAM 的 16 核处理器,但如果 CPU 无法处理负载,我宁愿将 FPS 降低到 10。

  private void OnFramesReceived(object sender, RawFrame rawFrame)
    {
         taskQueue.Enqueue(() => Task.Run(() => ProcessFrame?.Invoke(this, decodedFrame)));           
    }

  private void HandleProcessFrame(object sender, IDecodedVideoFrame decodedFrame)
    {
        try
        { 
            using (Bitmap bmpBitmap = new Bitmap(m_Width, m_Height))
            {
                BitmapData bmpData = bmpBitmap.LockBits(new Rectangle(0, 0, bmpBitmap.Width, bmpBitmap.Height), ImageLockMode.WriteOnly, bmpBitmap.PixelFormat);

                try
                {
                    decodedFrame.TransformTo(
                        bmpData.Scan0,
                        bmpData.Stride,
                        _transformParameters);
                }
                finally
                {
                    bmpBitmap.UnlockBits(bmpData);
                } 
                base.OnNewFrameEvent(this, bmpBitmap);
                decodedFrame = null;
            
            }


        }
        catch (Exception ex)
        {
            Logng.LogError(ex);
        }
    }
 public class TaskQueue
{
    private Task previous = Task.FromResult(false);
    private object key = new object();

    public Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
    {
        lock (key)
        {
            var next = previous.ContinueWith(t => taskGenerator()).Unwrap();
            previous = next;
            return next;
        }
    }
    public Task Enqueue(Func<Task> taskGenerator)
    {
        lock (key)
        {
            var next = previous.ContinueWith(t => taskGenerator(), TaskContinuationOptions.ExecuteSynchronously).Unwrap();
            previous = next;
            return next;
        }
    }
}
4

1 回答 1

0

通过使用延续,您正在创建一个不是集中控制的队列,也是一个内存效率不高的队列。在实际有效负载( )之上,您为每个延续支付 200-300 字节的开销RawFrame。我建议切换到更有条理和更高效的东西,比如TPL Dataflow库。

下面是使用 TPL 数据流库的示例。该库中最简单的单个ActionBlock组件为计算提供了强大的功能。BoundedCapacity您可以通过设置选项来配置其内部队列的大小。当队列变满时,即将到来的消息将被丢弃(该Post方法将返回false)。您还可以配置MaxDegreeOfParallelism. 您可以利用机器的所有可用内核/处理器,或者让一个或两个内核空闲来做其他工作。

private readonly ActionBlock<RawFrame> _actionBlock;

public MyClass() // constructor
{
    _actionBlock = new ActionBlock<RawFrame>(rawFrame =>
    {
        ProcessFrame(rawFrame);
    }, new ExecutionDataflowBlockOptions()
    {
        BoundedCapacity = 10, // the default is unbounded
        MaxDegreeOfParallelism = Environment.ProcessorCount,  // the default is 1
    });
}

private void OnFramesReceived(object sender, RawFrame rawFrame)
{
    _actionBlock.Post(rawFrame);
}

TPL 数据流库内置在 .NET Core 中,可作为.NET Framework的包使用。

于 2020-08-26T14:27:40.377 回答