c#中的实时视频流

本文关键字:实时视频 | 更新日期: 2023-09-27 18:13:32

我正在开发一个实时流媒体应用程序。流媒体包括两个部分。我使用捕捉卡捕捉一些实时源,需要实时流。还需要流式传输本地视频文件。

为了实时传输本地视频文件,我使用emgu cv将视频帧捕获为位图。为了实现这一点,我创建了位图列表,并使用一个线程将捕获的位图保存到该列表中。我还把这些框架放在一个图片框里。位图列表可以存储1秒的视频。如果帧率为它将存储30个视频帧。在填满这个列表后,我启动另一个线程来编码这1秒块视频。

出于编码目的,我使用名为nreco的ffmpeg包装器。我把视频帧写入ffmpeg然后启动ffmpeg进行编码。在停止该任务后,我可以得到编码数据作为字节数组。

然后我通过局域网使用UDP协议发送数据。

这很好。但我无法实现流畅的流动。当我通过VLC播放器接收流时,数据包之间有几毫秒的延迟,我也注意到有一帧丢失。

private Capture _capture = null;
Image<Bgr, Byte> frame;
// Here I capture the frames and store them in a list
private void ProcessFrame(object sender, EventArgs arg)
{
     frame = _capture.QueryFrame();
     frameBmp = new Bitmap((int)frameWidth, (int)frameHeight, PixelFormat.Format24bppRgb);
     frameBmp = frame.ToBitmap(); 

 twoSecondVideoBitmapFramesForEncode.Add(frameBmp);
                        ////}
     if (twoSecondVideoBitmapFramesForEncode.Count == (int)FrameRate)
     {
         isInitiate = false;
         thread = new Thread(new ThreadStart(encodeTwoSecondVideo));
         thread.IsBackground = true;
         thread.Start();
     }  
 }
public void encodeTwoSecondVideo()
{
    List<Bitmap> copyOfTwoSecondVideo = new List<Bitmap>();
    copyOfTwoSecondVideo = twoSecondVideoBitmapFramesForEncode.ToList();
    twoSecondVideoBitmapFramesForEncode.Clear();
    int g = (int)FrameRate * 2;
    // create the ffmpeg task. these are the parameters i use for h264 encoding
        string outPutFrameSize = frameWidth.ToString() + "x" + frameHeight.ToString();
        //frame.ToBitmap().Save(msBit, frame.ToBitmap().RawFormat);
        ms = new MemoryStream();
        //Create video encoding task and set main parameters for the video encode
        ffMpegTask = ffmpegConverter.ConvertLiveMedia(
            Format.raw_video,
            ms,
            Format.h264,
            new ConvertSettings()
            {
                CustomInputArgs = " -pix_fmt bgr24 -video_size " + frameWidth + "x" + frameHeight + " -framerate " + FrameRate + " ", // windows bitmap pixel format
                CustomOutputArgs = " -threads 7 -preset ultrafast -profile:v baseline -level 3.0 -tune zerolatency -qp 0 -pix_fmt yuv420p -g " + g + " -keyint_min " + g + " -flags -global_header -sc_threshold 40 -qscale:v 1 -crf 25 -b:v 10000k -bufsize 20000k -s " + outPutFrameSize + " -r " + FrameRate + " -pass 1 -coder 1 -movflags frag_keyframe -movflags +faststart -c:a libfdk_aac -b:a 128k "
                //VideoFrameSize = FrameSize.hd1080,
                //VideoFrameRate = 30
            });
        ////////ffMpegTask.Start();
        ffMpegTask.Start();

      // I get the 2 second chunk video bitmap from the list and write to the ffmpeg 
  foreach (var item in copyOfTwoSecondVideo)
        {
            id++;
            byte[] buf = null;
            BitmapData bd = null;
            Bitmap frameBmp = null;
            Thread.Sleep((int)(1000.5 / FrameRate));
            bd = item.LockBits(new Rectangle(0, 0, item.Width, item.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            buf = new byte[bd.Stride * item.Height];
            Marshal.Copy(bd.Scan0, buf, 0, buf.Length);
            ffMpegTask.Write(buf, 0, buf.Length);
            item.UnlockBits(bd);
        }
   }

这是我用来实现直播的过程。但这条河并不平坦。我试着用队列代替的列表,以减少填充列表的延迟。因为我认为延迟发生在线程编码时并发送2秒视频非常快。但是当它完成这个编码过程的位图列表不是完全充分。因此,编码线程将停止,直到下一个2秒视频准备好。

如果有人能帮我解决这个问题,我将不胜感激。如果我做这件事的方法不对,请纠正我。谢谢你!

c#中的实时视频流

由于您的片段没有提供整个过程的详细信息,因此很难对代码进行说明。

首先,你可以完全消除帧缓冲区(位图列表)。只需创建一个实时流编码进程(每2秒创建一个新进程是非常糟糕的主意),并在位图出现时使用Write方法将其推送到VideoConverter。因为你从实时捕获设备中获取帧,你也不需要进行任何手动延迟(Thread.Sleep((int)(1000.5/FrameRate)))。因此,您应该在VLC端获得平滑的视频(由于编码和网络传输,一些延迟-通常约200-500ms -是不可避免的)。

如果你从捕获设备获取帧,你可以尝试"-re" FFMpeg选项

我现在改变了我的代码。当帧缓冲区被填满后,我启动了用于编码视频帧的线程。在这个线程中,我对视频帧进行编码,并将这些编码数据保存在线程安全队列中。在一定程度上填充队列后,我启动计时器。定时器将触发每200毫秒的间隔,并发送编码数据。

这工作得很好,我在接收端得到了平滑的流。我用720视频进行了测试。但是当我尝试播放1080p视频时,一开始它的播放效果非常好。但经过一段时间后,流显示部分。我注意到这种情况发生在我的流媒体应用程序发送数据不快的时候。所以玩家缓冲区在毫秒的时间内是空的。我认为这是因为emgu cv没有实时捕获帧。它捕获帧非常快的低分辨率视频。当我捕捉1080p高清视频时,捕捉速度会变慢。甚至两者都具有相同的帧率。每次它都会在"应用程序"时捕获帧。空闲+= ProcessFrame;"事件触发。

我有一个捕获卡,可以用来实时捕获视频,如果我有HDMI线可以使用。所以我不知道如何使用捕获卡捕获视频文件。这就是我用开放式简历的原因。我也删除了所有的线程,正如你所说的。