TCP отправляет простое управление потоком и синхронизацию - P2P Webcam - PullRequest
1 голос
/ 13 января 2012

С помощью некоторых приятных людей из SO я медленно создал небольшое P2P-приложение, которое отправляет и получает поток изображений размером около 4 КБ каждое.

На 127.0.0.1 получение идет в ногу с отправкой, но когда я пытаюсь сделать это на удаленной машине, мне кажется, что получение не может идти в ногу, возможно, я отправил 6 изображений, но получатель получил только одно изображение ... и с течением времени разница становится больше, пока вы не увидитесебя целую минуту назад на другом экране.Стоит отметить, что я хотел бы, чтобы это хорошо работало на соединении со скоростью около 64 кбит / с-100 кбит / с в другой стране, где время пинга может быть очень большим, например 250 мс или более.

Какие у меня есть варианты синхронизации?

Мой брат посоветовал мне простое решение, которое заключается в реализации 1: 1 отправки / получения.Поэтому я отправляю изображение только тогда, когда получаю его.

Поскольку я абсолютный новичок в сетевом программировании, любые другие советы приветствуются, вот мой полный код:

namespace MyPrivateChat
{
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using AForge.Video;
    using AForge.Video.DirectShow;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Net;
    using System.Text.RegularExpressions;
    using System.Net.Sockets;
    using System.Diagnostics;
    using AForge.Imaging.Filters;

    public partial class fChat : Form
    {
        public fChat()
        {
            InitializeComponent();
        }
        private void fChat_Load(object sender, EventArgs e)
        {
            // get ip
            _ownExternalIp = GetPublicIP();
            Text = "My Private Chat - IP: " + _ownExternalIp;

            // get video cam
            var _videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (_videoDevices.Count != 0)
            {
                _videoDevice = new VideoCaptureDevice(_videoDevices[0].MonikerString);
                btnStart.Enabled = true;
            }

            // fire up listener
            listeningThread.RunWorkerAsync();
        }
        public string GetPublicIP()
        {
            string ip = "";
            using (WebClient wc = new WebClient())
            {
                Match m = Regex.Match(wc.DownloadString("http://checkip.dyndns.org/"), @"(?<IP>\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})");
                if (m.Success)
                {
                    ip = m.Groups["IP"].Value;
                }
            }
            return ip;
        }
        private void mnuPasteOwnIP_Click(object sender, EventArgs e)
        {
            txtPartnerIP.Text = _ownExternalIp;
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            if (_tcpOut == null)
            {
                // tcp server setup
                _tcpOut = new TcpClient();
                _tcpOut.Connect(txtPartnerIP.Text, 54321);
                tmrLive.Enabled = true;
            }
            else
            {
                tmrLive.Enabled = false;
                _tcpOut.Client.Disconnect(true);
                _tcpOut.Close();
                _tcpOut = null;
            }

            if (!_videoDevice.IsRunning)
            {
                _videoDevice.NewFrame += new NewFrameEventHandler(NewFrameReceived);
                _videoDevice.DesiredFrameSize = new Size(640, 480);
                _videoDevice.DesiredFrameRate = 100;
                _videoDevice.Start();
                btnStart.Text = "Stop";
            }
            else
            {
                _videoDevice.SignalToStop();
                btnStart.Text = "Start";
            }
        }
        private void NewFrameReceived(object sender, NewFrameEventArgs e)
        {
            Bitmap img = (Bitmap)e.Frame.Clone();
            byte[] imgBytes = EncodeToJpeg(img, 25).ToArray();

            if (_tcpOut.Connected)
            {
                NetworkStream ns = _tcpOut.GetStream();
                if (ns.CanWrite)
                {
                    ns.Write(BitConverter.GetBytes(imgBytes.Length), 0, 4);
                    ns.Write(imgBytes, 0, imgBytes.Length);
                    _totalFramesSent++;
                }
            }
        }
        private void listeningThread_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            _tcpIn = new TcpListener(IPAddress.Any, 54321);
            _tcpIn.Start();

            TcpClient _inClient = _tcpIn.AcceptTcpClient();
            lblStatus.Text = "Connected - Receiving Broadcast";
            tmrLive.Enabled = true;

