会話の間が空いたときに文字起しするc#プログラム
会話の間が空いた時と、バッファがいっぱいになったときにWhisperを使って文字起しをするプログラムを作りました。
2秒会話の間が空いた時にLLMに問合せすることを目的としています。COTOMOはそれよりも反応が早いですが2秒が精一杯ですね。
いつものように下記をNugetしてください。
実際のコードです。長ったらしい作文になっていますがご了承ください。
2秒はタイマーを使っています。無音が続くとWhisperを呼び出します。少しでも音を拾うとタイマーは停止され、無音が訪れるとまた2秒のカウントを始めます。
using System.Timers;
using NAudio.Wave;
using Whisper.net;
using NAudio.CoreAudioApi;
class Program
{
static void Main(string[] args)
{
var recorder = new AudioRecorder();
Console.WriteLine("Recording... Press Enter to exit.");
Console.ReadLine();
}
}
public class AudioRecorder
{
private static WasapiCapture? waveIn;
private static BufferedWaveProvider? waveBuff;
private static System.Timers.Timer? silenceTimer;
private static bool isSilent;
private static bool isRecording;
private static bool OnSilenceTimer;
private const int SilenceThreshold = 200; // Adjust this value as needed
private const int SilenceDuration = 2000; // 2 seconds in milliseconds
private static string modelFileName = Environment.GetEnvironmentVariable("LLMPATH", System.EnvironmentVariableTarget.User) + @"Whisper\ggml-medium.bin";
private static WhisperFactory? wFactory;
private static WhisperProcessor? wProcessor;
public AudioRecorder()
{
//Whisperの定義
string strPrompt = "The audio provided may have moments of silence, do not make up words to fill in extended times of silence.";
try
{
wFactory = WhisperFactory.FromPath(modelFileName);
wProcessor = wFactory.CreateBuilder().WithLanguage("ja").WithTemperature(0).WithThreads(16).WithPrompt(strPrompt).Build();
isSilent = false;
isRecording = false;
OnSilenceTimer = false;
waveIn = new WasapiCapture();
waveIn.WaveFormat = new WaveFormat(16000, 1);
waveBuff = new BufferedWaveProvider(waveIn.WaveFormat);
//waveBuff.BufferDuration = TimeSpan.FromSeconds(30);
waveBuff.DiscardOnBufferOverflow = true;
waveBuff.ReadFully = false;
waveIn.DataAvailable += OnDataAvailable;
silenceTimer = new System.Timers.Timer(SilenceDuration);
silenceTimer.Elapsed += OnSilenceTimerElapsed;
waveIn.StartRecording();
Console.WriteLine("Record Start");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static async Task WhisperEx()
{
try
{
if (!isRecording)
{
waveBuff.ClearBuffer();
return;
}
//WhisperのProcessAsyncがストリーム形式を欲しがってるからメモリストリームを使用する
MemoryStream ms = new MemoryStream();
ms.Position = 0;
ms.SetLength(0);
WaveFileWriter waveOut = new WaveFileWriter(ms, waveIn.WaveFormat);
//BufferedWaveProviderをメモリストリームに変換
byte[] buffer = new byte[waveBuff.BufferedBytes];
var read = waveBuff.Read(buffer, 0, waveBuff.BufferedBytes);
//waveWiterでメモリストリームに書き込み
waveOut.Write(buffer, 0, read);
waveOut.Flush();
//ms.Position = 0;
//Whisperでメモリストリームを文字に変換
string strChk = "";
string strMsg = "";
string strRst = "";
ms.Seek(0, SeekOrigin.Begin);
await foreach (var rst in wProcessor.ProcessAsync(ms))
{
strRst = rst.Text.Replace("\r\n", "").Replace("\n","");
if(strRst != strChk)
{
strChk = strRst;
strMsg += strRst;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(strRst);
}
}
Console.ResetColor();
Console.WriteLine(strMsg);
//メモリストリームをクリアする
ms.Position = 0;
ms.SetLength(0);
ms.Close();
ms.Dispose();
isRecording = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private void OnDataAvailable(object sender, WaveInEventArgs e)
{
bool isCurrentChunkSilent = true;
try
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
if (Math.Abs(sample) > SilenceThreshold)
{
isCurrentChunkSilent = false;
break;
}
}
if (isCurrentChunkSilent)
{
if (!isSilent)
{
isSilent = true;
silenceTimer.Start();
}
}
else
{
isSilent = false;
isRecording = true; //少しでも話すとON
silenceTimer.Stop();
}
if (isRecording) waveBuff.AddSamples(e.Buffer, 0, e.BytesRecorded);
if (waveBuff.BufferedBytes>=160000 || OnSilenceTimer)
{
OnSilenceTimer=false;
Console.WriteLine("Buffer Over!!");
isSilent = false;
isRecording = true; //少しでも話すとON
silenceTimer.Stop();
Task task = WhisperEx();
task.Wait();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void OnSilenceTimerElapsed(object sender, ElapsedEventArgs e)
{
OnSilenceTimer = true;
}
}
結果
とても残念ですが、無音のときに幻聴がひどくて会話に使えそうにない気がしてきました。何も話していない時に「ご視聴ありがとうございました。」などの文字起しをします。処理速度も会話で使えるレベルではないです。CreateBuilderのパラメータを見ていただければわかると思いますが、考えられそうなものはぶっこんだんですがダメでした。cotomoのレベルにはほど遠いなあ・・・