> For the complete documentation index, see [llms.txt](https://docs.digitalhumans.jp/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digitalhumans.jp/dev/miniprem/services/custom-stt.md).

# カスタム STT

本ガイドでは、独自の STT（音声認識）サービス（Azure Speech Services、AWS Transcribe、その他カスタム STT など）をデジタルヒューマンと統合する方法を説明します。

{% hint style="info" %}
本ページの内容はデジタルヒューマン株式会社の正式サポート対象外です。参考情報としてご利用ください。
{% endhint %}

## 概要：STT のオプションを理解する

デジタルヒューマンで音声認識を扱う方法は **3 種類** あります。

| オプション                          | 説明                         | 音声処理の担当               | 文字起こしの担当                    |
| ------------------------------ | -------------------------- | --------------------- | --------------------------- |
| **1. ビルトイン STT**               | プラットフォーム設定でマイクを有効化         | Hosted Experience SDK | プラットフォーム（Google／Deepgram）   |
| **2. ミニプレム（MiniPrem） Whisper** | MiniPrem の Whisper サービスを利用 | お客様のフロントエンド           | MiniPrem Whisper コンテナ       |
| **3. カスタム STT（BYOSTT）**        | 独自の STT プロバイダーを持ち込む        | お客様のフロントエンド           | お客様の STT サービス（Azure、AWS など） |

**本ガイドはオプション 3：カスタム STT（Bring Your Own STT）に焦点を当てます。**

## アーキテクチャ：カスタム STT の動作

カスタム STT を使用する場合、デジタルヒューマンプラットフォームにビルトインされた音声認識を完全にバイパスします。処理の流れは次のとおりです。

```mermaid
flowchart TD
    subgraph FE["お客様のフロントエンド"]
        mic["マイク（ブラウザ）"] --> capture["音声キャプチャ<br/>(Web Audio API)"]
        capture --> stt["お客様の STT サービス<br/>(Azure／AWS／カスタム)"]
        stt --> text["文字起こしテキスト<br/>'Hello, how are you?'"]
        text --> prompt["uneeq.chatPrompt(text,true)<br/>デジタルヒューマンへ送信"]
    end
    prompt --> dh["デジタルヒューマン<br/>テキストを処理し応答"]
```

## 主要概念：`chatPrompt()` メソッド

`chatPrompt()` メソッドは、カスタム STT を使用する際に、ユーザーのテキストをデジタルヒューマンに送信するための手段です。

```javascript
// 文字起こしされたテキストをデジタルヒューマンに送信
uneeqInstance.chatPrompt(transcribedText, addClosedCaption);
```

**パラメータ：**

* `transcribedText`（文字列）：STT サービスから受信したテキスト
* `addClosedCaption`（真偽値）：テキストを字幕として表示する場合は `true` を指定

**例：**

```javascript
// ユーザーが「What's the weather today?」と発話
// Azure STT で文字起こし
// 続いてデジタルヒューマンへ送信:
uneeqInstance.chatPrompt("What's the weather today?", true);
```

## ステップバイステップ実装ガイド

### ステップ 1：ビルトインマイクなしで初期化する

デジタルヒューマンセッションを初期化する際は、**ビルトインマイクを有効化しないでください**。重要なオプションは次のとおりです。

```javascript
const uneeqInstance = new Uneeq({
    // 必須: ペルソナ設定
    connectionUrl: 'https://api.us.uneeq.io',  // または利用するリージョンの API エンドポイント
    personaId: 'your-persona-id',

    // カスタム STT 利用時の重要設定: 無効化したままにする
    enableMicrophone: false,  // デフォルトは false - true に設定しない
    enableVad: false,         // 音声アクティビティ検出を無効化

    // オプション: その他よく使う設定
    showUserInputInterface: false,  // 独自 UI を利用する場合はビルトイン入力 UI を非表示
    showClosedCaptions: true,       // デジタルヒューマン応答の字幕を表示
    layoutMode: 'fullScreen',       // または 'overlay'、'contained'
    autoStart: false,               // セッション開始タイミングを制御
});
```

**カスタム STT 向けの主要設定オプション：**

| オプション                    | 値       | 理由                                   |
| ------------------------ | ------- | ------------------------------------ |
| `enableMicrophone`       | `false` | プラットフォームによる音声キャプチャを防止                |
| `enableVad`              | `false` | ビルトイン音声アクティビティ検出を無効化                 |
| `showUserInputInterface` | `false` | オプション：独自入力 UI がある場合は ビルトイン入力 UI を非表示 |

**やってはいけないこと：**

```javascript
// カスタム STT を利用するならこれは行わないでください
uneeqInstance.enableMicrophone();  // ビルトイン STT が有効化されます

// 設定で次の値も指定しないでください
{
    enableMicrophone: true,           // Google／Deepgram STT を使用します
    enableVad: true,                  // 音声検出が有効になります
    speechRecognitionProvider: 'deepgram',  // カスタム STT では不要
}
```

**注記：** `speechRecognitionProvider` オプション（google／deepgram）は、ビルトインマイクを使用する場合にのみ適用されます。カスタム STT では文字起こしをお客様側で処理するため、本オプションは無関係です。

### ステップ 2：ユーザーのマイクから音声をキャプチャする

Web Audio API または MediaRecorder を使用して音声をキャプチャします。

```javascript
class AudioCaptureService {
    private mediaRecorder: MediaRecorder | null = null;
    private audioChunks: Blob[] = [];

    async startCapture(): Promise<void> {
        const stream = await navigator.mediaDevices.getUserMedia({
            audio: {
                echoCancellation: true,
                noiseSuppression: true,
                sampleRate: 16000,  // Azure STT は通常 16kHz を想定
            }
        });

        this.mediaRecorder = new MediaRecorder(stream, {
            mimeType: 'audio/webm;codecs=opus'
        });

        this.mediaRecorder.ondataavailable = (event) => {
            if (event.data.size > 0) {
                this.audioChunks.push(event.data);
            }
        };

        this.mediaRecorder.start(100); // 100ms チャンクでキャプチャ
    }

    stopCapture(): Blob {
        this.mediaRecorder?.stop();
        const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
        this.audioChunks = [];
        return audioBlob;
    }
}
```

### ステップ 3：STT サービスへ音声を送信する（Azure の例）

#### オプション A：Azure Speech SDK（推奨）

```javascript
import * as SpeechSDK from 'microsoft-cognitiveservices-speech-sdk';

class AzureSTTService {
    private speechConfig: SpeechSDK.SpeechConfig;
    private recognizer: SpeechSDK.SpeechRecognizer | null = null;

    constructor(subscriptionKey: string, region: string) {
        this.speechConfig = SpeechSDK.SpeechConfig.fromSubscription(
            subscriptionKey,
            region
        );
        this.speechConfig.speechRecognitionLanguage = 'en-US';
    }

    async transcribeFromMicrophone(
        onResult: (text: string) => void,
        onError: (error: Error) => void
    ): Promise<void> {
        const audioConfig = SpeechSDK.AudioConfig.fromDefaultMicrophoneInput();
        this.recognizer = new SpeechSDK.SpeechRecognizer(
            this.speechConfig,
            audioConfig
        );

        // 中間結果（部分的な文字起こし）の処理
        this.recognizer.recognizing = (_, event) => {
            console.log('Recognizing:', event.result.text);
        };

        // 最終結果の処理
        this.recognizer.recognized = (_, event) => {
            if (event.result.reason === SpeechSDK.ResultReason.RecognizedSpeech) {
                onResult(event.result.text);
            }
        };

        // エラー処理
        this.recognizer.canceled = (_, event) => {
            onError(new Error(`Recognition canceled: ${event.errorDetails}`));
        };

        // 連続認識を開始
        await this.recognizer.startContinuousRecognitionAsync();
    }

    async stop(): Promise<void> {
        await this.recognizer?.stopContinuousRecognitionAsync();
    }
}
```

#### オプション B：Azure REST API

音声処理を自前で行い、Azure の REST API を呼び出したい場合は次のように実装します。

```javascript
async function transcribeWithAzureREST(
    audioBlob: Blob,
    subscriptionKey: string,
    region: string
): Promise<string> {
    const response = await fetch(
        `https://${region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=en-US`,
        {
            method: 'POST',
            headers: {
                'Ocp-Apim-Subscription-Key': subscriptionKey,
                'Content-Type': 'audio/wav; codecs=audio/pcm; samplerate=16000',
            },
            body: audioBlob,
        }
    );

    const result = await response.json();
    return result.DisplayText;
}
```

### ステップ 4：文字起こしテキストをデジタルヒューマンへ送信する

STT サービスから文字起こしテキストを受信したら、デジタルヒューマンへ送信します。

```javascript
// 完全な統合例
class CustomSTTIntegration {
    private uneeqInstance: any;
    private azureSTT: AzureSTTService;

