按帧性能问题播放视频

时间:2015-06-17 07:47:31

标签: c# video directshow simulator avcodec

我希望逐帧模式播放视频(主要是带动态JPEG的.mov),并改变帧速率。我有一个函数给我一个framenumber然后我必须跳到那里。它主要是在一个方向,但可以不时跳过几帧;速度也不是恒定的。 所以我有一个计时器询问每40ms一个新的帧号并设置新的位置。 我现在的第一个方法是使用DirectShow.Net(Interop.QuartzTypeLib)。因此,我渲染并打开视频并将其设置为暂停以在图形中绘制图片

    FilgraphManagerClass media = new FilgraphManagerClass();
    media.RenderFile(FileName);
    media.pause();

现在我将设置一个新职位

    media.CurrentPosition = framenumber * media.AvgTimePerFrame;

由于视频处于暂停模式,因此它将绘制每个请求的新位置(帧)。工作得很好,但很慢......视频口吃和滞后,而不是视频源;记录足够的帧以播放流畅的视频。 通过一些性能测试,我发现LAV-Codec是这里的瓶颈。这不会直接包含在我的项目中,因为它的DirectShow-Player将通过我安装在我PC上的编解码器包进行投射。

思路:

  • 我自己直接在C#中使用LAV-Codec。我搜索过,但似乎每个人都在使用DirectShow,构建自己的过滤器,而不是直接在项目中使用现有过滤器。
  • 不是寻找或设置时间,我可以仅使用framenumber获取单帧并简单地绘制它们吗?
  • 是否有完整的其他方式来归档我想要做的事情?

背景

这个项目必须是一个火车模拟器。我们记录了从驾驶舱内驾驶的火车的实时视频,并知道哪个框架是什么位置。现在我的C#程序根据时间和加速度计算列车的位置,返回适当的帧数并绘制该帧。

其他信息:

在C / C ++中还有另一个项目(不是由我编写)直接使用DirectShow和avcodec-LAV,我用的方式类似,工作正常!那是因为我有想法自己使用像avrcodec-lav这样的编解码器/过滤器。但我无法找到与C#一起使用的互操作或界面。

感谢大家阅读本文并尝试提供帮助! :)

2 个答案:

答案 0 :(得分:0)

通过寻找过滤器图形(整个管道)获取特定框架非常慢,因为每个搜索操作都涉及到其后院的以下内容:刷新所有内容,可能重新创建工作线程,寻找第一个关键帧/拼接点/清理点/ I帧在请求的时间之前,从找到的位置跳过帧开始解码,直到达到最初请求的时间。

总体而言,当您擦除暂停的视频或检索特定静止帧时,此方法很有效。然而,当你尝试将其作为流畅的视频播放时,它最终会导致大部分工作被浪费掉并用于在视频流中寻找。

这里的解决方案是:

  • 重新编码视频以删除或减少时间压缩(例如Motion JPEG AVI / MOV / MP4文件)
  • 尽可能根据您的算法而不是寻求
  • 来跳过帧和/或重新加时间戳
  • 有一个已解码的视频帧缓存并从那里挑选,在工作线程中根据需要填充它们

如果没有先进的过滤器开发(后者通过寻求操作而不中断连续解码是实现良好性能的关键),后两者很难实现。使用基本的DirectShow.Net,您只能对流进行基本控制,因此也是上面列表中的第一项。

答案 1 :(得分:0)

想要发表评论而不是回答,但没有声誉。我认为你的方向与Direct Show一致。现在,在C#& amp;和C#之间的几年里,我一直在使用motion-jpeg。 Android,并通过内置的.NET代码(用于将字节数组转换为Jpeg帧)和一些多线程获得了出色的性能。我可以轻松地从多个设备上实现超过30fps,每个设备都在自己的线程中运行。

下面是我的C#app'OmniView'中我的motion-jpeg解析器的旧版本。要使用,只需将网络流发送到构造函数,然后接收OnImageReceived事件。然后,您可以轻松地将帧保存到硬盘驱动器以供以后使用(可能将文件名设置为时间戳以便于查找)。但是为了获得更好的性能,您需要将所有图像保存到一个文件中。

using OmniView.Framework.Helpers;
using System;
using System.IO;
using System.Text;
using System.Windows.Media.Imaging;

