namespace Yuuna.Recognition.Speech { using Google.Apis.Auth.OAuth2; using Google.Cloud.Speech.V1; using Grpc.Auth; using Grpc.Core; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; public sealed class SpeechRecognizer : ISpeechRecognizer { private readonly SpeechClient _speech; private readonly RecognitionConfig _config; private readonly IRecorder _recorder; private readonly object _lock = new object(); private volatile bool _started; /// /// 建立新 實體。 /// /// /// public static ISpeechRecognizer Create(string secret) { if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentException("secret is null or empty.", nameof(secret)); return new SpeechRecognizer(new FileInfo(secret)); } private SpeechRecognizer(FileInfo secret) { if (secret is null) throw new ArgumentNullException(nameof(secret)); if (!secret.Exists) throw new FileNotFoundException("secret file not found.", secret.FullName); var credential = GoogleCredential.FromFile(secret.FullName); var channel = new Channel(SpeechClient.DefaultEndpoint.Host, credential.ToChannelCredentials()); this._speech = SpeechClient.Create(channel); this._config = new RecognitionConfig { Encoding = RecognitionConfig.Types.AudioEncoding.Linear16, SampleRateHertz = 16_000, LanguageCode = Thread.CurrentThread.CurrentCulture.Name, }; this._recorder = new WaveRecorder(); this._recorder.Completed += this.OnComplete; } private void OnComplete(Stream stream) { lock (this._lock) { var wavStream = RecognitionAudio.FromStream(stream); var response = this._speech.Recognize(this._config, wavStream); var alternatives = from r in response.Results from a in r.Alternatives select new AlternativeBridge(a) as IAlternative; this.RecognizeCompleted?.Invoke(alternatives.ToList()); } } public event Action> RecognizeCompleted; public IDisposable Recognize() { lock (this._lock) { if (this._started) return null; this._started = true; return new Duration(this._recorder, () => { this._started = false; }); } } } }