    constructor(uneeqInstance: any, azureConfig: { key: string; region: string }) {
        this.uneeqInstance = uneeqInstance;
        this.azureSTT = new AzureSTTService(azureConfig.key, azureConfig.region);
    }

    async startListening(): Promise<void> {
        await this.azureSTT.transcribeFromMicrophone(
            // 文字起こし成功時
            (transcribedText: string) => {
                console.log('User said:', transcribedText);

                // デジタルヒューマンへ送信
                this.uneeqInstance.chatPrompt(transcribedText, true);
            },
            // エラー時
            (error: Error) => {
                console.error('STT Error:', error);
            }
        );
    }

    async stopListening(): Promise<void> {
        await this.azureSTT.stop();
    }
}
```

### ステップ 5：デジタルヒューマンの発話状態を処理する

ハウリングを防ぐため、デジタルヒューマンが発話している間はマイクをミュート／ミュート解除します。

```javascript
// デジタルヒューマンのイベントをリッスン
window.addEventListener('UneeqMessage', (event) => {
    const msg = event.detail;

    switch (msg.uneeqMessageType) {
        case 'AvatarStartedSpeaking':
            // デジタルヒューマンの音声を拾わないようマイクをミュート
            customSTT.stopListening();
            break;

        case 'AvatarStoppedSpeaking':
            // デジタルヒューマンの発話終了時にリスニングを再開
            customSTT.startListening();
            break;
    }
});
```

## 完全な動作サンプル

以下は Azure を用いてカスタム STT を実装する完全な React コンポーネント例です。

```typescript
import React, { useEffect, useRef, useState } from 'react';
import * as SpeechSDK from 'microsoft-cognitiveservices-speech-sdk';

