首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在rtsp反向通道上发送音频数据?

如何在rtsp反向通道上发送音频数据?
EN

Stack Overflow用户
提问于 2019-12-24 08:25:59
回答 1查看 7.2K关注 0票数 9

我正在使用一个支持音频输出(音频后通道)的IP摄像头。我想做的是实时流我的个人电脑麦克风音频数据通过提供的RTSP URL,使任何话在PC麦克风将在摄像头扬声器结束时听到。我读过关于onvif流规范的文章,它告诉我,一旦我获得了摄像机媒体的RTSP url,我就必须将我的音频数据发送到提供的rtsp url上,以便在摄像机端输出音频。此外,我的相机支持Onvif配置文件T。

到目前为止我试过的是-

代码语言:javascript
复制
  public static RtspClient rtspClient;
  public static IWaveIn sourceStream;

  private static void CallAudio()
    {
        string CameraIp = "192.168.1.69";
        string UserName = "admin";
        string Password = "admin123";
        var ClientMessageInspector = new ClientMessageInspector(UserName, Password);

        //Call Device Url and get Services.
        string DeviceServiceUrl = "http://" + CameraIp + "/onvif/device_service";
        var deviceClient = new DeviceClient("DeviceBinding", new EndpointAddress(DeviceServiceUrl));
        deviceClient.Endpoint.Behaviors.Add(ClientMessageInspector);
        var getServices = deviceClient.GetServices(false);                   
        
        //Call media2 getStreamingUri.
        string url = "http://" + CameraIp + "/onvif/media2_service";
        var Media2Client = new Media2Client("Media2Binding", new EndpointAddress(url));
        Media2Client.Endpoint.Behaviors.Add(ClientMessageInspector);
        var media2GetProfiles = Media2Client.GetProfiles(null, null);            
        var resp = Media2Client.GetAudioDecoderConfigurationOptions(null, null);
        var responseGetAudioStreamUri = Media2Client.GetStreamUri("tcp", profiles[0].token);  //This gets rtsp url of media from camera.

        rtspClient = new RtspClient(responseGetAudioStreamUri, UserName, Password);            
        sourceStream = new WaveInEvent();
        sourceStream.WaveFormat = new WaveFormat(64, 8, 1);   //8000 16
        sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(SourceStream_DataAvailable);
        sourceStream.StartRecording();

        Console.ReadKey();
    }

    //This method gets data from PC microphone and enocodes it into Mu-Law G711 and send to rtsp url.
    private static void SourceStream_DataAvailable(object sender, WaveInEventArgs e)
    {
        byte[] encoded = TwoWayAudio_Encode_MuLaw(e.Buffer, 0, e.BytesRecorded);
        rtspClient.SendData(encoded, encoded.Length, 3);
    }

    private static byte[] TwoWayAudio_Encode_MuLaw(byte[] data, int offset, int length)
    {
        byte[] encoded = new byte[length / 2];
        int outIndex = 0;
        for (int n = 0; n < length; n += 2)
        {
            encoded[outIndex++] = MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(data, offset + n));
        }
        return encoded;
    }

在我的项目中用于描述、设置和播放rtsp的rtsp客户端是从这个github https://github.com/BogdanovKirill/RtspClientSharp中获取的。

Rtspclient.cs

