C# - 捕获 RTP 流并发送到语音识别

Posted

技术标签:

【中文标题】C# - 捕获 RTP 流并发送到语音识别【英文标题】:C# - Capture RTP Stream and send to speech recognition 【发布时间】:2013-03-30 23:52:17 【问题描述】:

我想要完成的工作:

在 C# 中捕获 RTP 流 将该流转发到 System.Speech.SpeechRecognitionEngine

我正在创建一个基于 Linux 的机器人,它将接受麦克风输入,将其发送给 Windows 机器,该机器将使用 Microsoft Speech Recognition 处理音频并将响应发送回机器人。机器人可能距离服务器数百英里,所以我想通过 Internet 执行此操作。

到目前为止我做了什么:

让机器人使用 FFmpeg(机器人在运行 Arch Linux 的 Raspberry Pi 上运行)生成以 MP3 格式(其他可用格式)编码的 RTP 流 使用 VLC ActiveX 控件在客户端计算机上捕获的流 发现 SpeechRecognitionEngine 有可用的方法:
    recognizer.SetInputToWaveStream() recognizer.SetInputToAudiostream() recognizer.SetInputToDefaultAudioDevice()
查看使用 JACK 将应用程序的输出发送到 line-in,但完全被它弄糊涂了。

我需要什么帮助:

我不知道如何将流从 VLC 实际发送到 SpeechRecognitionEngine。 VLC 根本不公开流。有没有一种方法可以捕获一个流并将该流对象传递给 SpeechRecognitionEngine?还是 RTP 不是这里的解决方案?

提前感谢您的帮助。

【问题讨论】:

无需通过互联网发送音频。您可以使用离线语音识别引擎CMUSphinx 存档即时响应。 CMUSphinx 引擎的准确性与 Microsoft 引擎没有太大区别,它可以在 Raspberry Pi 本身上完美运行。 我正在使用仅在 Windows 上运行的高质量语音合成引擎,所以很遗憾我无法在 Pi 上包含所有内容。 AI 还将执行计算密集型任务,这些任务超出了 Pi 的处理能力。 【参考方案1】:

这是一个旧线程,但对我正在进行的项目很有用。但是,我和其他一些尝试使用 dgreenheck 的代码并将 Windows PC 作为源代码的人遇到了同样的问题。

让 FFMpeg 使用以下参数对代码进行 0 次更改:

ffmpeg -ac 1 -f dshow -i audio="recording device" -ar 16000 -acodec pcm_s16le -f rtp rtp://hostname:port

在我的情况下,录音设备名称是“麦克风(Realtek High Definition Audio)”,但我使用以下内容获取录音设备名称:

ffmpeg -list_devices true -f dshow -i dummy

【讨论】:

【参考方案2】:

经过大量工作,我终于让Microsoft.SpeechRecognitionEngine 接受了 WAVE 音频流。流程如下:

在 Pi 上,我正在运行 ffmpeg。我使用此命令流式传输音频

ffmpeg -ac 1 -f alsa -i hw:1,0 -ar 16000 -acodec pcm_s16le -f rtp rtp://XXX.XXX.XXX.XXX:1234

在服务器端,我创建了一个UDPClient 并侦听端口 1234。我在单独的线程上接收数据包。首先,我剥离 RTP 标头 (header format explained here) 并将有效负载写入一个特殊的流。我必须使用 SpeechStreamer 类 described in Sean's response 才能使 SpeechRecognitionEngine 工作。它不适用于标准的 Memory Stream

在语音识别方面我唯一需要做的就是将输入设置为音频流而不是默认音频设备。

recognizer.SetInputToAudioStream( rtpClient.AudioStream,
    new SpeechAudioFormatInfo(WAVFile.SAMPLE_RATE, AudioBitsPerSample.Sixteen, AudioChannel.Mono));

我还没有对它进行过广泛的测试(即让它播放几天,看看它是否仍然有效),但我可以将音频样本保存在 SpeechRecognized 中,这听起来很棒。我正在使用 16 KHz 的采样率。我可能会将其降低到 8 KHz 以减少数据传输量,但一旦出现问题我会担心。

我还应该提到,响应速度非常快。我可以说一个完整的句子并在不到一秒钟的时间内得到回应。 RTP 连接似乎给进程增加了很少的开销。我将不得不尝试一个基准测试并将其与仅使用 MIC 输入进行比较。

