Asteriskの留守電音声をさくらのAI Engineで文字起こしする

AsteriskのARIを使って、留守電音声を記録し、さくらのAI EngineのWhisper APIで文字起こしをしてみました。
文字起こしした内容はSlackに通知します。

初めに

Asteriskはすでに環境があったのでそれを利用しています。

ARIを使えるようにする

Asteriskの設定ファイルari.confを編集して、ARIを有効にします。

  • ari.conf
    [general]
    enabled = yes
    
    [asterisk]
    type = user
    read_only = no
    password = asterisk
    password_format = plain
    

AsteriskのHTTPサーバーも有効にします。

  • http.conf
    [general]
    servername=Asterisk
    enabled=yes
    bindaddr=127.0.0.1
    bindport=8088
    

今回は999番にかかってきた電話を留守電にするようにします。

  • extensions.conf
    [default]
    exten => 999,1,Wait(1)
      same => n,Stasis(rusudenkun)
      same => n,Hangup()
    

やることの流れ

  1. 999番に電話がかかってくる
  2. ARIアプリケーションrusudenkunが起動する
  3. 音声アナウンスを流す
  4. 録音を開始する
  5. 録音が終了したら、録音したWAVファイルをさくらのAI EngineのWhisper APIで文字起こしする
  6. 文字起こしした内容をSlackに通知する

ということで、雑にGoでARIアプリケーションを実装してみました。

非常に雑な実装ですが、HTTPのリクエストで簡単に文字起こしできるのは楽ですね。

func AudioTranscription(url string, token string, model string, audioFile string) (text string, err error) {
	f, err := os.Open(audioFile)
	if err != nil {
		return "", fmt.Errorf("open audio file: %w", err)
	}
	defer f.Close()

	var body bytes.Buffer
	w := multipart.NewWriter(&body)

	if err := w.WriteField("model", model); err != nil {
		return "", fmt.Errorf("write model field: %w", err)
	}

	fw, err := w.CreateFormFile("file", f.Name())
	if err != nil {
		return "", fmt.Errorf("create file field: %w", err)
	}
	if _, err := io.Copy(fw, f); err != nil {
		return "", fmt.Errorf("copy audio: %w", err)
	}

	if err := w.Close(); err != nil {
		return "", fmt.Errorf("finalize multipart: %w", err)
	}

	req, err := http.NewRequest(http.MethodPost, url, &body)
	if err != nil {
		return "", fmt.Errorf("new request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", w.FormDataContentType())

	client := &http.Client{Timeout: 60 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		slurp, _ := io.ReadAll(resp.Body)
		return "", fmt.Errorf("bad status %d: %s", resp.StatusCode, string(slurp))
	}

	var out struct {
		Model string `json:"model"`
		Text  string `json:"text"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return "", fmt.Errorf("decode response: %w", err)
	}

	return out.Text, nil
}

実際に電話してみる

999番に電話をかけてみて、留守番電話に吹き込んでみます。

root@asterisk:~# tail -f /var/log/rusudenkun.log
{"time":"2025-09-26T15:25:45.529096517+09:00","level":"INFO","msg":"StasisStart","channel":"1758867943.63"}
{"time":"2025-09-26T15:25:45.529204145+09:00","level":"INFO","msg":"Running app","channel":"1758867943.63"}
{"time":"2025-09-26T15:25:45.680639184+09:00","level":"INFO","msg":"playing announce","media":"sound:custom/rusuden"}
{"time":"2025-09-26T15:25:54.607939088+09:00","level":"INFO","msg":"announce finished"}
{"time":"2025-09-26T15:26:04.981533804+09:00","level":"INFO","msg":"completed recording"}
{"time":"2025-09-26T15:26:05.982012514+09:00","level":"INFO","msg":"recorded","path":"rusudenkun-1758867943.63.wav"}
{"time":"2025-09-26T15:26:06.69882038+09:00","level":"INFO","msg":"transcribed text","text":"留守番電話の音声書き起こしテストです留守番電話の音声文字起こしテストです"}

録音したWAVファイルは/var/lib/asterisk/sounds/rusudenkun-<channel>.wavに保存されます。
この音声ファイルをさくらのAI EngineのWhisper APIで文字起こししています。

Slack通知

無事、文字起こしできました。

まとめ

  • AsteriskのARIを使って、留守電音声を録音することができた
  • 録音したWAVファイルをさくらのAI EngineのWhisper APIで文字起こしすることができた
  • 文字起こしした内容をSlackに通知することができた

正直、我が家の家電にはあまり電話がかかってこないので、無償枠の50リクエストでも十分かなと思います。