代码语言:javascript
复制
using Rtsp;
using Rtsp.Messages;
using Rtsp.Sdp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace Rtsp
{
   public class RtspClient
   {
    private RtspListener rtsp_client;
    private RtspTcpTransport tcp_socket;
    public string url;
    public bool canPlay = false;
    public string username;
    public string password;
    public ushort seqNo = 0;
    public event EventHandler<string> RtspError;
    public event EventHandler<byte[]> RtpDataReceived;
    public Stopwatch stopwatch { get; private set; }

    public RtspClient(string _url, string _username, string _password)
    {
        url = _url;
        username = _username;
        password = _password;

        var uri = new Uri(_url);

        tcp_socket = new RtspTcpTransport(uri.Host, 554); // 554);

        if (tcp_socket.Connected == false)
        {
            Console.WriteLine("Error - did not connect");
            return;
        }

        // Connect a RTSP Listener to the TCP Socket to send messages and listen for replies
        rtsp_client = new RtspListener(tcp_socket);

        rtsp_client.MessageReceived += Rtsp_client_MessageReceived;
        rtsp_client.DataReceived += DataReceived;
        rtsp_client.Start(); // start reading messages from the server
        rtsp_client.AutoReconnect = true;
        RtspRequest describe_message = new RtspRequestDescribe();
        describe_message.RtspUri = uri;            
        describe_message.AddHeader("Accept: application/sdp");
        describe_message.AddHeader("Require: www.onvif.org/ver20/backchannel");
        rtsp_client.SendMessage(describe_message);            

        stopwatch = new Stopwatch();
        stopwatch.Start();
    }

    private void DataReceived(object sender, RtspChunkEventArgs e)
    {
        int rtp_version = (e.Message.Data[0] >> 6);
        int rtp_padding = (e.Message.Data[0] >> 5) & 0x01;
        int rtp_extension = (e.Message.Data[0] >> 4) & 0x01;
        int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F;
        int rtp_marker = (e.Message.Data[1] >> 7) & 0x01;
        int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F;
        uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]);
        uint rtp_timestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]);
        uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]);

        int rtp_payload_start = 4 // V,P,M,SEQ
                            + 4 // time stamp
                            + 4 // ssrc
                            + (4 * rtp_csrc_count); // zero or more csrcs

        uint rtp_extension_id = 0;
        uint rtp_extension_size = 0;
        if (rtp_extension == 1)
        {
            rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0);
            rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0);
            rtp_payload_start += 4 + (int)rtp_extension_size;  // extension header and extension payload
        }

        Console.WriteLine("RTP Data"
                           + " V=" + rtp_version
                           + " P=" + rtp_padding
                           + " X=" + rtp_extension
                           + " CC=" + rtp_csrc_count
                           + " M=" + rtp_marker
                           + " PT=" + rtp_payload_type
                           + " Seq=" + rtp_sequence_number
                           + " Time=" + rtp_timestamp
                           + " SSRC=" + rtp_ssrc
                           + " Size=" + e.Message.Data.Length);


        // If rtp_marker is '1' then this is the final transmission for this packet.
        // If rtp_marker is '0' we need to accumulate data with the same timestamp

        // ToDo - Check Timestamp matches

        // Add to the tempoary_rtp List
        if (rtp_payload_type == 98 || rtp_payload_type == 0)
        {
            byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed
            System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload
            RtpDataReceived?.Invoke(null, rtp_payload);
        }
    }

    public bool SendData(byte[] data, int count, int channel)
    {
        byte[] rtp_packet = new byte[12 + data.Length];
        int rtp_version = 2;
        int rtp_padding = 0;
        int rtp_extension = 0;
        int rtp_csrc_count = 0;
        int rtp_marker = 1; // set to 1 if the last NAL in the array
        //int rtp_payload_type = 98;
        int rtp_payload_type = 0;

        RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

        RTPPacketUtil.WriteSequenceNumber(rtp_packet, seqNo);
        seqNo++;
        RTPPacketUtil.WriteTS(rtp_packet, (uint)stopwatch.ElapsedMilliseconds);

        UInt32 empty_ssrc = 1293847657;
        RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

        // Now append the raw NAL
        System.Array.Copy(data, 0, rtp_packet, 12, data.Length);

        if (canPlay)
        {
            rtsp_client.SendData(channel, rtp_packet);
            return true;
        }
        else return false;
    }

    private void Rtsp_client_MessageReceived(object sender, RtspChunkEventArgs e)
    {
        RtspResponse message = e.Message as RtspResponse;
        if (message.ReturnCode == 500)
        {
            RtspError?.Invoke(this, "Internal Server Error");
        }
        if (message.ReturnCode == 401)
        {
            Rtsp.Messages.RtspRequest msg = null;
            switch (message.OriginalRequest.Method)
            {
                case "DESCRIBE":
                    msg = new RtspRequestDescribe();
                    break;
                case "SETUP":
                    msg = new RtspRequestSetup();
                    break;
                default:
                    break;
            }
            msg.RtspUri = new Uri(url);
            var header = message.Headers["WWW-Authenticate"];
            var _realm = GrabHeaderVar("realm", header);
            var _nonce = GrabHeaderVar("nonce", header);
            var ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", username, _realm, password));
            var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", message.OriginalRequest.Method, url));
            var digestResponse = CalculateMd5Hash(string.Format("{0}:{1}:{2}", ha1, _nonce, ha2));

            var digest = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\" ",
                username, _realm, _nonce, url, digestResponse);
            msg.AddHeader("Authorization: " + digest);
            msg.AddHeader("Accept: application/sdp");
            rtsp_client.SendMessage(msg);
            return;
        }
        Console.WriteLine("Received " + message.OriginalRequest.ToString());

        if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestDescribe)
        {
            // Got a reply for DESCRIBE
            // Examine the SDP
            Console.Write(Encoding.UTF8.GetString(message.Data));

            SdpFile sdp_data;
            using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data)))
            {
                sdp_data = SdpFile.Read(sdp_stream);
            }

            // Process each 'Media' Attribute in the SDP.
            // If the attribute is for Video, then send a SETUP
            for (int x = 0; x < sdp_data.Medias.Count; x++)
            {
                if (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.audio || sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video)
                {
                    // seach the atributes for control, fmtp and rtpmap
                    String control = "";  // the "track" or "stream id"
                    String fmtp = ""; // holds SPS and PPS
                    String rtpmap = ""; // holds the Payload format, 96 is often used with H264
                    foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs)
                    {
                        if (attrib.Key.Equals("control")) control = attrib.Value;
                        if (attrib.Key.Equals("fmtp")) fmtp = attrib.Value;
                        if (attrib.Key.Equals("rtpmap")) rtpmap = attrib.Value;
                    }

                    // Get the Payload format number for the Video Stream
                    String[] split_rtpmap = rtpmap.Split(' ');
                    var video_payload = 0;
                    bool result = Int32.TryParse(split_rtpmap[0], out video_payload);
                                      
                    // Send SETUP for the Video Stream
                    // using Interleaved mode (RTP frames over the RTSP socket)
                    Rtsp.Messages.RtspRequest setup_message = new Rtsp.Messages.RtspRequestSetup();
                    setup_message.RtspUri = new Uri(url + "/" + control);                        
                    //setup_message.AddHeader("Transport: RTP/AVP/TCP;interleaved=0");                       
                    setup_message.AddHeader("Require: www.onvif.org/ver20/backchannel");                                              
                    rtsp_client.SendMessage(setup_message);                                            
                }
            }
        }

        if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestSetup)
        {
            // Got Reply to SETUP
            Console.WriteLine("Got reply from Setup. Session is " + message.Session);

            String session = message.Session; // Session value used with Play, Pause, Teardown

            // Send PLAY
            RtspRequest play_message = new RtspRequestPlay();
            play_message.RtspUri = new Uri(url);               
            play_message.Session = session;
            play_message.AddHeader("Require: www.onvif.org/ver20/backchannel");
            rtsp_client.SendMessage(play_message);
        }

        if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestPlay)
        {
            // Got Reply to PLAY
            Console.WriteLine("Got reply from Play  " + message.Command);
            canPlay = true;

        }
    }

    private static string GrabHeaderVar(string varName, string header)
    {
        var regHeader = new Regex(string.Format(@"{0}=""([^""]*)""", varName));
        var matchHeader = regHeader.Match(header);
        if (matchHeader.Success)
            return matchHeader.Groups[1].Value;
        throw new ApplicationException(string.Format("Header {0} not found", varName));
    }

    private static string CalculateMd5Hash(string input)
    {
        var inputBytes = Encoding.ASCII.GetBytes(input);
        var hash = MD5.Create().ComputeHash(inputBytes);
        var sb = new StringBuilder();
        foreach (var b in hash)
            sb.Append(b.ToString("x2"));
        return sb.ToString();
    }

    public void Dispose()
    {
        rtsp_client.Stop();
        rtsp_client.Dispose();
    }
}

}

