C#视频采集与推送RTMP服务器代码思路整理:在看过FFmpeg后是否认为写C#的视频流采集和推送还是一头雾水啊?深有此感。领导是C#的高手,说可以通过C或C++的代码直接复制粘贴到C#工程然后进行适配代码就可以了,因为C#使用ffmpeg的类名和变量、方法等都与C保持高度一致的,经领导这么一说C#里面只需要参考C或C++的实现就可以完成相关的操作了,这样就更容易理解了(涉及到指针问题,C#也支持)。本文旨在分析实现思路,而不提供源代码,敬请谅解!
服务端和客户端的职能
客户端负责视频采集,服务端负责视频转发或播放
整体设计视频传输架构
说明:视频数据发送H264数据包给服务端,服务端获取到数据包进行编解码转成FLV格式的流,而RTMP只支持FLV格式的流。
原理:socket视频数据采集,socket 数据包发送,编解码处理,推流。
视频直播推流效果展示
启动顺序:程序客户端和服务端是两个项目需要分别启动,先启动服务端,再启动客户端,允许多个实例存在。
第一幕:启动视频编解码服务端
第二幕:启动视频采集摄像头客户端
点击开始连接到服务端。
第三幕:服务端处理要连接的客户端并建立数据传输
选择客户端,并开始传输视频,此时会弹出摄像头访问窗口。
第四幕:验证直播是否进行
访问RTMP服务器状态监控地址,如:http://172.16.20.10:1990/stat
第五幕:使用ffplay进行直播流播放
cmd进入ffmpeg的路径执行:ffplay rtmp://172.16.20.10:1935/live/S013333333333
以上就是所有架构实现的效果了。
摄像头视频采集客户端
客户端窗体变量:
// socket数据包封装对象
JTData jtdata = new JTData();
// 数据分包对象
SplitData splitData = new SplitData();
// TCP通信通道
Network.TCPChannel channel;
// 标识用户手机号
string SimKey = “013333333333”;
// 线程对象
Thread thVideo;
// Socket实例对象
private Socket send_sock;
private Queue<Bitmap> mQueues = new Queue<Bitmap>();
// 摄像头处理H264对象
Cam2H264 cam2H264 = new Cam2H264();
private Setting setting;
IPEndPoint iPEndPoint;
客户端窗体开始录像按钮处理:
private void btnStart_Click(object sender, EventArgs e)
{
channel = new Network.TCPChannel(txtServer.Text.Trim(), Convert.ToInt32(txtPort.Text.Trim()));//实例化TCP连接通道
channel.DataReceive = Receive;
channel.DataSend = DataSend;
channel.ChannelConnect += new EventChannelConnect(channel_ChannelConnect);// 设置通道连接事件
channel.Connect();
//channel.StartReceiveAsync();
btnStart.Enabled = false;
btnStop.Enabled = true;
}
客户端窗体TCP通道回调函数:
void channel_ChannelConnect(object sender, ChannelConnectArg arg)
{
Console.WriteLine(arg.SocketError.ToString());
SendHeart();
}
private void SendHeart()
{
var lst = jtdata.Package(0x0002, SimKey);
SendAnswer(lst);
}
#region 链路
public void Receive(object sender, ChannelReceiveArg arg)
{
splitData.SplitDataBy7E(arg.Data, ((byte[] bGps) =>
{
if (bGps.Length > 11 && CheckHelper.CheckXOR(bGps, 0, bGps.Length – 1) == bGps[bGps.Length – 1])//效验通过
{
var head = JTHeader.NewEntity(bGps);
JXData(bGps, head);
}
}));
}
public void JXData(byte[] bGps, JTHeader head)
{
switch (head.MsgId)
{
case 0x8001://通用应答
break;
case 0x8B00://4.1.1 实时音视频传输请求
if (thVideo != null && thVideo.IsAlive)
{
Console.WriteLine(“已经在传输”);
}
else
{
BaseLineTime = DateTime.Now;
SerialNumber = 0;
LastIFrameTime = DateTime.MinValue;
LastFrameTime = DateTime.MinValue;
var req = JTRealVideoTransferRequest.NewEntity(bGps, head.HeadLen);
thVideo = new Thread(StartVideo);
thVideo.IsBackground = true;
iPEndPoint = new IPEndPoint(IPAddress.Parse(req.IPAddress), req.UdpPort);
thVideo.Start(iPEndPoint);
}
break;
default:
break;
}
}
public void DataSend(object sender, ChannelSendArg arg)
{
}
public bool SendAnswer(JTPData data)
{
foreach (var item in data)
{
channel.Send(item.Data.ToArray());
//return true;
}
return true;
}
客户端窗体线程处理视频数据:
unsafe void StartVideo(object ep)
{
try
{
send_sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
send_sock.SendBufferSize = 1000000;
cam2H264.Run(setting.VideoDevice, SendToServer, 0);
}
catch (Exception ex)
{
}
}
具体264的处理基本上可以参考FFmpeg的C实现方式来做,下面是步骤:
请参考:https://github.com/FFmpeg/FFmpeg
视频数据包转发服务端
注:实际服务端未做转码处理,已在客户端线程中处理。
服务端窗体常量:
// 开启本机TCP连接
Network.TCPServer ser = new Network.TCPServer(“0.0.0.0”, 9700);
// RTP服务端
RTPServer rtServer;
// 连接到的客户端列表
List<VideoClient> lstTCPChannel = new List<VideoClient>();
// 数据包拆分对象
JX.SplitData splitData = new JX.SplitData();
服务端窗体事件绑定与客户端连接自动发现:
#region 窗体事件
private void frmServer_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
ser.Stop();
}
catch
{
}
try
{
rtServer.Dispose();
}
catch
{
}
}
private void btnStart_Click(object sender, EventArgs e)
{
var vc = lstTCPChannel[lstBoxTcp.SelectedIndex];
//rtServer.AddClient(vc.SimKey, “rtmp://localhost:32768/rtmplive/S” + vc.SimKey);
rtServer.AddClient(vc.SimKey, “rtmp://172.16.20.10:1935/live/S” + vc.SimKey);// 添加直播客户端—RTMP推流服务器地址和流名称
vc.SendVideoCtrl(new JTRealVideoTransferRequest
{
IPAddress = “127.0.0.1”,
UdpPort = 9700,
Channel = 0,
DataType = 1,
StreamType = StreamType.SubStream
});
}
private void frmServer_Load(object sender, EventArgs e)
{
rtServer = new RTPServer(9700, pictureBox1);//RTP服务端图像传输对象
rtServer.Start();
ser.ChannelConnect += new Network.EventChannelConnect(ser_ChannelConnect);// 通道连接事件
ser.ChannelDispose += new Network.EventChannelDispose(ser_ChannelDispose);// 通道连接断开事件
ser.Start();
Network.Utils.Setup(10);
}
#endregion
服务端窗体客户端连接列表控制:
#region 列表控制
VideoClient AddToList(Channel channel)
{
var item = new VideoClient(channel, splitData);
channel.Tag = item;
lstTCPChannel.Add(item);
//rtServer.AddClient(item.SimKey, “rtmp://localhost:32768/rtmplive/S” + item.SimKey);
rtServer.AddClient(item.SimKey, “rtmp://172.16.20.10:1935/live/S” + item.SimKey);// 添加直播客户端—RTMP推流服务器地址和流名称
lstBoxTcp.BeginInvoke(new MethodInvoker(() =>
{
lstBoxTcp.Items.Add(channel.RemoteHost + “:” + channel.RemotePort);
}));
return item;
}
void RemoveList(Channel channel)
{
var item = (VideoClient)channel.Tag;
lstTCPChannel.Remove(item);
rtServer.RemoveClient(item.SimKey);
lstBoxTcp.Invoke(new MethodInvoker(() =>
{
lstBoxTcp.Items.Remove(channel.RemoteHost + “:” + channel.RemotePort);
}));
}
#endregion
服务端窗体通信链路事件方法:
#region 链路相关
void ser_ChannelDispose(object sender, Network.ChannelDisposeArg arg)
{
RemoveList(arg.Channel);
}
void ser_ChannelConnect(object sender, Network.ChannelConnectArg arg)
{
var item = AddToList(arg.Channel);
arg.Channel.DataReceive = item.Receive;
arg.Channel.DataSend = item.DataSend;
arg.Channel.StartReceiveAsync();
}
#endregion
大致代码的思路都在此了。
需要思考的问题
想达到这样的目的:视频能够一边下载一边播放。如何编程实现服务器端的视频流信息向客户端的发送? 客服端又如何接收并播放视频?
使用System.Net的NetStream和使用System.IO的FileStream、MediaPlayer播放插件。
(1)在服务端建立一个Socket服务,将文件分段放入缓冲区。
(2)在客户端建立一个Socket客户端,读取服务端的缓冲区内容。
(3)将读到的部分发送给MediaPlayer进行播放。
上面的客户端和服务端的情况就是按照这种思路来写的,后续需要加入视频预览、播放的功能。实际上就是将H264流数据转成YUV/RGB->Bitmap前端进行展示,然后加入音频数据传输的线程等进行处理同步播放。
上图是服务器端获取到的H264->YUV播放示例。
————————————————
版权声明:本文为CSDN博主「boonya」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/boonya/article/details/78798853/