            NetworkStream ns = _inClient.GetStream();
            while (true)
            {
                // read image size. 
                Byte[] imgSizeBytes = new Byte[4];
                int totalBytesRead = 0;
                do
                {
                    int bytesRead = ns.Read(imgSizeBytes, totalBytesRead, 4 - totalBytesRead);
                    if (bytesRead == 0)
                    {
                        break; // problem
                    }
                    totalBytesRead += bytesRead;
                } while (totalBytesRead < 4);

                // read image
                int imgSize = BitConverter.ToInt32(imgSizeBytes, 0);
                Byte[] imgBytes = new Byte[imgSize];
                totalBytesRead = 0;
                do
                {
                    int bytesRead = ns.Read(imgBytes, totalBytesRead, imgSize - totalBytesRead);
                    if (bytesRead == 0)
                    {
                        break; // problem
                    }
                    totalBytesRead += bytesRead;
                } while (totalBytesRead < imgSize);

                picVideo.Image = Image.FromStream(new MemoryStream(imgBytes));
                _totalFramesReceived++;
            }
        }
        private void CloseVideoDevice()
        {
            if (_videoDevice != null)
            {
                if (_videoDevice.IsRunning)
                {
                    _videoDevice.SignalToStop();
                }
                _videoDevice = null;
            }
        }
        private void fChat_FormClosing(object sender, FormClosingEventArgs e)
        {
            CloseVideoDevice();
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void tmrLive_Tick(object sender, EventArgs e)
        {
            _totalSecondsLive++;
            lblStats.Text = "S:"+_totalFramesSent + " R:" + _totalFramesReceived + " T:"+ _totalSecondsLive;
            if (_totalSecondsLive == 60)
            {
                MessageBox.Show("Total Frames : " + _totalFramesSent);
            }
        }

        #region ENCODING JPEG

        private MemoryStream EncodeToJpeg(Bitmap img, long quality)
        {
            using (EncoderParameters myEncoderParameters = new EncoderParameters(1))
            {
                MemoryStream ms = new MemoryStream();
                myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
                img.Save(ms, GetEncoder(ImageFormat.Jpeg), myEncoderParameters);
                return ms;
            }
        }
        private ImageCodecInfo GetEncoder(ImageFormat format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }

        #endregion

        VideoCaptureDevice _videoDevice;
        TcpClient _tcpOut;
        TcpListener _tcpIn;
        string _ownExternalIp;
        int _totalFramesSent;
        int _totalFramesReceived;
        int _totalSecondsLive;
    }
}

Ответы [ 3 ]

1 голос
/ 13 января 2012

Что здесь происходит, так это то, что вы заполняете буферы на отправляющем компьютере, генерируя данные для отправки гораздо быстрее, чем их можно отправить по сети. Мой подход будет

  • создать два логических флага newImageAvailable и readyToSend на уровне класса, оба из которых начинаются с false
  • В NewFrameReceived создайте новое изображение, но пока не отправляйте его. Вместо этого сохраните byte [] в классе vairable и установите для свойства newImageAvailable значение true, затем вызовите новую функцию TrySendImage () (см. Информацию ниже)
  • На Tcp-соединении установите для readyToSend значение true и вызовите TrySendImage ()
  • В TrySendImage ()
  • .... проверить, истинны ли оба значения newImageAvailable и readyToSend, если нет нет ничего
  • .... установить newImageAvailable и readyToSend в false
  • .... Посылка изображения (Размер + данные) асинхронно (BeginSend ())
  • В уведомлении о завершении для BeginSend () установите для ReadyToSend значение true

Хотя это немного сложно, он гарантирует, что всегда отправляется самое новое изображение, и только если ранее изображение было «на проводе».

Я считаю, что это лучше, чем решение «1: 1 отправка / получение», так как у вас часто бывают случаи, когда пропускная способность отличается в двух направлениях - в этом случае решение 1: 1 ухудшит производительность правильного направления для выполнение плохого направления.

Оптимизация, которую я использовал в аналогичном проекте, заключается в динамической адаптации параметра качества кодирования JPEG: если частота кадров падает ниже порогового значения, снижается качество в несколько раз, пока не будет достигнут минимум, если частота кадров возрастает выше другого порогового значения качество с коэффициентом, опять же до максимума.

1 голос
/ 13 января 2012

Ну, это не специфическая проблема TCP.Вы производите быстрее, чем потребляете.Поэтому вам нужно ограничить вашего производителя.

Я бы поменял производителя, чтобы он ждал подтверждения от потребителя, прежде чем отправлять следующее изображение.В то же время я откажусь от всех новых изображений.

На производителе вы сохраняете флаг статуса, который позволяет отслеживать, был ли отправлен кадр и , он не был подтвержден.Пока этот флаг установлен, вы удаляете новые изображения по мере их появления.Когда оно ложно, вы отправляете изображение и устанавливаете его в true.Когда приходит подтверждение, вы устанавливаете флаг в значение false.

Редактировать: я бы реализовал подтверждение как «bool» (байт в сети), потому что это будет подтверждаться намного быстрее, чем отправка изображения в ответ.Я бы определил два «сообщения»: MessageType.Image и MessageType.Acknowledgement.Затем получатель может увидеть, какой MessageType прибыл, и либо обновить экран, либо начать отправку следующего изображения.

Редактировать 2: Вам не нужно просто отбрасывать изображения.Вы можете иметь переменную Image latestUnsentImage.Когда камера создает изображение, вы безоговорочно перезаписываете эту переменную.Когда вам нужно отправить изображение, вы просто получаете доступ к этой переменной.Это будет отправлять всегда последние доступные и неотправленные изображения.

0 голосов
/ 13 января 2012

Что касается того, что "usr" сказал о регулировании, вместо того, чтобы отбрасывать ваши изображения, вы можете добавить эти захваченные изображения в очередь.Из очереди вы сможете получать их по очереди и через другой поток и передавать их по сети.Вы можете даже добавить порядковый номер к изображениям, транспортируемым отдельно от логического подтверждения, чтобы обеспечить транспортировку изображений.Все, что не транспортируется, должно быть снова помещено в очередь.Секвенирование должно позволять вам сортировать правильный порядок изображений и производить меньшую потерю данных.

Отправитель

  • создать порядковый номер
  • назначить флаг bool
  • Поставить в очередь в транспортной очереди
  • Заблокировать и транспортировать (отдельный поток)
  • Повторно поставить в очередь, если нет подтверждения

Получатель (возможно, что вы можете сделать)

  • Получение из сети
  • Отправка подтверждения
  • Проверка номера последовательности и сортировка (может потребоваться память для проверки нумерации последовательности)
...