2024年09月02日
字幕付きのビデオ通話アプリを作る
※この投稿は、Agoraの日本代理店であるブイキューブが、Agoraブログを翻訳した記事です。
約50%の人は番組や映画を観る時に字幕を利用しているようです。背景としてコンテンツを視聴する時、にたまに内容をうまく聞き取れないことがあります。また、字幕を見ながらコンテンツを楽しみたい時もあります。Agoraがこれらの要素を取り入れた視聴体験を、お客様のビデオ通話アプリに取り込むことができれば需要に応えられるかもしれないとの発想からこのガイド記事を作りました。
目次
[非表示]※上記のdemoビデオは、字幕付きのビデオ通話アプリで機能検証したものです。
前提条件
- Flutter
- Agoraの開発者アカウント
- Agoraのリアルタイムの文字起こし機能(Real Time Transcription)と通信するバックエンドサーバー(本記事の検証用にこちらのサンプルサーバーは使用可能)
プロジェクト設定
Agora SDKで実装したシンプルなビデオ通話アプリの仕組みに関する基礎知識があることを前提でこのガイド記事を作りました。
Agora SDKの技術の仕組みに関する基礎知識を学びたい方は、こちらの公式ドキュメントからFlutterのクイックスタートを参照してください。あるいは、Agoraに関してさらに理解を深めたい方は、こちらからFlutterとAgoraでグループ通話アプリの実装方法を習うことができます。
このガイド記事は、Agoraでシンプルなビデオ通話アプリの実装から始めます。サンプルコードはこちらから確認できます。
このサンプル「starter」のソースコードは、シンプルな通話機能で検証が目的なのでトップ画面にビデオ通話を開始するためのボタンだけを設けています。 test (機能検証用のdemoのためこのチャンネル名で進めるが、本番アプリでは別のチャンネル名を利用するのが推奨)というチャンネル名で通話します。ビデオ通話を開始すると、アプリの画面にはリモートユーザーとご自身(ローカルユーザー)の映像が表示されるほか、通話終了ボタンも設けてあります。イベントハンドラー(event handler)でビュー(view)経由でユーザーの映像を追加・削除します。
文字起こし
Agoraはリアルタイムの文字起こし(Real Time Transcription)機能を提供しています。お客様はこの機能を有効化すれば、指定するチャンネルでスピーカー音声をリアルタイムで文字に変換することができます。
リアルタイムの文字起こし機能はRESTful APIで動き、AIマイクロサービス(microservice)を利用してユーザーのオンライン通話へ接続し音声をリアルタイムで文字に起こします。onStreamMessage
イベントでこのテキストデータをお客様がビデオ通話するチャンネルに送信します。この方法以外には、このデータファイルをサードパーティのクラウドストレージにアップロードすることもできます。進め方については、このガイド記事で紹介します。
バックエンド
Agoraの文字起こし機能を実装するには、お客様側で準備いただいたサーバー(business server)を介してRESTful APIを使用し、HTTPリクエストを送信します。バックエンド側でマイクロサービスを制御することで、各チャンネル内にてリアルタイムの文字起こし機能のインスタンスが一つだけ実行されるようにすることができます。また、この機能を使うためにトークン(token)を文字起こしサービスに渡す必要もあるので、これもバックエンド側で行えばクライアント側でトークンが分かることなく済みます。
このガイド記事では、こちらのサーバーをバックエンドとして利用します。このサーバーには二つのエンドポイント(endpoint)があります。一つは文字起こし機能を開始するためのもので、もう一つはこの機能を停止するためのものです。
リアルタイムの音声の文字起こしを開始
/start-transcribing/<--Channel Name-->
リアルタイムの音声の文字起こしを停止
/stop-transcribing/<--Channel Name-->/<--Task ID-->/<--Builder Token-->
ビデオ通話時に文字起こし機能を起動
お客様のFlutterアプリからHTTPパッケージを使ってネットワーク通信(network call)することができます。同一のAgoraサービスへアクセスするようにフロントエンドのアプリとバックエンドサーバーは同様のAppIDを使ってください。そして、APIをコール(call)して文字起こし機能を起動しましょう。
こちらのcall.dartファイルのように下記の startTranscription を追加してみてください。
Future startTranscription({required String channelName}) async {
final response = await post(
Uri.parse('$serverUrl/start-transcribing/$channelName'),
);
if (response.statusCode == 200) {
print('Transcription Started');
taskId = jsonDecode(response.body)['taskId'];
builderToken = jsonDecode(response.body)['builderToken'];
} else {
print('Couldn\'t start the transcription : ${response.statusCode}');
}
}
チャンネルに入室(join)する処理のすぐ後にこのファンクションを呼び出すことで、最初のユーザーがこのチャンネルに入室できたら文字起こし機能をすぐに利用することができます。 文字起こし機能が問題なく起動されると、「taskId」および「builderToken」がレスポンスの中に含まれる形で返ってきます。これは文字起こし機能を終了する際に必要な値なので、大事に保管しておいてください。
文字起こし機能が動き始めると、まるでリアルのユーザーがチャンネルに存在するように見えるが、これはただのボット(bot)です。この文字起こしサービスを動かすためのUID(ユーザーID)がバックエンドサーバーにて定義されます。もし本記事の検証用に用意しているこちらのサンプルサーバーを使うのであれば、UIDは101となります。onUserJoinedのイベントに下記の処理を追加することで、このUIDをリモートユーザー一覧から外すことができます。
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
if (remoteUid == 101) return;
setState(() {
_remoteUsers.add(remoteUid);
});
}
文字起こし機能を終了
文字起こし機能を終了するために使うファンクション「 stopTranscriptionは」、この機能を起動する際に使うのと少し似ています。このファンクションを実行するために「taskId」および「builderToken」が必要です。
Future stopTranscription() async {
final response = await post(
Uri.parse('$serverUrl/stop-transcribing/$taskId/$builderToken'),
);
if (response.statusCode == 200) {
print('Transcription Stopped');
} else {
print('Couldn\'t stop the transcription : ${response.statusCode}');
}
}
そして、通話を終了するためのdispose処理でこのstopTranscriptionを呼び出します。こうすると、ユーザーがチャンネルから退室する前に文字起こし機能を停止し、リソースを解放することができます。
文字に変換したテキストデータの取得
ビデオ通話中にリアルタイムの文字起こし機能で音声をテキスト化したデータにアクセスしたい場合は、イベントハンドラーにonStreamMessageイベントを使ってください。
onStreamMessage: (RtcConnection connection, int uid, int streamId,
Uint8List message, int messageType, int messageSize) {
print(message);
}
上記のソースコードは、AIにしか読み取れない一連の番号が配列の形式で出力されることに気づいているかもしれません。この一連の番号はGoogleのProtocol Buffers(protobufとも呼ばれている)で生成されたものです。
protobufはデータをプラットフォームに依存しない(platform-agnostic)形式でエンコーディングします。このデータを取得して、アプリやソフトウェアの使用言語に応じてシリアライズすることができます。
テキストデータをデコーディングする
Protocol Bufferを使ってテキストデータ(メッセージ)をデコーディングします。このガイド記事では、Messageというオブジェクトにてランダムに見える番号をシリアライズします。
まずは、下記で.protoファイルを作ります。
syntax = "proto3";
package call_summary;
message Message {
int32 vendor = 1;
int32 version = 2;
int32 seqnum = 3;
int32 uid = 4;
int32 flag = 5;
int64 time = 6;
int32 lang = 7;
int32 starttime = 8;
int32 offtime = 9;
repeated Word words = 10;
}
message Word {
string text = 1;
int32 start_ms = 2;
int32 duration_ms = 3;
bool is_final = 4;
double confidence = 5;
}
上記で作ったファイルをlib/protobuf/file.protoという新規フォルダに格納しておきます。これはジェネレーターがMessageのオブジェクトを作るためのインプット用のファイルです。
protobufを動かすために自前のパソコンにprotobufコンパイラをインストールする必要があります。Mac「brew install protobuf」およびLinux「apt install -y protobuf-compiler」のパッケージマネージャーからインストールできます。Windowsまたは特定のバージョンを利用する場合は、protobufダウンロードページから確認してください。
上記の他、flutter pub add protobufのコマンドを叩いて、Flutterプロジェクトにprotobufのdartパッケージをインストールする必要もあります。
そして、ターミナルにて下記のコマンドを叩くと、四つのファイルがlib/protobufのフォルダに生成されます。
protoc --proto_path= --dart_out=. lib/protobuf/file.proto
この手順までたどり着けたら、protobufは設定完了です。Messageのオブジェクトで英語のテキストデータを取得することができます。このオブジェクトには、文字に起こしたメッセージの配列「words」が含まれます。言葉が記録されてきた際に現在の文章へ組み込まれて、ユーザーが読みやすいように再構成します。変数subtitleの中身を最新のtextで上書きすることもできます。
onStreamMessage: (RtcConnection connection, int uid, int streamId,
Uint8List message, int messageType, int messageSize) {
Message text = Message.fromBuffer(message);
setState(() {
subtitle = text.words[0].text;
});
},
String型の変数subtitleを追加し、文字列の内容を画面の下部に表示させます。onStreamMessageのコールバックに新たに記録された言葉が入ると、この変数の中身を更新し、文章再構成のイベントをトリガーして最新の字幕で表示します。
アプリを起動
上記の手順で字幕付きのビデオ通話アプリを実装完了しました。flutter runのコマンドを叩いて、アプリを起動しましょう。そして、通話を開始するボタンをクリックして test チャンネルへ入室すると、発話すると同時に画面の下部に字幕が表示されます。
※上記のdemoビデオは、字幕付きのビデオ通話アプリで機能検証したものです。
リアルタイムの文字起こし機能が実装完了
これで、ユーザーがチャンネルに入室するとリアルタイムの文字起こし機能がすぐにトリガーされる字幕付きのビデオ通話アプリは、このガイド記事に記載している手順で実装完了しました。今後は、会話の内容を聞き逃すことを心配する必要がなくなります。
すべてのサンプルコードはこちらに添付しています。リアルタイムの文字起こし機能に関しては、公式ドキュメントを参考にしていただき、このガイド記事に従ってアプリを作ってみてはいかがでしょうか。
本記事をご覧いただきありがとうございます!
無料でAgoraをご体験いただけます。
こちらからサインアップし、リアルタイムの音声とビデオ通話を実装してみましょう!
利用分数が多くなければ料金は発生しません。
執筆者ブイキューブ