- XiaoFeng.GB28181 目录
- 国标GB28181理论知识
- 国标GB28181抓包
- 国标GB28181代码实践
- 国标GB28181.XiaoFengAkNVR前端项目
- 国标GB28181.AKStream相关教程
-
- XiaoFeng.GB28181 目录
- 国标GB28181理论知识
- 国标GB28181抓包
- 国标GB28181代码实践
- 国标GB28181.XiaoFengAkNVR前端项目
- 国标GB28181.AKStream相关教程
-
.NET Core C#系列之AKStream.NET摄像头国标GB28181语音对讲之广播请求(信令服务-->摄像头)作者 : Jacky 发布于 2023-08-20 19:05:00 浏览 780 次
内容比较多,先一步步的讲吧。发送语音对讲之前,先发用对讲的信令。
广播请求(信令服务—>摄像头)
至于抓包分析,网上分享的很多了。其实都是发送 Broadcast 广播通知,
然后摄像头是广播通知应答,然后进行sdp 就行协议交换,可以简单理解成端口交换,其实就是音频流要推动到哪个端口,协商完毕,可以发送音频流。音频流发送结束,发送拜拜信令。整个流程就是如此,这里以
https://github.com/chatop2020/AKStream 这个开源项目为例子,带大家了解一下。这里我粘贴一下核心的代码,因为我也是自己测试的,是可以发声了。就是一个demo,大家可以整合到自己系统里。
第一步发送信令:
public bool BroadcastRequest(string deviceId, string channelId) { try { Common.SipServer.BroadcastRequest(deviceId, channelId, _autoResetEvent, _timeout); _commandType = CommandType.Broadcast; var isTimeout = _autoResetEvent.WaitOne(_timeout); if (!isTimeout) { Console.WriteLine($"[{Common.LoggerHead}]->Sip代理语音广播失败"); return false; } return true; } finally { Dispose(); } }
下面的代码是:
public void BroadcastRequest(string deviceId, string channelId, AutoResetEvent evnt, int timeout = 5000) { var tmpSipDevice = _globalData.SipDevices.FindLast(x => x.DeviceId.Equals(deviceId)); if (tmpSipDevice == null) //设备是否在列表中存在 { try { evnt.Set(); } catch (Exception ex) { throw new UserFriendlyException("BroadcastRequest-->AutoResetEvent.Set异常", ErrorNumber.Sys_AutoResetEventExcept.ToString(), details: ex.Message, ex); } return; } SIPMethodsEnum method = SIPMethodsEnum.MESSAGE; VoiceBroadcastNotify voiceBroadcast = new VoiceBroadcastNotify() { CmdType = CommandType.Broadcast, SourceID ="34020000002000000001",//deviceId, TargetID = "34020000001370000001",//"34020000001370000001",//"34020000001320000068",//channelId,//"34020000001370000001", SN = new Random().Next(1, ushort.MaxValue), }; try { string xmlBody = VoiceBroadcastNotify.Instance.Save<VoiceBroadcastNotify>(voiceBroadcast); string subject = $"{_globalData.SipServerConfig.ServerSipDeviceId}:{0},{deviceId}:{new Random().Next(100, ushort.MaxValue)}"; Func<SipDevice, SIPMethodsEnum, string, string, string, CommandType, bool, AutoResetEvent, AutoResetEvent, object, int, Task > request = SendRequestViaSipDevice; request(tmpSipDevice, method, ConstString.Application_MANSCDP, subject, xmlBody, voiceBroadcast.CmdType, true, evnt, null, null, timeout); } catch (AkStreamException ex) { try { evnt.Set(); } catch (Exception e) { throw new UserFriendlyException("BroadcastRequest-->AutoResetEvent.Set异常", ErrorNumber.Sys_AutoResetEventExcept.ToString(), details: e.Message, e); } } }
上面的代码差不多就是协议发完了。
然后重点是发送rtp音频包,这个是我分享的重点。
public async Task PushAudio(string base64String, string deviceId, string channelId, string remoteIpAddress, int remotePort, string localIpAddress, int localPort) { if (udpClient == null) { udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, localPort)); } //byte[] pcmBytes = Convert.FromBase64String(base64String); FileStream fs = new FileStream("C:\\recorder.pcm", FileMode.Open); BinaryReader br = new BinaryReader(fs); long fileSize = fs.Length; byte[] pcmBytes = new byte[fileSize]; br.Read(pcmBytes, 0, (int)fileSize); br.Close(); fs.Close(); var g711data = new Byte[pcmBytes.Length / 2]; for (Int32 i = 0, k = 0; i < pcmBytes.Length - 1; i += 2, k++) { var v = (Int16)(pcmBytes[i + 1] << 8 | pcmBytes[i]); g711data[k] = LinearToAlaw(v); } var bytes = g711data; var ms = new MemoryStream(bytes); var length = 600; Console.WriteLine($"本次发送的rtp次数是:{g711data.Length / length},总长度是:{g711data.Length}"); var packet = new RtpPacket(); while (ms.Position < ms.Length) { var _bytes = new byte[length]; var len = ms.Read(_bytes, 0, length); packet.Write(_bytes, len); packet.SequenceNumber++; packet.Timestamp += length; var packetArray = packet.ToArray(); //发送数据包 udpClient.Send(packetArray, packetArray.Length, new IPEndPoint(IPAddress.Parse(remoteIpAddress), remotePort)); if (len < length) break; await Task.Delay(20); } }
用到的rtp帮助类是:
/// <summary> /// Rtp数据包 /// </summary> public class RtpPacket { #region 构造器 /// <summary> /// 无参构造器 /// </summary> public RtpPacket() { } #endregion #region 属性 /// <summary> /// /// </summary> public int PayloadType { get; set; } = 8; /// <summary> /// /// </summary> public int SequenceNumber { get; set; } = 0; /// <summary> /// /// </summary> public int Timestamp { get; set; } = 0; /// <summary> /// /// </summary> public uint SSRC { get; set; } = 0; /// <summary> /// 内存流 /// </summary> private MemoryStream Data { get; set; } #endregion #region 方法 /// <summary> /// 添加数据流 /// </summary> /// <param name="data"></param> /// <param name="len"></param> public void Write(byte[] data, int len = 0) { this.SetHeader(); this.Data.Write(data, 0, len == 0 ? data.Length : len); } /// <summary> /// 设置头 /// </summary> private void SetHeader() { this.Data = new MemoryStream(); this.WriteByte((byte)0x80); var marker = 1 << 15; var payloadType = (byte)this.PayloadType; payloadType |= (byte)(marker >> 8); this.WriteByte((byte)payloadType); var sequenceNumber = (byte)(this.SequenceNumber >> 8); sequenceNumber |= (byte)(marker & 0xFF); this.WriteByte((byte)sequenceNumber); this.WriteByte((byte)(this.SequenceNumber & 0xFF)); this.WriteByte((byte)(this.Timestamp >> 24)); this.WriteByte((byte)((this.Timestamp >> 16) & 0xFF)); this.WriteByte((byte)((this.Timestamp >> 8) & 0xFF)); this.WriteByte((byte)(this.Timestamp & 0xFF)); this.WriteByte((byte)(this.SSRC >> 24)); this.WriteByte((byte)((this.SSRC >> 16) & 0xFF)); this.WriteByte((byte)((this.SSRC >> 8) & 0xFF)); this.WriteByte((byte)(this.SSRC & 0xFF)); } /// <summary> /// 写字节 /// </summary> /// <param name="b">字节</param> private void WriteByte(byte b) { if (this.Data == null) this.Data = new MemoryStream(); this.Data.WriteByte(b); } /// <summary> /// /// </summary> /// <returns></returns> public byte[] ToArray() => this.Data.ToArray(); #endregion }
这个是音频pcm 转换的代码
private readonly Int16[] seg_end = { 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF }; private readonly Int32 QUANT_MASK = 0xf; private readonly Int32 SEG_SHIFT = 4; private Byte LinearToAlaw(Int16 pcm_val) { Int16 mask; Int16 seg; Char aval; if (pcm_val >= 0) mask = 0xD5; else { mask = 0x55; pcm_val = (Int16)(-pcm_val - 1); if (pcm_val < 0) pcm_val = 32767; } //Convert the scaled magnitude to segment number. seg = Search(pcm_val, seg_end, 8); //Combine the sign, segment, and quantization bits. if (seg >= 8) //out of range, return maximum value. return (Byte)(0x7F ^ mask); else { aval = (Char)(seg << SEG_SHIFT); if (seg < 2) aval |= (Char)(pcm_val >> 4 & QUANT_MASK); else aval |= (Char)(pcm_val >> seg + 3 & QUANT_MASK); return (Byte)(aval ^ mask); } } private static Int16 Search(Int16 val, Int16[] table, Int16 size) { for (Int16 i = 0; i < size; i++) if (val <= table[i]) return i; return size; }
整体核心代码思路都在这里了,后期等我整理好一个漂亮的代码pr到到源码项目里,尽情等待。
所有评论(0)