
Blazor、SignalR、Azureで電話を翻訳する
機械学習モデルによって、私たちはあらゆる種類のすてきなことができるようになる。例えば、リアルタイムの音声翻訳だ。このチュートリアルでは、Vonage番号でかかってきた電話を翻訳する方法を学ぶ。発信者の音声を翻訳し、翻訳されたテキストをすべてフロントエンドに送信します。.NET CoreでホストされたBlazor WebAssembly AppとSignalRを使用することで、このプロセスは驚くほどシームレスになります。
前提条件
Azure Speech Resourceが必要です。 ここで.リソース上の
Keys and Endpointタブから地域とキーの値を引き出します。Visual StudioまたはVisual Studio Code。このデモではVisual Studio 2019を使用します。
私たちのCLI。お持ちでない場合は、以下の方法でインストールできます。
npm install @vonage/cli -gngrokをテストする。必要なのは無料版だけです。
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
コードに直接ジャンプ
もしあなたがすべての前提条件をセットアップしているなら、このチュートリアルをスキップしてコードに直接ジャンプすることができる。アプリは GitHub
スピンアップ・ングロック
ngrok を使用して、ローカルで実行中の ASP.NET Core アプリケーションをインターネットに公開します。ngrok をインストールしたら、コンソールでコマンド ngrok http 5000を実行するだけだ。以下のようなものが表示される:
Example of Ngrok running in the terminal
注意 - このチュートリアルでは Kestrelを使用しています。代わりに IIS Express を使用したい場合は、私たちの 説明- を参照してください。
ここで注意しなければならないのは、転送先URLである。 http://1976e6d913a7.ngrok.io.Vonage番号に電話がかかってくると、VonageはWebHookと呼ばれるHTTP GETリクエストをアプリケーションに送り、Nexmo Call Control Object(NCCO)と呼ばれるものを要求します。アプリケーションは /webhooks/answerでリッスンするので、必要なURLはすべて http://1976e6d913a7.ngrok.io/webhooks/answer.
CLIの設定
CLIをまだセットアップしていない場合は、次のコマンドを実行してください。 vonage config:set --apiKey=API_KEY --apiSecret=API_SECRETここで、APIキーとシークレットは、あなたのアカウントの設定ページにあるAPIキーとシークレットです。 アカウントの設定ページで確認できます。
背番号の購入とアプリケーションの作成
CLIがセットアップされたので、番号を購入し、Vonageアプリケーションを作成し、そのアプリケーションに番号をリンクします。
番号を買う
番号を購入するには、次のコマンドを使用します(あなたの国IDを US)
を入力する。 confirm購入した番号が出力されます。
アプリケーションの作成
次にアプリケーションを作成します。create applicationコマンドは2つのURLを取ります。answer URLはVonageが着信コールを送る先の番号で、event URLはVonageがあなたの番号から発生したイベントを送るURLです。忘れずに 1976e6d913a7をngrok URLのランダムハッシュに置き換えることを忘れないでください:
この操作により、アプリケーションIDと秘密鍵が返される。これら両方の値を保存してください。このチュートリアルではアプリIDのみを使用しますが、秘密鍵はアプリケーションリクエストを認証するために使用します。
アプリケーションをリンクする
次に、新しく購入した番号をアプリケーションにリンクさせる必要があります。番号をリンクすることで、Vonageはその番号で受信したすべてのコールをアプリケーションのWebhook URLに送信するようになります。これを行うには、アプリの作成リクエストで受け取ったアプリケーションIDが必要です。 e7a25242-77a1-42cd-a32e-09febcb375f4そして購入したばかりの電話番号で、次のようなコマンドを実行します:
アプリを作る
これでセットアップと設定は完了したので、次はアプリをビルドする。ターミナルで典型的なソース・ディレクトリに行き、以下のコマンドを実行する:
このコマンドは Blazor WebAssembly アプリケーションを scaffold してくれます。SSLを設定するつもりはありません。
つのcsprojファイルが作成される、
VonageDotnetTranslator.Client - ここでWebAssemblyが定義され、アプリケーションのフロントエンドとなります。
VonageDotnetTranslator.Server - これは私たちのアプリケーションのための.NETコアホストサーバになります。このプロジェクトは、私たちが行う必要があることの大部分を行う場所です。
VonageDotnetTranslator.Shared - これはクライアントとサーバー間の共有データです。
NuGetパッケージの追加
このサンプルでは以下のNuGetパッケージを使用します:
ボネージ
Microsoft.aspnetcore.signalr.core
Microsoft.CognitiveServices.Speech
Microsoft.aspnetcore.signalr.クライアント
これらをインストールするには、まずターミナルで VonageDotnetTranslator\Serverに移動し、以下のコマンドを実行する:
次に、ターミナルで VonageDotnetTranslator\Clientに移動して実行する:
このコマンドを実行すると、必要なパッケージがすべてインストールされます。次にVisual StudioでVonageDotnetTranslator.slnファイルを開きます。
モデルを追加する
クライアントとサーバーの間で共有されるデータのモデルを作成します。このために、1つのクラスを使用します。 Translation.このクラスには、翻訳するコールの一意な識別子、翻訳イベントのテキスト、話された言語、翻訳された言語が格納されます。共有プロジェクトの Translation.csを作成し、そこに以下を追加します:
public class Translation
{
public string UUID { get; set; }
public string Text { get; set; }
public string LanguageSpoken { get; set; }
public string LanguageTranslated { get; set; }
} 翻訳ハブの追加
SignalR接続を使って、フロントエンドで翻訳イベントを受信します。バックエンドからフロントエンドへの通信には、Hub接続を使用します。これを使うには、サーバーにHubを定義する必要がある。サーバ・プロジェクトにHubsフォルダを追加し、それから TranslationHubというクラスを追加し、それをパブリック・クラスにして Microsoft.AspNetCore.SignalR.Hubs.このクラスには他のロジックは必要ありません。
翻訳者の構築
このプロジェクトで最も複雑なクラスはトランスレーターになります。ここでは TranslationEngine.まず TranslationEngine.csファイルを VonageDotnetTranslator.Serverプロジェクトにファイルを作りましょう。この TranslationEngineは、いくつかの基本的なシステム・リソースを扱うことになる。その結果 TranslationEngineは IDisposableインターフェイスを実装します。クラス定義は以下のようになる:
public class TranslationEngine : IDisposable 定数の定義
まず、このクラスにいくつかの定数を追加します。これらの定数は、これから扱うオーディオ・ストリームに関するさまざまなメタデータになります。これから SAMPLES_PER_SECONDを追加します。 BITS_PER_SAMPLEを16に、a NUMBER_OF_CHANNELS1、そして BUFFER_SIZEを640(または320 * 2)に追加します。
const int SAMPLES_PER_SECOND = 16000;
const int BITS_PER_SAMPLE = 16;
const int NUMBER_OF_CHANNELS = 1;
const int BUFFER_SIZE = 320 * 2; プライベート・フィールドの追加
TranslationEngineクラスには、かなり多くの可動部分があります。多くのプライベートフィールドを定義する必要があります。これらのフィールドのほとんどは、翻訳者と音声合成器の設定を処理します。翻訳エンジンのメタデータを扱うフィールドもいくつかあります。また、通話に書き戻す音声をキューに入れるコンカレント・キューもあります。いずれにせよ、フィールドはこのようになる:
private ConcurrentQueue<byte[]> _audioToWrite = new ConcurrentQueue<byte[]>(); // queue to managed synthesized audio
private readonly IConfiguration _config; //Where Azure Subscription Keys will be stored
private readonly IHubContext<TranslationHub> _hub; // Hub connection we'll use to talk to frontend
private string _uuid; // Unique ID of the call being translated
private string _languageSpoken; // The language being spoken on the call
private string _languageTranslated; // The language being translated to
private SpeechTranslationConfig _translationConfig; // the configuration for the speech translator
private SpeechConfig _speechConfig; // configuration for the speech synthesizer
private PushAudioInputStream _inputStream = AudioInputStream.CreatePushStream(AudioStreamFormat.GetWaveFormatPCM(SAMPLES_PER_SECOND, BITS_PER_SAMPLE, NUMBER_OF_CHANNELS)); //Stream for handling audio input to the translator
private AudioConfig _audioInput; //configuration for the translation audio
private TranslationRecognizer _recognizer; // The translator
private SpeechSynthesizer _synthesizer; // The syntheziser, which will turn translated text into audio
private AudioOutputStream _audioOutputStream; // Output stream from the synthezier
private AudioConfig _outputConfig; // output configuration for the speech syntheizer
このクラスで使っているインポートは以下の通り:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.CognitiveServices.Speech;
using Microsoft.CognitiveServices.Speech.Audio;
using Microsoft.CognitiveServices.Speech.Translation;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using VonageDotnetTranslator.Server.Hubs;
using VonageDotnetTranslator.Shared;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks; コンストラクタの追加
このクラスにはコンストラクタを1つ用意する。このコンストラクタは、ミドルウェアから2つの依存性注入タイプのアイテムを取得することになります。オブジェクトです。 IConfigurationオブジェクト、これは Cognitive Services リソースからの Azure 認証情報を格納する場所です。 IHubContextオブジェクトで、フロントエンドとの通信に使用します。これらを適切なクラス・フィールドに割り当て、オーディオ用の設定とストリームも構築します。
public TranslationEngine(IConfiguration config, IHubContext<TranslationHub> hub)
{
_hub = hub;
_config = config;
_translationConfig = SpeechTranslationConfig.FromSubscription(
_config["SUBSCRIPTION_KEY"], _config["REGION"]);
_speechConfig = SpeechTranslationConfig.FromSubscription(
_config["SUBSCRIPTION_KEY"], _config["REGION"]);
_audioInput = AudioConfig.FromStreamInput(_inputStream);
_audioOutputStream = AudioOutputStream.CreatePullStream();
_outputConfig = AudioConfig.FromStreamOutput(_audioOutputStream);
}
ハンドル翻訳認識
次に、翻訳認識イベントを処理するイベントを追加する必要があります。翻訳者が品詞を翻訳するたびに、このイベントが発生します。認識イベントから翻訳を取り出します。そして、その翻訳を SpeechSynthesizerに送られ、通話中にユーザーに再生される音声が取り出されます。次に、翻訳されたテキストから Translationオブジェクトを作成し、ハブを聞いているすべてのクライアントに送信します。最後に、合成された音声を先ほど作成したキューにエンキューします。
private void RecognizerRecognized(object sender, TranslationRecognitionEventArgs e)
{
var translationLanguage = _languageTranslated.Split("-")[0];
var translation = e.Result.Translations[translationLanguage].ToString();
Trace.WriteLine("Recognized: " + translation);
var ttsAudio = _synthesizer.SpeakTextAsync(translation).Result.AudioData;
var translationResult = new Translation
{
LanguageSpoken = _languageSpoken,
LanguageTranslated = _languageTranslated,
Text = translation,
UUID = _uuid
};
_hub.Clients.All.SendAsync("receiveTranslation", translationResult);
_audioToWrite.Enqueue(ttsAudio);
} トランスレーターとシンセサイザーを起動する
電話を受けたら、トランスレーターとシンセサイザーを起動する。トランスレータに RecognizerRecognizedイベントをトランスレータに登録し、すべてを継続的に起動させます。入力ストリームを使って音声をトランスレータに送るので、特定の時間が経過するか、トランスレータが音声の途切れを検出すると、翻訳イベントが継続的にプッシュされます。
private async Task StartSpeechTranslationEngine(string recognitionLanguage, string targetLanguage)
{
_translationConfig.SpeechRecognitionLanguage = recognitionLanguage;
_translationConfig.AddTargetLanguage(targetLanguage);
_speechConfig.SpeechRecognitionLanguage = targetLanguage;
_speechConfig.SpeechSynthesisLanguage = targetLanguage;
_synthesizer = new SpeechSynthesizer(_speechConfig, _outputConfig);
_recognizer = new TranslationRecognizer(_translationConfig, _audioInput);
_recognizer.Recognized += RecognizerRecognized;
await _recognizer.StartContinuousRecognitionAsync();
} 翻訳停止
翻訳エンジンを停止するメソッドが必要です。このメソッドは RecognizerRecognizedイベントを _recognizerを呼び出します。 StopContinuousRecognitionAsyncを呼び出します。このメソッドが終了するまでに数秒かかることがあるので、非同期で行います。
private async Task StopTranscriptionEngine()
{
if (_recognizer != null)
{
_recognizer.Recognized -= RecognizerRecognized;
await _recognizer.StopContinuousRecognitionAsync();
}
} メイン処理ループ
私たちはWebSocketで通話から音声を受信します。つまり、WebSocketから音声を継続的に読み取ることになります。つまり、WebSocketから音声を継続的に読み込むことになります。 SpeechSynthesizerから合成された音声を受け取り、それをWebSocketに書き戻すことで、発信者は翻訳を聞くことができます。この処理は1つのメインループで行われ、クローズステータスが表示されるまでWebSocketからメッセージを読み続けます。
特に、WebSocketから受け取る最初のメッセージは、UTF-8でエンコードされたJSONになります。 Headersに対応するUTF-8エンコードされたJSONになります。このJSONは、後でVonage Voice APIにWebSocketの作成を依頼する際に渡されます。このメッセージには Headersには、共有プロジェクトで定義した Translationオブジェクトにデシリアライズします。 Translationオブジェクトにデシリアライズし、そのメタデータを使用してTranslationEngineを起動します。
public async Task ReceiveAudioOnWebSocket(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[BUFFER_SIZE];
try
{
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
var config = JsonConvert.DeserializeObject<Translation>(System.Text.Encoding.Default.GetString(buffer));
_uuid = config.UUID;
await StartSpeechTranslationEngine(config.LanguageSpoken,
config.LanguageTranslated);
_languageSpoken = config.LanguageSpoken;
_languageTranslated = config.LanguageTranslated;
while (!result.CloseStatus.HasValue)
{
byte[] audio;
while (_audioToWrite.TryDequeue(out audio))
{
const int bufferSize = 640;
for (var i = 0; i + bufferSize < audio.Length; i += bufferSize)
{
var audioToSend = audio[i..(i + bufferSize)];
var endOfMessage = audio.Length > (bufferSize + i);
await webSocket.SendAsync(new ArraySegment<byte>(audioToSend, 0, bufferSize), WebSocketMessageType.Binary, endOfMessage, CancellationToken.None);
}
}
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
_inputStream.Write(buffer);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
catch (Exception e)
{
Trace.WriteLine(e.ToString());
}
finally
{
await StopTranscriptionEngine();
}
}
すべてを処分する
最後に、良きメモリー市民として、このオブジェクトが破棄されるときに、翻訳中にアクセスしたすべての管理されていないリソースを破棄する。
public void Dispose()
{
_inputStream.Dispose();
_audioInput.Dispose();
_recognizer.Dispose();
_synthesizer.Dispose();
_audioOutputStream.Dispose();
} ボイスコントローラーを追加する
ここからは下り坂だ。とりあえず、空のAPIコントローラ VoiceControllerという空の API コントローラを Controllersフォルダに追加します。ここに Answer.エンドポイントは /webhooks/answer.このメソッドは、Vonage API番号がコールを受信したときに呼び出されるGETリクエストになります。このメソッドは Nexmo Call Control Object (NCCO) を生成し、Vonage にサーバへの WebSocket を構築するように指示する。先に述べたように Translationオブジェクトをこのオブジェクトのヘッダーに渡す。このNCCOを受け取り、VonageにJSONを返し、リクエストの処理方法を伝えます。
[Route("/webhooks/answer")]
[HttpGet]
public ActionResult Answer()
{
var host = Request.Host.ToString();
var webSocketAction = new ConnectAction()
{
Endpoint = new[]
{
new WebsocketEndpoint()
{
Uri = $"ws://{host}/ws",
ContentType="audio/l16;rate=16000",
Headers = new Translation
{
UUID = Request.Query["uuid"].ToString(),
LanguageSpoken = "en-US",
LanguageTranslated = "es-MX"
}
}
}
};
var ncco = new Ncco(webSocketAction);
return Ok(ncco.ToString());
} ミドルウェアの設定
コントローラができたので、アプリのサーバ部分で最後に行うことは、アプリのミドルウェアを設定することです。開く Startup.cs.
サービス設定
メソッド内に ConfigureServicesメソッドの中にSignalRを追加するコールを追加する:
services.AddSignalR(); app.UseEndpoints
次は app.UseEndpointsメソッドの Configureメソッドの呼び出しに大きな変更を加える必要がある。まず、クライアントが使用できるハブへのルートを /TranslationHubへのルートを定義します:
endpoints.MapHub<Hubs.TranslationHub>("/TranslationHub");
次に、使用している16khzのリニアPCMエンコーディングに基づいて、Vonageからの適切なサイズのメッセージを処理するためのWebSocketオプションを設定します:
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
ReceiveBufferSize = 640
};
app.UseWebSockets(webSocketOptions);最後に、Webソケットへの直接のルートを app.UseEndpointsデリゲートに定義します。このルートは、WebSocket用のHubContextを取得し、WebSocketをアップグレードします。 TranslationHubのHubContextを取得し、WebSocketをアップグレードし、Translationエンジンを起動するために必要なハブ、設定、WebSocket、httpContextを取得します。
endpoints.Map("/ws", async (context) => {
if (context.WebSockets.IsWebSocketRequest)
{
var hub = (IHubContext<TranslationHub>)app.ApplicationServices.GetService(typeof(IHubContext<TranslationHub>));
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
using (var engine = new TranslationEngine(Configuration, hub))
{
await engine.ReceiveAudioOnWebSocket(context, webSocket);
}
}
else
{
context.Response.StatusCode = 400;
}
});
フロントエンドの構築
最後に必要なのは、フロントエンドの構築だ。皮肉なことに、これはこの練習の簡単な部分です。 VonageDotnetTranslator.Clientプロジェクトの Pagesフォルダに追加します。 TranslationComponent.razor.このファイルでフロントエンドのロジックを定義します。
依存関係を取り込む
この翻訳コンポーネントで動作するために必要な依存関係を取り込みます。これらの依存関係には、SignalRクライアント、これまで使ってきた共有プロジェクト、ルーティングを助けるための NavigationManager(を注入し、最後にIDisposableを実装します:
@using Microsoft.AspNetCore.SignalR.Client
@using VonageDotnetTranslator.Shared
@inject NavigationManager NavigationManager
@implements IDisposable 翻訳を更新するコードを追加する
次に @codeブロックの中で、サーバーから取得したすべての翻訳を格納する辞書を定義します。 HubConnection.を定義します。 OnInitializedAsyncを構築します。 HubConnectionミドルウェアで定義した /TranslationHubルートを構築します。そして receiveTranslationイベントが発生するたびに(これは翻訳が発生したときに送信されるイベントです)、翻訳辞書を更新します。もし UUIDがすでに辞書に登録されている場合、その翻訳テキストを現在の翻訳テキストに連結します。そうでない場合は、新しい翻訳オブジェクトを追加します。その後、Hub Connectionを起動し、接続を管理するためのメソッドをいくつか追加して、接続が完了したらクリーンアップします。
@code {
private Dictionary<string, Translation> _translations = new Dictionary<string, Translation>();
private HubConnection _hubConnection;
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/TranslationHub"))
.Build();
_hubConnection.On<Translation>("receiveTranslation", (translation) =>
{
if (_translations.ContainsKey(translation.UUID))
{
_translations[translation.UUID].Text += translation.Text;
}
else
{
_translations.Add(translation.UUID, translation);
}
StateHasChanged();
});
await _hubConnection.StartAsync();
}
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = _hubConnection.DisposeAsync();
}
}
見解を加える
次に、すべての翻訳を格納するテーブルを追加します。このテーブルは Translationオブジェクトのプロパティに対応するヘッダを持ちます。 _translationsコレクションから直接入力します。
<h3>Translation</h3>
<table class="table">
<thead>
<tr>
<th>Uuid</th>
<th>Language Spoken</th>
<th>Language Translated To</th>
<th>Text</th>
</tr>
</thead>
<tbody>
@foreach (var translation in _translations.Values)
{
<tr>
<td>@translation.UUID</td>
<td>@translation.LanguageSpoken</td>
<td>@translation.LanguageTranslated</td>
<td>@translation.Text</td>
</tr>
}
</tbody>
</table>
インデックスに追加
最後にすることは TranslationComponentを index.razorファイルに追加することだ。そのためには Index.razorを開き @pageディレクティブ以外の内容を削除し、追加する:
アプリの設定
最後にすることは、2つのAzure Configuration項目を VonageDotnetTranslation.Server/appsettings.jsonファイルに追加する。ベースオブジェクトに2つのフィールドを追加します、 SUBSCRIPTION_KEYと REGIONという2つのフィールドを追加し、サブスクリプションキーとリージョンに設定する。
テスト
音声翻訳機を作るために必要なことは以上です!あとはテストするだけです。 VonageDotnetTranslation/Serverディレクトリから dotnet runまたはf5か再生ボタンを使ってください。
注意 - IIS Express を使用している場合は、以下のガイドを参照してください。 ngrok を IIS Express で使用するためのガイドを参照してください。
私たちの VoiceControllerでは、翻訳言語をスペイン語に、話し言葉をアメリカ英語に設定しています。 対応言語ドキュメントを参照してください。
さらに突き進む
WebSocketは、Azure Cognitive Servicesと組み合わせることで、あらゆる種類の強力なユースケースを可能にする。翻訳だけでなく、通常の書き起こしやセンチメント分析も可能だ!VonageのAPIは、PSTNやVoIPとの柔軟性が高く、比較的簡単に統合を構築することができます。
リソース
このデモのソースは GitHub
