原文:SignalR+AForge实现视频会话[WPF]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lordwish/article/details/51785023

AForge是基于.NET的强大视频分析库,而SignalR是微软推出的实时通信技术,两者结合起来实现简单的视频会话。预期的效果是实现:在线终端刷新、会话请求、会话接受、会话拒绝、会话繁忙、会话结束。本示例采用USB摄像头。


1服务端及辅助类

1.1创建服务端

[ 使用WPF创建SignalR服务端]

1.2在线终端刷新

[ WPF+SignalR实现用户列表实时刷新]

1.3通信状态辅助类

    public enum MessageState
{
VideoApply,//视频会话请求
VideoOpen,//视频会话开始
VideoRefuse,//视频会话拒绝
VideoBusy,//视频会话繁忙
VideoOver,//视频会话结束
Null
}

1.4服务端方法

在MyHub类中添加方法MessageConnect,其中参数messageid是由会话发起端生成的唯一Guid,用来标识此次会话,当会话接受端拒绝、繁忙或会话过程中任一方终止会话时,会话结束,移除messageid。因为会话状态消息在两个终端之间来回传输的,所以从消息的角度讲,终端发送消息时就是sender,接收消息时就是receiver了。

public Task MessageConnect(ClientModel sender,ClientModel receiver,string messageid,MessageState status)
{
return Clients.Client(receiver.ConnectionId).messageconnect(receiver, sender, messageid, status);
}

2客户端方法

使用NuGet添加AForge程序包,只安装AForge.Video和AForge.Controls就够了,图方便全装也可以。

2.1页面布局

摄像头图像显示使用AForge.Controls中的控件VideoSourcePlayer,所以要引用AForge.Controls命名空间。由于视频实际上是以图像帧的形式传输的,格式为bitmap,所以对方图像显示在PictureBox中,对System.Windows.Form的引用也是不能少的。

    xmlns:aforge="clr-namespace:AForge.Controls;assembly=AForge.Controls"
xmlns:form="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        <WrapPanel>
<Grid Margin="10,10,5,10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>
<WindowsFormsHost Width="320" Height="240" VerticalAlignment="Center" HorizontalAlignment="Center">
<form:PictureBox x:Name="imgCapture" Width="320" Height="240"></form:PictureBox>
</WindowsFormsHost>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Name="txtName"></TextBlock>
</StackPanel>
</Grid>
<Grid Margin="5,10,10,10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>
<WindowsFormsHost Width="320" Height="240" VerticalAlignment="Center" HorizontalAlignment="Center">
<aforge:VideoSourcePlayer x:Name="sourcePlayer" Width="320" Height="240"></aforge:VideoSourcePlayer>
</WindowsFormsHost>
<StackPanel Grid.Row="1" VerticalAlignment="Center" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="btnCapture" Width="90" Height="30" Click="btnCapture_Click">捕获</Button>
<Button Name="btnClose" Width="90" Height="30" Margin="10,0,0,0" Click="btnClose_Click">关闭</Button>
</StackPanel>
</Grid>
</WrapPanel>

2.2发送会话请求

选择在线终端列表上的任一用户,双击发起会话请求。

        private void dgList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (dgList.SelectedItem == null)
{
return;
}
client = (ClientModel)dgList.SelectedItem;
var result = MessageBox.Show("确定要进行视频会话?", "会话提醒", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK);
if (result == MessageBoxResult.OK && client != null)
{
string messageid = Guid.NewGuid().ToString();
MyHub.Invoke("MessageConnect", MyClient, client, messageid, MessageState.VideoApply);
}
}

2.3会话状态监听

接收到会话请求时,首先会判断本地是不是已经有正在进行的视频会话了,如果有就返回繁忙标识,没有就打开摄像头。这里对会话关闭状态是为了在一方终止会话关闭摄像头时,另一方也能收到关闭通知从而关闭摄像头。

        private void ConnectListener()
{
try
{
MyHub.On<ClientModel, ClientModel, string, MessageState>("messageconnect", (sender, receiver, id, state) =>
{
switch (state)
{
case MessageState.VideoApply:
{
if (messageid == "")
{
var result = MessageBox.Show("是否接收" + sender.ClientName + "的会话请求?", "会话提醒", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (result == MessageBoxResult.Yes)
{
this.Dispatcher.Invoke(delegate
{
client = sender;
messageid = id;
txtName.Text = sender.ClientName;
OpenVideo();//打开摄像头
});
}
else
{
MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.VideoRefuse);
}
}
else
{
MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.VideoBusy);
}
}
break;
case MessageState.VideoOpen:
{
this.Dispatcher.Invoke(delegate
{
client = sender;
messageid = id;
txtName.Text = sender.ClientName;
OpenVideo();//打开摄像头
});
}
break;
case MessageState.VideoRefuse:
{
MessageBox.Show(sender.ClientName + "拒绝了您的视频会话请求!");
}
break;
case MessageState.VideoBusy:
{
MessageBox.Show(sender.ClientName + "正忙,请稍后发送请求!");
}
break;
case MessageState.VideoOver:
{
this.Dispatcher.Invoke(delegate
{
CloseVideo();//关闭摄像头
});
}
break;
}
});
}
catch (Exception)
{
throw;
}
}