interface CustomSTTProps {
    uneeqInstance: any;
    azureKey: string;
    azureRegion: string;
}

export function CustomSTTMicrophone({ uneeqInstance, azureKey, azureRegion }: CustomSTTProps) {
    const [isListening, setIsListening] = useState(false);
    const [transcript, setTranscript] = useState('');
    const recognizerRef = useRef<SpeechSDK.SpeechRecognizer | null>(null);

    const startListening = async () => {
        const speechConfig = SpeechSDK.SpeechConfig.fromSubscription(azureKey, azureRegion);
        speechConfig.speechRecognitionLanguage = 'en-US';

        const audioConfig = SpeechSDK.AudioConfig.fromDefaultMicrophoneInput();
        const recognizer = new SpeechSDK.SpeechRecognizer(speechConfig, audioConfig);
        recognizerRef.current = recognizer;

        // 中間結果（UI フィードバック用）
        recognizer.recognizing = (_, event) => {
            setTranscript(event.result.text);
        };

        // 最終結果 - デジタルヒューマンへ送信
        recognizer.recognized = (_, event) => {
            if (event.result.reason === SpeechSDK.ResultReason.RecognizedSpeech) {
                const text = event.result.text;
                setTranscript(text);

                // デジタルヒューマンへ送信
                uneeqInstance.chatPrompt(text, true);

                // 送信後にトランスクリプトをクリア
                setTimeout(() => setTranscript(''), 500);
            }
        };

        recognizer.canceled = (_, event) => {
            console.error('Recognition canceled:', event.errorDetails);
            setIsListening(false);
        };

        await recognizer.startContinuousRecognitionAsync();
        setIsListening(true);
    };

    const stopListening = async () => {
        await recognizerRef.current?.stopContinuousRecognitionAsync();
        recognizerRef.current = null;
        setIsListening(false);
    };

    // デジタルヒューマンが発話中は自動ミュート
    useEffect(() => {
        const handleUneeqMessage = (event: CustomEvent) => {
            const msg = event.detail;
            if (msg.uneeqMessageType === 'AvatarStartedSpeaking') {
                stopListening();
            } else if (msg.uneeqMessageType === 'AvatarStoppedSpeaking') {
                startListening();
            }
        };

        window.addEventListener('UneeqMessage', handleUneeqMessage as EventListener);
        return () => {
            window.removeEventListener('UneeqMessage', handleUneeqMessage as EventListener);
        };
    }, []);

    return (
        <div className="custom-stt-controls">
            <button
                onClick={isListening ? stopListening : startListening}
                className={isListening ? 'listening' : 'muted'}
            >
                {isListening ? 'Stop Listening' : 'Start Listening'}
            </button>
            {transcript && (
                <div className="transcript">
                    {transcript}
                </div>
            )}
        </div>
    );
}
```

## Azure Speech Services のセットアップ

### 前提条件

1. Speech Services リソースを利用できる **Azure アカウント**
2. Azure Portal から取得した **サブスクリプションキー** と **リージョン**

### 認証情報の取得

1. [Azure Portal](https://portal.azure.com) にアクセスします
2. 新規に「Speech Services」リソースを作成します
3. 作成後、「キーとエンドポイント」へ移動します
4. 以下をコピーします：
   * **KEY 1** または **KEY 2**（サブスクリプションキー）
   * **場所／リージョン**（例：`eastus`、`westus2`）

### サポートされる音声フォーマット

Azure Speech Services は以下を受け付けます。

* **PCM WAV**：16 ビット、モノラル、16kHz（推奨）
* **Opus／WebM**：ストリーミングに対応
* **MP3**：対応するが効率は劣る

### 言語サポート

設定で使用言語を指定します。

```javascript
speechConfig.speechRecognitionLanguage = 'en-US';  // 英語（米国）
speechConfig.speechRecognitionLanguage = 'es-ES';  // スペイン語（スペイン）
speechConfig.speechRecognitionLanguage = 'fr-FR';  // フランス語（フランス）
speechConfig.speechRecognitionLanguage = 'de-DE';  // ドイツ語
speechConfig.speechRecognitionLanguage = 'ja-JP';  // 日本語
speechConfig.speechRecognitionLanguage = 'zh-CN';  // 中国語（簡体字）
```

サポート対象言語の一覧は [Azure 言語サポート](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support) を参照してください。

## トラブルシューティング

### よくある問題

**1. 「デジタルヒューマンへの接続に失敗しました」**

* `chatPrompt()` を呼び出す前にデジタルヒューマンセッションが初期化されているか確認してください
* WebSocket 接続が確立されているか確認してください

**2. 「Azure STT が文字起こししない」**

* サブスクリプションキーとリージョンが正しいか確認してください
* ブラウザのマイク権限を確認してください
* 音声フォーマットが Azure の要件に合致しているか確認してください（16kHz 推奨）

**3. 「デジタルヒューマンが応答しない」**

* `chatPrompt()` が呼び出されているか確認してください（console.log を追加）
* テキストが空または空白のみでないか確認してください
* 会話／セッションがアクティブか確認してください

**4. 「エコー／ハウリングが発生する」**

* デジタルヒューマンが発話中の自動ミュートを実装してください（ステップ 5 参照）
* 音声キャプチャ設定でエコーキャンセルを使用してください
* スピーカーとマイクの物理的距離を確保してください

**5. 「文字起こしが遅い」**

* バッチ処理ではなくストリーミング／連続認識を使用してください
* ユーザーに最も近い Azure リージョンを選択してください
* 精度向上のため Azure のニューラルモデルの利用を検討してください

### デバッグログ

処理の流れを追跡するためのログを追加します。

```javascript
// STT ハンドラ内
recognizer.recognized = (_, event) => {
    console.log('[CustomSTT] Recognition result:', {
        reason: event.result.reason,
        text: event.result.text,
        duration: event.result.duration
    });

    if (event.result.text) {
        console.log('[CustomSTT] Sending to digital human:', event.result.text);
        uneeqInstance.chatPrompt(event.result.text, true);
    }
};

