【问题标题】:C# Streaming video over NetworkStream / TCPClientC# 通过 NetworkStream / TCPClient 流式传输视频
【发布时间】:2012-03-26 12:02:22
【问题描述】:

我正在将来自 Xbox Kinect 的视频源从客户端程序发送到服务器。我一切正常,但问题是帧速率。我在想正在发生的事情是它发送的速度超过了它可以读取的速度。因此,当它无法再发送时,它会存储即将发送的内容并等待缓冲区中有空间。我认为这是发生这种情况的原因是因为我可以看到程序的内存使用量稳步增长。也因为当我观看视频时,我看到了大约 10 秒前发生的一切,并且播放速度较慢,但​​它没有跳过任何帧。所以我所做的是将帧速率降低到 5 fps,当我这样做时它是稳定的。但这不是最好的方法。我想要做的是当缓冲区已满时,只需跳过该帧并等待缓冲区有空间发送帧。这听起来像它可能是问题,如果是这样我应该如何解决它?谢谢。

这是发送和接收数据的代码。

    private const int constChunkSize = 4096;
    private const int constIntSize = 4;

    protected TcpClient tcpObject;
    protected NetworkStream tcpStream;

    private void HandleComm()
    {
        try
        {
            tcpStream = tcpObject.GetStream();
            byte[] totalByteAray = new byte[constIntSize];
            byte[] message = new byte[constChunkSize];
            byte[] fullMessage = new byte[0];

            //this is how many bytes long the message will be
            int totalBytes = 0;
            int currentBytes = 0;
            int chunkSize = constChunkSize;
            int bytesRead = 0;

            pingThread = new Thread(sendPing);
            pingThread.Start();

            while (true)
            {                    
                //skip reading if no data is available
                //DataAvailable does not tell you when all the data has arrived
                //it just tell you if some data has arrived
                if (tcpStream.CanRead)
                {
                    totalBytes = 0;
                    currentBytes = 0;
                    message = new byte[constChunkSize];
                    chunkSize = constChunkSize;
                    bytesRead = 0;

                    //The first 4 bytes of the message will always contain the length of the message, not including
                    //the first 4 bytes. This is how you know when to stop reading.                                                
                    bytesRead = tcpStream.Read(totalByteAray, 0, constIntSize);
                    if (bytesRead == 0)                        
                        Disconnect();                        
                    //there are 4 bytes in a 32 bit number, so totalByteArrayContains 4 index that is a byte which is
                    //the 32 bit int that tells us how many bytes the whole message will be.
                    //now convert the totalByteArray to a 32bit int
                    totalBytes = BitConverter.ToInt32(totalByteAray, 0);
                    //fullMessage will contain the entire message but it has to be built message by message.                    
                    fullMessage = new byte[totalBytes];
                    //keep reading until we get all the data
                    while (currentBytes < totalBytes)
                    {
                        //when you send something over TCP it will some times get split up
                        //this is why you only read in chuncks, 4096 is a safe amount of bytes
                        //to split the data into.
                        if (totalBytes - currentBytes < constChunkSize)
                        {
                            chunkSize = totalBytes - currentBytes;
                            message = new byte[chunkSize];
                        }

                        bytesRead = tcpStream.Read(message, 0, chunkSize);
                        if (bytesRead == 0)                            
                            Disconnect();                            
                        //since we know each chunk will always come in at 4096 bytes if it doesn't that means that it's the end
                        //this part cuts off the extra empty bytes                           

                        //copy the message to fullMessage starting at current bytes and ending with the bytes left
                        message.CopyTo(fullMessage, currentBytes);
                        currentBytes += bytesRead;                            
                    }

                    //message has successfully been received
                    if (totalBytes != 0)
                    {
                        //if the message was a ping handle it here to reduce the size of the packet
                        if (fullMessage.Length == 1 && (fullMessage[0] == 0 || fullMessage[0] == 255))
                        {
                            //if the message matches your ping byte, then it's yours
                            if (fullMessage[0] == pingByte[0])
                            {
                                lastReceivedPing = DateTime.Now;
                                latency = (lastReceivedPing - lastSentPing).TotalMilliseconds;

                                if (OnPingReceived != null)
                                {
                                    PingReceivedArgs args = new PingReceivedArgs();
                                    args.receivedTime = lastReceivedPing;
                                    args.latency = latency;
                                    OnPingReceived(this, args);
                                }
                            }
                            //if it doesn't then send it off
                            else
                            {
                                sendData(fullMessage);
                            }
                        }
                        //if it's anything else pass it on
                        else
                        {
                            if (OnRawDataReceived != null)
                            {
                                RawDataReceivedArgs args = new RawDataReceivedArgs();
                                args.Data = new byte[fullMessage.Length];
                                fullMessage.CopyTo(args.Data, 0);
                                OnRawDataReceived(this, args);
                            }
                        }
                        totalBytes = 0;
                    }
                }
            }
        }
        catch
        {
            Disconnect();
        }
    }

    protected void sendData(byte[] data)
    {
        try
        {
            //we need to know how big the data that we are sending will be
            int length = data.Length;
            //convert the 32bit int to a 4 byte array
            byte[] lengthArray = BitConverter.GetBytes(length);

            //init the main byte array that will be sent over
            byte[] buffer = new byte[length + constIntSize];

            //the first 4 bytes will contain the length of the data
            lengthArray.CopyTo(buffer, 0);

            //the rest of the buffer will contain the data being sent
            data.CopyTo(buffer, constIntSize);

            tcpStream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(sendingData), tcpStream);
        }
        catch
        {
            Disconnect();
        }
    }

我研究了使用 Socket.Available 属性 (http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.available.aspx) 来查看缓冲区中有多少数据但它似乎永远不会满。

【问题讨论】:

  • 您能解释一下一次发送一帧是什么意思吗?你介意发布代码吗?

标签: c# video buffer tcpclient networkstream


【解决方案1】:

TCP 在此任务中可能效率低下。您应该对 UDP(数据报套接字)使用无连接且不可靠的传输。由于 TCP 需要连接并提供安全性,它比 UDP 慢,因此在视频流传输期间不应优先使用它。

【讨论】:

  • 我认为你是对的,我只是不想学习如何使用 UDP,但我可能必须。
  • 我最终让它与 TCP 一起工作。解决方案是降低帧分辨率并确保我一次只发送一帧。因为我在一个新线程上发送它允许我一次发送多个帧,这导致了我的问题。
  • 实际上使用 TCP 比使用 UDP 来传输视频更常见。例如,Adobe Flash Player(YouTube 等使用)使用 TCP 流式传输视频。通过最小化发送的数据量来提高效率。它不是每条消息都发送一个完整的帧,而是发送一个代表一个完整帧的“关键帧”,然后发送更新相对于该关键帧的视觉效果的消息。每隔一段时间就会发送一个新的关键帧,并且以下消息是相对于最后一个关键帧的更新。
  • @blachniet 这取决于你想在这里实现什么.. youtube 希望流式传输视频而不会丢弃任何帧,因此 TCP 是正确的选择,但在大多数情况下,流式传输视频作为流的意义(不作为文件)是制作实时视频播放..在这种情况下UDP是更好的选择(会丢弃一些帧)
猜你喜欢
  • 2019-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 2011-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多