因此,在调用任何rtsp方法之前,我添加了Rtsp Require: www.onvif.org/ver20/backchannel,这对于检查摄像机是否支持AudioBack通道非常重要。

调用“描述”、“设置”和“播放”之后得到的输出是可以的。

代码语言:javascript
复制
Received Rtsp.Messages.RtspRequestDescribe
v=0
o=- 0 0 IN IP4 192.168.1.69
s=LIVE VIEW
c=IN IP4 0.0.0.0
t=0 0
a=control:rtsp://192.168.1.69/rtsp_tunnel?p=0&h26x=4&aon=1&aud=0
m=video 0 RTP/AVP 35
a=rtpmap:35 H264/90000
a=control:rtsp://192.168.1.69/rtsp_tunnel?p=0&h26x=4&aon=1&aud=0&stream=video
a=recvonly
a=fmtp:35 packetization-mode=1;profile-level-id=4d0029;sprop-parameter- 
sets=Z00AKZpkA8ARPy4C1BQEFAg=,aO48gA==
m=audio 0 RTP/AVP 96
a=rtpmap:96 mpeg4-generic/16000/1
a=fmtp:96 streamtype=5; profile-level-id=5; mode=AAC-hbr; config=1408; SizeLength=13; IndexLength=3; 
IndexDeltaLength=3
a=control:rtsp://192.168.1.69/rtsp_tunnel?p=0&h26x=4&aon=1&aud=0&stream=audio
a=recvonly
m=audio 0 RTP/AVP 0
a=rtpmap:0 PCMU/8000/1
a=control:rtsp://192.168.1.69/rtsp_tunnel?p=0&h26x=4&aon=1&aud=0&stream=backchannel
a=sendonly
Received Rtsp.Messages.RtspRequestSetup
Got reply from Setup. Session is 12346e9856840dc
Received Rtsp.Messages.RtspRequestSetup
Got reply from Setup. Session is 12346e9856840dc
Received Rtsp.Messages.RtspRequestSetup
Got reply from Setup. Session is 12346e9856840dc
Received Rtsp.Messages.RtspRequestPlay
Got reply from Play  RTSP/1.0 200 OK
Received Rtsp.Messages.RtspRequestPlay
Got reply from Play  RTSP/1.0 200 OK
Received Rtsp.Messages.RtspRequestPlay
Got reply from Play  RTSP/1.0 200 OK

从Play方法获得响应后,我开始使用Rtsp客户端中的send方法发送编码数据。

但是在摄像机的末端听不到声音。我的问题很简单-

  1. 是否可以通过RTSP URL发送音频数据。
  2. 我调用方法的方式有什么问题吗?有谁能指出这一点吗?
  3. 是否有任何简单的方法(或示例/教程)显示如何在rtsp上做音频反向通道,请提供给。

请不要介意我是Rtsp的新手。提前谢谢。如果问题的任何问题告诉我,我会编辑它,以移动明确的理解。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-11-10 13:08:47

我想出了我做错了什么。实际上,上面提到的步骤很好,相机返回也可以,我所做的错误就在上面的代码中:

代码语言:javascript
复制
sourceStream.WaveFormat = new WaveFormat(64, 8, 1);   //8000 16

而不是64 &8参数,它应该是:

代码语言:javascript
复制
sourceStream.WaveFormat = new WaveFormat(8000, 16, 1);   //8000 16

这都是由于声音放大率的原因,声音发送是听不见的。谢谢!

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59465932

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档