Play video frame by frame

I want to play a video (mainly .mov with Motion JPEG) in frame-by-frame mode with a change in frame rate. I have a function that gives me the frame number, and then I have to jump there. It will be mainly in one direction, but may miss several frames from time to time; also the speed is not constant. Therefore, I have a timer asking every 40 ms about a new frame number and setting up a new position. My first approach now is with DirectShow.Net (Interop.QuartzTypeLib). So I visualize and open the video and set it to pause to draw the image on the chart

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

Now I just set a new position

    media.CurrentPosition = framenumber * media.AvgTimePerFrame;

Since the video is in pause mode, it will draw each requested new position (frame). It works fine, but very slowly ... the video continues to stutter and lag, and not from the source of the video; there are enough frames recorded to play free video. With some performance tests, I found that the LAV codec is the bottleneck here. This is not directly included in my project, as its DirectShow-Player will be transferred through my codec package, which I installed on my PC.

Ideas:

  • Using the LAV codec yourself directly in C #. I searched, but everyone uses DirectShow, it seems they create their own filters and do not use existing ones directly in the project.
  • Instead of looking for or setting the time, can I get single frames with just a frame and just draw them?
  • , ?

:

. , - . # , .


:

C/++ ( ), DirectShow avcodec-LAV , , ! , /, avrcodec-lav. #.


, !:)

+4
2

( ) , : , , / / /I -Frame , , , , .

, , . , , , .

:

  • (, Motion JPEG AVI/MOV/MP4).
  • / ,
  • ,

, , ( ). DirectShow.Net , , .

0

, . , Direct Show. motion-jpeg # Android .NET- ( Jpeg-) . 30 , .

motion-jpeg # OmniView. , 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;
        }
    }
}
0

All Articles