.NET Core C#系列之AKStream.NET摄像头国标GB28181语音对讲之广播请求(信令服务-->摄像头)
作者 : Jacky 发布于 2023-08-20 19:05:00 浏览 319 次

内容比较多,先一步步的讲吧。发送语音对讲之前,先发用对讲的信令。

广播请求(信令服务—>摄像头)

至于抓包分析,网上分享的很多了。其实都是发送 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)