2.4打开摄像头

在打开摄像头后,为控件绑定NewFrame事件开始发送图像帧,同时开启线程videoThread接收图像帧,并显示在PictureBox中。发送和接收其实都是用了最基本的Socket。

        private Socket videoSocket;
private Thread videoThread;
private object bitmapTemp = new object();
private IPEndPoint receiver = null;
private byte[] buffTemp = new byte[2 * 1024 * 1024];
private void OpenVideo()
{
try
{
receiver = new IPEndPoint(IPAddress.Parse(client.ClientIP), VideoPort);
videoSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
uint IOC_IN = 0x80000000;
uint IOC_VENDOR = 0x18000000;
uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
videoSocket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
videoSocket.Bind(new IPEndPoint(IPAddress.Parse(ClientIp), VideoPort)); FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (videoDevices.Count > 0)
{
sourcePlayer.VideoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
sourcePlayer.NewFrame += SourcePlayer_NewFrame;
sourcePlayer.Start();
videoThread = new Thread(ReceiveVideo);
videoThread.IsBackground = true;
videoThread.Start();
}
else
{
MessageBox.Show("未找到视频设备!");
} }
catch (Exception ex)
{
MessageBox.Show(ex.Message);
//throw;
}
}

2.5发送图像帧

        private void SourcePlayer_NewFrame(object sender, ref Bitmap image)
{
lock (bitmapTemp)
{
System.Drawing.Image imageTemp = image.Clone(new System.Drawing.Rectangle(0, 0, image.Width, image.Height), image.PixelFormat);
MemoryStream stream = new MemoryStream();
imageTemp.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
byte[] buffImage = new byte[stream.Length];
stream.Read(buffImage, 0, buffImage.Length);
videoSocket.BeginSendTo(buffImage, 0, buffImage.Length, SocketFlags.None, receiver, SendData, videoSocket);
stream.Dispose();
stream = null;
}
}
private void SendData(IAsyncResult ar)
{
Socket socket = (Socket)ar.AsyncState;
socket.EndSendTo(ar);
}

2.6接收图像帧

        private void ReceiveVideo()
{
while (true)
{
bool result = videoSocket.Poll(5000, SelectMode.SelectRead);
if (result)
{
videoSocket.BeginReceive(buffTemp, 0, buffTemp.Length, SocketFlags.None, ReceiveData, videoSocket);
}
}
}
private void ReceiveData(IAsyncResult ar)
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndReceive(ar);
if (count > 0)
{
byte[] buff = new byte[count];
Buffer.BlockCopy(buffTemp, 0, buff, 0, count);
MemoryStream ms = new MemoryStream(buff);
Bitmap bitmap = (Bitmap)System.Drawing.Image.FromStream(ms);
imgCapture.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
imgCapture.Image = bitmap;
}
}

2.7关闭摄像头

        private void btnClose_Click(object sender, RoutedEventArgs e)
{
try
{
MyHub.Invoke("MessageConnect", MyClient, client, messageid, MessageState.VideoOver);
CloseVideo();
client = null;
messageid = ""; }
catch (Exception)
{ throw;
}
}
private void CloseVideo()
{
videoThread.Abort();
if (sourcePlayer.IsRunning)
{
sourcePlayer.SignalToStop();
sourcePlayer.WaitForStop();
}
imgCapture.Image = null;
sourcePlayer.NewFrame -= SourcePlayer_NewFrame;
}

后记

总体来说运行效果还是不错的。最开始想用SignalR做图像帧的发送和接收,发现没经过压缩的图像数据量太大,SignalR根本反应不过来,还是用底层的Socket快。而SignalR中的很多特性都是从底层封装好的,在实时通信上比Socket要方便很多。此外视频控件并不局限于AForge,类似WPFMediaTookit之类的也可以做,原理都是一样的。