// chatPrompt 後
console.log('[CustomSTT] chatPrompt called successfully');
```

## セキュリティ上の考慮事項

### API キーをフロントエンドで露出させない

Azure 認証はバックエンドプロキシ経由で処理します。

```javascript
// 悪い例 - これは行わないでください
const azureKey = 'your-key-exposed-in-frontend';

// 良い例 - バックエンドのトークンサービスを利用
async function getAzureToken(): Promise<string> {
    const response = await fetch('/api/azure-stt-token');
    const { token } = await response.json();
    return token;
}

// 続けてトークンベース認証を使用
const speechConfig = SpeechSDK.SpeechConfig.fromAuthorizationToken(
    token,
    region
);
```

### バックエンドのトークンサービス例（Node.js）

```javascript
// /api/azure-stt-token
import fetch from 'node-fetch';

export async function getAzureToken(req, res) {
    const response = await fetch(
        `https://${process.env.AZURE_REGION}.api.cognitive.microsoft.com/sts/v1.0/issueToken`,
        {
            method: 'POST',
            headers: {
                'Ocp-Apim-Subscription-Key': process.env.AZURE_SPEECH_KEY,
                'Content-Length': '0',
            },
        }
    );

    const token = await response.text();
    res.json({ token });
}
```

## その他の STT プロバイダー

### AWS Transcribe

```javascript
import { TranscribeStreamingClient, StartStreamTranscriptionCommand } from '@aws-sdk/client-transcribe-streaming';