编辑:这是我的 RTPClient 类。

    /// <summary>
    /// Connects to an RTP stream and listens for data
    /// </summary>
    public class RTPClient
    
        private const int AUDIO_BUFFER_SIZE = 65536;

        private UdpClient client;
        private IPEndPoint endPoint;
        private SpeechStreamer audioStream;
        private bool writeHeaderToConsole = false;
        private bool listening = false;
        private int port;
        private Thread listenerThread; 

        /// <summary>
        /// Returns a reference to the audio stream
        /// </summary>
        public SpeechStreamer AudioStream
        
            get  return audioStream; 
        
        /// <summary>
        /// Gets whether the client is listening for packets
        /// </summary>
        public bool Listening
        
            get  return listening; 
        
        /// <summary>
        /// Gets the port the RTP client is listening on
        /// </summary>
        public int Port
        
            get  return port; 
        

        /// <summary>
        /// RTP Client for receiving an RTP stream containing a WAVE audio stream
        /// </summary>
        /// <param name="port">The port to listen on</param>
        public RTPClient(int port)
        
            Console.WriteLine(" [RTPClient] Loading...");

            this.port = port;

            // Initialize the audio stream that will hold the data
            audioStream = new SpeechStreamer(AUDIO_BUFFER_SIZE);

            Console.WriteLine(" Done");
        

        /// <summary>
        /// Creates a connection to the RTP stream
        /// </summary>
        public void StartClient()
        
            // Create new UDP client. The IP end point tells us which IP is sending the data
            client = new UdpClient(port);
            endPoint = new IPEndPoint(IPAddress.Any, port);

            listening = true;
            listenerThread = new Thread(ReceiveCallback);
            listenerThread.Start();

            Console.WriteLine(" [RTPClient] Listening for packets on port " + port + "...");
        

        /// <summary>
        /// Tells the UDP client to stop listening for packets.
        /// </summary>
        public void StopClient()
        
            // Set the boolean to false to stop the asynchronous packet receiving
            listening = false;
            Console.WriteLine(" [RTPClient] Stopped listening on port " + port);
        

        /// <summary>
        /// Handles the receiving of UDP packets from the RTP stream
        /// </summary>
        /// <param name="ar">Contains packet data</param>
        private void ReceiveCallback()
        
            // Begin looking for the next packet
            while (listening)
            
                // Receive packet
                byte[] packet = client.Receive(ref endPoint);

                // Decode the header of the packet
                int version = GetRTPHeaderValue(packet, 0, 1);
                int padding = GetRTPHeaderValue(packet, 2, 2);
                int extension = GetRTPHeaderValue(packet, 3, 3);
                int csrcCount = GetRTPHeaderValue(packet, 4, 7);
                int marker = GetRTPHeaderValue(packet, 8, 8);
                int payloadType = GetRTPHeaderValue(packet, 9, 15);
                int sequenceNum = GetRTPHeaderValue(packet, 16, 31);
                int timestamp = GetRTPHeaderValue(packet, 32, 63);
                int s-s-rcId = GetRTPHeaderValue(packet, 64, 95);

                if (writeHeaderToConsole)
                
                    Console.WriteLine("0 1 2 3 4 5 6 7 8",
                        version,
                        padding,
                        extension,
                        csrcCount,
                        marker,
                        payloadType,
                        sequenceNum,
                        timestamp,
                        s-s-rcId);
                

                // Write the packet to the audio stream
                audioStream.Write(packet, 12, packet.Length - 12);
            
        

        /// <summary>
        /// Grabs a value from the RTP header in Big-Endian format
        /// </summary>
        /// <param name="packet">The RTP packet</param>
        /// <param name="startBit">Start bit of the data value</param>
        /// <param name="endBit">End bit of the data value</param>
        /// <returns>The value</returns>
        private int GetRTPHeaderValue(byte[] packet, int startBit, int endBit)
        
            int result = 0;

            // Number of bits in value
            int length = endBit - startBit + 1;

            // Values in RTP header are big endian, so need to do these conversions
            for (int i = startBit; i <= endBit; i++)
            
                int byteIndex = i / 8;
                int bitShift = 7 - (i % 8);
                result += ((packet[byteIndex] >> bitShift) & 1) * (int)Math.Pow(2, length - i + startBit - 1);
            
            return result;
        
    

【讨论】:

知道如何在 Windows 上使用 VLC 或在 Windows 上使用 ffmpeg 测试 RTPClient 吗? 不要使用 RTP。使用ffmpeg ... -acodec pcm_s16le -f s16le tcp://... 通过 TCP 发送原始音频样本。现在您无需处理 RTP 协议 TCP 可确保您按顺序可靠地接收数据包。【参考方案3】:

我认为你应该让它更简单。为什么要使用 RTP 和一个特殊的库来捕获 RTP?为什么不直接从 Rasperry Pi 获取音频数据并使用 Http Post 将其发送到您的服务器?

请记住,System.Speech 不支持 MP3 格式。这可能会有所帮助 - Help with SAPI v5.1 SpeechRecognitionEngine always gives same wrong result with C#。对于 System.Speech,音频必须为 PCM、ULaw 或 ALaw 格式。确定您的识别器支持哪些格式的最可靠方法是使用 RecognizerInfo.SupportedAudioFormats 询问它。

然后您可以将数据发布到您的服务器(并使用 ContentType = "audio/x-wav")。我们使用了像

这样的 Url 格式
http://server/app/recognize/sampleRate/bits/isStereo

在请求中包含音频参数。在 POST 正文中发送捕获的 wav 文件。

我们遇到的一个问题是我们必须在将数据发送到 System.Speech 之前将 WAV 文件头添加到数据中。我们的数据是 PCM,但不是 WAV 格式。如果您需要这样做,请参阅https://ccrma.stanford.edu/courses/422/projects/WaveFormat/。

【讨论】:

除了发送 wav 文件将不允许您存档即时响应。发送操作需要时间,响应需要时间。如果正确实施基于 RTP 的解决方案,或者更好的是,MRCP 可以为您提供比发送整个文件的实施更短 10 倍的响应时间。 true,但是对于可能通过 Internet 接收语音命令并在桌面 Windows 机器上进行处理的机器人(假设因为正在使用 System.Speech),我有一种添加的感觉延迟不是问题。 感谢您的回复。实际上,我最终确实让它在 RTP 上工作。我会发布我所做的,以防其他人正在研究如何做到这一点。

以上是关于C# - 捕获 RTP 流并发送到语音识别的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将实时数据发送到 Bing 语音识别?

vb.net 捕获系统音频流

MRCP协议相关学习及语音识别合成

百度语音识别TTS REST API 用C# 怎么处理下行数据 求实例

没有预定义语法的 C# 语音识别

如何在 C# 中使用 SpInprocRecoContext 识别语音事件?