SignalR+AForge实现视频会话[WPF]的更多相关文章

  1. SignalR+NAudio实现语音会话[WPF]

    原文:SignalR+NAudio实现语音会话[WPF] 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lordwish/article/detai ...

  2. Emgucv(二)Emgucv和Aforge录制视频

    一.Emgucv录制视频 Emgucv中的Capture类可以完成视频文件的读取,利用EmguCV播放视频的原理是:将视频看作图片,用capture获取抓取通道,通过不断的调用{frame = cap ...

  3. 使用AForge录制视频

    使用AForge录制视频,基于Winform开发 (一)首先导入AForge包 需要先导入 using AForge.Video;using AForge.Video.FFMPEG; 两个工具包 (二 ...

  4. 基于webrtc的多人视频会话的demo运行程序

    服务端程序: 该服务程序为windows平台下的程序,使用libevent书写,并集成了UDP的中转程序.(该服务器程序不能和客户端程序运行在同一台PC机电脑,不然服务器程序和客户端程序会抢占同一UD ...

  5. 【SignalR学习系列】5. SignalR WPF程序

    首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...

  6. Android IOS WebRTC 音视频开发总结(八十二)-- VP8对VP9,质量还是码率?

    本文主要介绍VP9(我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:blacke ...

  7. WebRTC源码分析四:视频模块结构

    转自:http://blog.csdn.net/neustar1/article/details/19492113 本文在上篇的基础上介绍WebRTC视频部分的模块结构,以进一步了解其实现框架,只有了 ...

  8. WebRTC源码分析三:视频处理流程

    转自:http://blog.csdn.net/neustar1/article/details/19480863 文本介绍视频的处理流程.图1中显示了两路视频会话视频信号流过程. 图1 视频流程示意 ...

  9. 【转载】视频编码(H264概述)

    一视频编码介绍 1.1 视频压缩编码的目标 1)保证压缩比例 2)保证恢复的质量 3)易实现,低成本,可靠性 1.2 压缩的出发点(可行性) 1)时间相关性 在一组视频序列中,相邻相邻两帧只有极少的不 ...

随机推荐

  1. theme-windowAnimationStyle 动画四个方法的意义

    首先看代码 <style name="Animation.Activity"> <!--A打开B,B的出现动画--> <item name=" ...

  2. CSS3常用属性及用法

    1.transition: 过渡属性,可以替代flash和javascript的效果 兼容性:Internet Explorer 9 以及更早的版本,不支持 transition 属性. Chrome ...

  3. python序列中是否包含某个元素

    http://outofmemory.cn/code-snippet/9098/python-list-contains-with-in-not-in theList = ['a','b','c'] ...

  4. .net core 修改网站启动端口

    原文:.net core 修改网站启动端口 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/yenange/article/details/81675 ...

  5. HTML中input标签maxlength属性的妙处

    HTML中的input标签可是很常用的. HTML本身也非常简单,就是若干标签,每个标签有若干属性. 我在学习HTML的过程中,也没有太过重视. 今年,在写前端表单验证的时候,发现maxlength这 ...

  6. [置顶] WebService学习总结(4)——第三方webService服务调用

    互联网上面有很多的免费webService服务,我们可以调用这些免费的WebService服务,将一些其他网站的内容信息集成到我们的Web应用中显示,下面就以获取天气预报数据和查询国内手机号码归属地为 ...

  7. Java Web学习总结(8)——使用Cookie进行会话管理

    一.会话的概念 会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话. 有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾 ...

  8. vue 图片lazyload

    今天看到我一醉哥的一篇朋友圈分享:<不如我们从头来过 | 掘金> 看完之后,百感交集,互联网的浪潮使创业公司如雨后春笋般崛起, 每一个初创公司都会有一群怀着美好愿景的小伙伴, 但是当寒冬来 ...

  9. [D3] Start Visualizing Data Driven Documents with D3 v4

    It’s time to live up to D3’s true name and potential by integrating some real data into your visuali ...

  10. js进阶 12-8 如何知道上一个函数的返回值是什么(如何判断上一个函数是否执行成功)

    js进阶 12-8 如何知道上一个函数的返回值是什么(如何判断上一个函数是否执行成功) 一.总结 一句话总结:event的result属性即可. 1.event的result属性的实际应用场景是什么? ...