namespace OmniView.Framework.Devices.MJpeg
{
    public class MJpegStream : IDisposable
    {
        private const int BUFFER_SIZE = 4096;
        private const string tag_length = "Content-Length:";
        private const string stamp_format = "yyyyMMddHHmmssfff";

        public delegate void ImageReceivedEvent(BitmapImage img);
        public delegate void FrameCountEvent(long frames, long failed);
        public event ImageReceivedEvent OnImageReceived;
        public event FrameCountEvent OnFrameCount;

        private bool isHead, isSetup;
        private byte[] buffer, newline, newline_src;
        private int imgBufferStart;

        private Stream data_stream;
        private MemoryStream imgStreamA, imgStreamB;
        private int headStart, headStop;
        private long imgSize, imgSizeTgt;
        private bool useStreamB;

        public volatile bool EnableRecording, EnableSnapshot;
        public string RecordPath, SnapshotFilename;

        private string boundary_tag;
        private bool tagReadStarted;
        private bool enableBoundary;

        public volatile bool OututFrameCount;
        private long FrameCount, FailedCount;


        public MJpegStream() {
            isSetup = false;
            imgStreamA = new MemoryStream();
            imgStreamB = new MemoryStream();
            buffer = new byte[BUFFER_SIZE];
            newline_src = new byte[] {13, 10};
        }

        public void Init(Stream stream) {
            this.data_stream = stream;
            FrameCount = FailedCount = 0;
            startHeader(0);
        }

        public void Dispose() {
            if (data_stream != null) data_stream.Dispose();
            if (imgStreamA != null) imgStreamA.Dispose();
            if (imgStreamB != null) imgStreamB.Dispose();
        }

        //=============================

        public void Process() {
            if (isHead) processHeader();
            else {
                if (enableBoundary) processImageBoundary();
                else processImage();
            }
        }

        public void Snapshot(string filename) {
            SnapshotFilename = filename;
            EnableSnapshot = true;
        }

        //-----------------------------
        // Header

        private void startHeader(int remaining_bytes) {
            isHead = true;
            headStart = 0;
            headStop = remaining_bytes;
            imgSizeTgt = 0;
            tagReadStarted = false;
        }

        private void processHeader() {
            int t = BUFFER_SIZE - headStop;
            headStop += data_stream.Read(buffer, headStop, t);
            int nl;
            //
            if (!isSetup) {
                byte[] new_newline;
                if ((nl = findNewline(headStart, headStop, out new_newline)) >= 0) {
                    string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
                    if (tag.StartsWith("--")) boundary_tag = tag;
                    headStart = nl+new_newline.Length;
                    newline = new_newline;
                    isSetup = true;
                    return;
                }
            } else {
                while ((nl = findData(newline, headStart, headStop)) >= 0) {
                    string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
                    if (!tagReadStarted && tag.Length > 0) tagReadStarted = true;
                    headStart = nl+newline.Length;
                    //
                    if (!processHeaderData(tag, nl)) return;
                }
            }
            //
            if (headStop >= BUFFER_SIZE) {
                string data = Encoding.UTF8.GetString(buffer, headStart, headStop - headStart);
                throw new Exception("Invalid Header!");
            }
        }

        private bool processHeaderData(string tag, int index) {
            if (tag.StartsWith(tag_length)) {
                string val = tag.Substring(tag_length.Length);
                imgSizeTgt = long.Parse(val);
            }
            //
            if (tag.Length == 0 && tagReadStarted) {
                if (imgSizeTgt > 0) {
                    finishHeader(false);
                    return false;
                }
                if (boundary_tag != null) {
                    finishHeader(true);
                    return false;
                }
            }
            //
            return true;
        }

        private void finishHeader(bool enable_boundary) {
            int s = shiftBytes(headStart, headStop);
            enableBoundary = enable_boundary;
            startImage(s);
        }

        //-----------------------------
        // Image

        private void startImage(int remaining_bytes) {
            isHead = false;
            imgBufferStart = remaining_bytes;
            Stream imgStream = getStream();
            imgStream.Seek(0, SeekOrigin.Begin);
            imgStream.SetLength(imgSizeTgt);
            imgSize = 0;
        }

