2013-04-08 209 views
4

我試圖做到:C# - 捕捉RTP流發送到語音識別

  • 捕獲RTP流在C#
  • 轉發流的System.Speech.SpeechRecognitionEngine

我正在創建一個基於Linux的機器人,它將採用麥克風輸入,並將其發送給使用Microsoft語音識別功能處理音頻的Windows機器,並將響應發送回機器人。機器人可能距離服務器數百英里,所以我想通過互聯網做到這一點。

我迄今所做的:

  • 有機器人產生使用FFmpeg的(其他格式有售)MP3格式編碼的RTP流(機器人在樹莓派運行運行的Arch Linux)
  • 使用VLC ActiveX控件在客戶端計算機上捕獲流
  • 發現這個SpeechRecognitionEngine具有可用的方法:
    1. recognizer.SetInputToWaveSt令()
    2. recognizer.SetInputToAudioStream()
    3. recognizer.SetInputToDefaultAudioDevice()
  • 在使用JACK發送該應用線路輸入的輸出來看,但通過將其完全混淆。

我需要什麼幫助:

我卡在如何真正從VLC串流傳送到SpeechRecognitionEngine。 VLC根本不暴露流。有沒有一種方法可以捕獲流並將該流對象傳遞給SpeechRecognitionEngine?或者RTP不是這裏的解決方案?

在此先感謝您的幫助。

+0

沒有必要通過互聯網發送音頻。您可以使用脫機語音識別引擎[CMUSphinx](http://cmusphinx.sourceforge.net)存檔即時響應。 CMUSphinx引擎的精確度與微軟引擎並無太大區別,它完全可以在Raspberry Pi本身上運行。 – 2013-04-08 19:54:40

+0

我使用的是高質量的語音合成引擎,只能在Windows上運行,所以不幸的是我無法在Pi上做所有的事情。人工智能也將執行比Pi可以處理的計算密集型任務。 – dgreenheck 2013-04-08 20:15:34

回答

4

經過很多工作,我終於得到了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)並將有效載荷寫入特殊流。爲了使SpeechRecognitionEngine正常工作,我必須使用SpeechStreamerdescribed in Sean's response。它不符合標準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 ssrcId = 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, 
         ssrcId); 
       } 

       // 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; 
     } 
    } 
+0

任何想法如何在Windows上使用VLC或在Windows上使用ffmpeg來測試RTPClient? – 2013-07-03 19:07:47

1

我認爲你應該保持簡單。爲什麼使用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/

+0

除了發送wav文件不會允許您存檔即時響應。發送操作需要時間和響應需要時間。如果正確實施了基於RTP的解決方案,或者更好的MRCP,則可以爲您提供比發送整個文件的實現小10倍的響應時間。 – 2013-04-09 20:35:31

+1

是真實的,但對於可能通過互聯網接收語音命令的機器人,在桌面Windows機器上進行處理(假設自使用System.Speech之後),我感覺增加的延遲不會是問題。 – 2013-04-09 23:42:01

+0

感謝您的回覆。我其實最終得到它在RTP上工作。如果其他人在考慮如何做到這一點,我會發布我所做的事情。 – dgreenheck 2013-04-10 18:41:25

0

這是一個古老的線程,但對於一個項目我工作非常有用。但是,我和其他一些人試圖在Windows PC上使用dgreenheck的代碼相同的問題。

了FFmpeg的使用下列參數與0更改代碼的工作:

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

在我的情況,記錄設備名稱爲「麥克風(Realtek高保真音頻)」,但我用下面的獲取錄音設備名稱:

ffmpeg -list_devices true -f dshow -i dummy