C#FFmpeg視訊採集與推送RTMP伺服器程式碼思路整理
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實現方式來做,下面是步驟:
視訊資料包轉發服務端
注:實際服務端未做轉碼處理,已在客戶端執行緒中處理。
服務端窗體常量:
// 開啟本機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播放示例。