        private void processImage() {
            long img_r = (imgSizeTgt - imgSize - imgBufferStart);
            int bfr_r = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
            int t = (int)Math.Min(img_r, bfr_r);
            int s = data_stream.Read(buffer, imgBufferStart, t);
            int x = imgBufferStart + s;
            appendImageData(0, x);
            imgBufferStart = 0;
            //
            if (imgSize >= imgSizeTgt) processImageData(0);
        }

        private void processImageBoundary() {
            int t = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
            int s = data_stream.Read(buffer, imgBufferStart, t);
            //
            int nl, start = 0;
            int end = imgBufferStart + s;
            while ((nl = findData(newline, start, end)) >= 0) {
                int tag_length = boundary_tag.Length;
                if (nl+newline.Length+tag_length > BUFFER_SIZE) {
                    appendImageData(start, nl+newline.Length - start);
                    start = nl+newline.Length;
                    continue;
                }
                //
                string v = Encoding.UTF8.GetString(buffer, nl+newline.Length, tag_length);
                if (v == boundary_tag) {
                    appendImageData(start, nl - start);
                    int xstart = nl+newline.Length + tag_length;
                    int xsize = shiftBytes(xstart, end);
                    processImageData(xsize);
                    return;
                } else {
                    appendImageData(start, nl+newline.Length - start);
                }
                start = nl+newline.Length;
            }
            //
            if (start < end) {
                int end_x = end - newline.Length;
                if (start < end_x) {
                    appendImageData(start, end_x - start);
                }
                //
                shiftBytes(end - newline.Length, end);
                imgBufferStart = newline.Length;
            }
        }

        private void processImageData(int remaining_bytes) {
            if (EnableSnapshot) {
                EnableSnapshot = false;
                saveSnapshot();
            }
            //
            try {
                BitmapImage img = createImage();
                if (EnableRecording) recordFrame();
                if (OnImageReceived != null) OnImageReceived.Invoke(img);
                FrameCount++;
            }
            catch (Exception) {
                // output frame error ?!
                FailedCount++;
            }
            //
            if (OututFrameCount && OnFrameCount != null) OnFrameCount.Invoke(FrameCount, FailedCount);
            //
            useStreamB = !useStreamB;
            startHeader(remaining_bytes);
        }

        private void appendImageData(int index, int length) {
            Stream imgStream = getStream();
            imgStream.Write(buffer, index, length);
            imgSize += (length - index);
        }

        //-----------------------------

        private void recordFrame() {
            string stamp = DateTime.Now.ToString(stamp_format);
            string filename = RecordPath+"\\"+stamp+".jpg";
            //
            ImageHelper.Save(getStream(), filename);
        }

        private void saveSnapshot() {
            Stream imgStream = getStream();
            //
            imgStream.Position = 0;
            Stream file = File.Open(SnapshotFilename, FileMode.Create, FileAccess.Write);
            try {imgStream.CopyTo(file);}
            finally {file.Close();}
        }

        private BitmapImage createImage() {
            Stream imgStream = getStream();
            imgStream.Position = 0;
            return ImageHelper.LoadStream(imgStream);
        }

        //-----------------------------

        private Stream getStream() {return useStreamB ? imgStreamB : imgStreamA;}

        private int findNewline(int start, int stop, out byte[] data) {
            for (int i = start; i < stop; i++) {
                if (i < stop-1 && buffer[i] == newline_src[0] && buffer[i+1] == newline_src[1]) {
                    data = newline_src;
                    return i;
                } else if (buffer[i] == newline_src[1]) {
                    data = new byte[] {newline_src[1]};
                    return i;
                }
            }
            data = null;
            return -1;
        }

        private int findData(byte[] data, int start, int stop) {
            int data_size = data.Length;
            for (int i = start; i < stop-data_size; i++) {
                if (findInnerData(data, i)) return i;
            }
            return -1;
        }

        private bool findInnerData(byte[] data, int buffer_index) {
            int count = data.Length;
            for (int i = 0; i < count; i++) {
                if (data[i] != buffer[buffer_index+i]) return false;
            }
            return true;
        }

        private int shiftBytes(int start, int end) {
            int c = end - start;
            for (int i = 0; i < c; i++) {
                buffer[i] = buffer[end-c+i];
            }
            return c;
        }
    }
}