C# - захват потока RTP и отправка на распознавание речи
Что я пытаюсь сделать:
- Захват потока RTP в C#
- Переслать этот поток в System.Speech.SpeechRecognitionEngine
Я создаю робота на основе Linux, который будет принимать входной сигнал с микрофона, отправлять ему компьютер с Windows, который будет обрабатывать звук с помощью распознавания речи Microsoft и отправлять ответ обратно роботу. Робот может находиться в сотнях миль от сервера, поэтому я хотел бы сделать это через Интернет.
Что я сделал до сих пор:
- Пусть робот сгенерирует поток RTP, закодированный в формате MP3 (доступны другие форматы) с использованием FFmpeg (робот работает на Raspberry Pi с Arch Linux)
- Захваченный поток на клиентском компьютере с использованием элемента управления VLC ActiveX
- Обнаружено, что SpeechRecognitionEngine имеет доступные методы:
- recognizer.SetInputToWaveStream ()
- recognizer.SetInputToAudioStream ()
- recognizer.SetInputToDefaultAudioDevice ()
- Посмотрел на использование JACK для отправки вывода приложения на линейный вход, но был совершенно смущен этим.
Что мне нужно помочь с:
Я застрял на том, как на самом деле отправить поток из VLC в SpeechRecognitionEngine. VLC не раскрывает поток вообще. Есть ли способ, которым я могу просто захватить поток и передать этот объект потока в SpeechRecognitionEngine? Или RTP не является решением здесь?
Заранее спасибо за помощь.
3 ответа
После большой работы я наконец-то получил Microsoft.SpeechRecognitionEngine
принять аудио поток WAVE. Вот процесс:
На Пи у меня работает 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 ( формат заголовка объяснен здесь) и записываю полезную нагрузку в специальный поток. Я должен был использовать SpeechStreamer
класс, описанный в ответе Шона, чтобы SpeechRecognitionEngine работал. Это не работает со стандартом Memory Stream
,
Единственное, что мне нужно было сделать на стороне распознавания речи, это установить входной аудиопоток вместо аудиоустройства по умолчанию.
recognizer.SetInputToAudioStream( rtpClient.AudioStream,
new SpeechAudioFormatInfo(WAVFile.SAMPLE_RATE, AudioBitsPerSample.Sixteen, AudioChannel.Mono));
Я не проводил обширные тесты на нем (то есть позволил ему транслироваться в течение нескольких дней и посмотрел, работает ли он по-прежнему), но я могу сохранить аудиосэмпл в SpeechRecognized
и это звучит великолепно. Я использую частоту дискретизации 16 кГц. Я мог бы снизить частоту до 8 кГц, чтобы уменьшить объем передаваемых данных, но я буду беспокоиться об этом, как только это станет проблемой.
Я также должен отметить, что ответ очень быстрый. Я могу произнести целое предложение и получить ответ менее чем за секунду. Соединение 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;
}
}
Я думаю, вы должны держать это проще. Зачем использовать RTP и специальную библиотеку для захвата RTP? Почему бы просто не взять аудиоданные с Rasperry Pi и использовать Http Post, чтобы отправить их на ваш сервер?
Помните, что System.Speech не поддерживает формат MP3. Это может быть полезно - Справка по SAPI v5.1 SpeechRecognitionEngine всегда дает один и тот же неверный результат с C#. Для System.Speech аудио должно быть в формате PCM, ULaw или ALaw. Самый надежный способ определить, какие форматы поддерживает ваш распознаватель, - опросить его с помощью RecognizerInfo.SupportedAudioFormats.
Затем вы можете опубликовать данные на вашем сервере (и использовать ContentType = "audio/x-wav"). Мы использовали формат URL, как
http://server/app/recognize/{sampleRate}/{bits}/{isStereo}
включить аудио параметры в запрос. Отправьте захваченный файл WAV в теле сообщения POST.
Единственный недостаток, с которым мы столкнулись, - это добавить заголовок файла WAV к данным, прежде чем отправлять их в System.Speech. Наши данные были PCM, но не в формате WAV. Смотрите https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ если вам нужно это сделать.
Это старая ветка, но она была полезна для проекта, над которым я работал. Но у меня были те же проблемы, что и у некоторых других людей, пытающихся использовать код dgreenheck на ПК с Windows в качестве источника.
Получил 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