
シェア:
スティーブは、かつてVonageチームの一員でした。彼はVonageで.NET開発者アドボケートを務め、フルスタックかつ多言語対応のソフトウェアエンジニアであり、AI/MLの専門家でもありました。
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をテストする。必要なのは無料版だけです。
コードに直接ジャンプ
もしあなたがすべての前提条件をセットアップしているなら、このチュートリアルをスキップしてコードに直接ジャンプすることができる。アプリは 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