const client = new TranscribeStreamingClient({ region: 'us-east-1' });

// AWS Transcribe に音声をストリーミング
const command = new StartStreamTranscriptionCommand({
    LanguageCode: 'en-US',
    MediaEncoding: 'pcm',
    MediaSampleRateHertz: 16000,
    AudioStream: audioStream,
});

const response = await client.send(command);
for await (const event of response.TranscriptResultStream) {
    const transcript = event.TranscriptEvent?.Transcript?.Results?.[0]?.Alternatives?.[0]?.Transcript;
    if (transcript) {
        uneeqInstance.chatPrompt(transcript, true);
    }
}
```

### Google Cloud Speech-to-Text

```javascript
import speech from '@google-cloud/speech';

const client = new speech.SpeechClient();

const request = {
    config: {
        encoding: 'LINEAR16',
        sampleRateHertz: 16000,
        languageCode: 'en-US',
    },
    audio: { content: audioBase64 },
};

const [response] = await client.recognize(request);
const transcript = response.results
    .map(result => result.alternatives[0].transcript)
    .join(' ');

uneeqInstance.chatPrompt(transcript, true);
```

## まとめ

デジタルヒューマンでカスタム STT を利用する手順は次のとおりです。

1. ビルトインマイクを **有効化しない**（`enableMicrophone: false`）
2. Web Audio API または MediaRecorder を使って **音声をキャプチャ** する
3. キャプチャした音声を STT サービス（Azure、AWS、Google など）へ **送信** する
4. `chatPrompt(text, true)` を使って **文字起こしテキストをデジタルヒューマンへ送信** する
5. デジタルヒューマンの発話状態を扱い、マイクのミュート／ミュート解除を行う

要となるメソッドは次のとおりです。

```javascript
uneeqInstance.chatPrompt(transcribedText, true);
```

このメソッドにより、独自に文字起こししたテキストをデジタルヒューマンへ処理用に送信します。
