2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust で音声合成して WAV ファイルに保存する

Posted at

動機

下記の記事を最近の windows クレート仕様に書き換えてみたかったのと、音声ファイルとして保存する方法を調べてみました。

環境

rustc 1.79.0
Windows 10 22H2

実装

短いので全部載せます。

Cargo.toml
[package]
name = "speech"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"

[dependencies.windows]
version = "0.57"
features = [
    "Media_Core",
    "Media_Playback",
    "Media_SpeechSynthesis",
    "Storage_Streams",
    "Win32_System_WinRT",
]
main.rs
use anyhow::Result;
use std::fs;
use std::path::Path;
use std::slice;
use std::sync::mpsc;
use windows::{
    core::{Interface, HSTRING},
    Foundation::TypedEventHandler,
    Media::{
        Core::MediaSource,
        Playback::MediaPlayer,
        SpeechSynthesis::{SpeechSynthesisStream, SpeechSynthesizer},
    },
    Storage::Streams::DataReader,
    Win32::System::WinRT::IBufferByteAccess,
};

fn speech_synthesis_stream(source: &str) -> Result<SpeechSynthesisStream> {
    let utf16 = source.encode_utf16().collect::<Vec<_>>();
    let source = HSTRING::from_wide(&utf16)?;
    let synth = SpeechSynthesizer::new()?;
    let stream = synth.SynthesizeTextToStreamAsync(&source)?.get()?;
    Ok(stream)
}

fn speech(source: &str) -> Result<()> {
    let stream = speech_synthesis_stream(source)?;
    let player = MediaPlayer::new()?;
    let source = MediaSource::CreateFromStream(&stream, &stream.ContentType()?)?;
    player.SetSource(&source)?;
    
    let (tx, rx) = mpsc::channel();
    let tx_clone = tx.clone();

    // イベントハンドラの登録
    // 再生終了時に呼ばれる
    let token_media_ended = player.MediaEnded(&TypedEventHandler::new(move |_, _| {
        tx_clone.send(()).ok();
        Ok(())
    }))?;
    
    // 再生失敗時に呼ばれる?
    let token_media_failed = player.MediaFailed(&TypedEventHandler::new(move |_, _| {
        tx.send(()).ok();
        Ok(())
    }))?;
    
    player.Play()?;
    
    // 再生終了を待つ
    rx.recv()?;

    // イベントハンドラの削除
    player.RemoveMediaEnded(token_media_ended)?;
    player.RemoveMediaFailed(token_media_failed)?;
    
    Ok(())
}

fn save_to_wav<P: AsRef<Path>>(path: P, source: &str) -> Result<()> {
    let stream = speech_synthesis_stream(source)?;
    
    // ストリームがらデータを読む
    let reader = DataReader::CreateDataReader(&stream)?;
    let size = stream.Size()? as u32;
    reader.LoadAsync(size)?.get()?;

    // 生ポインタを取得するために IBuffer を IBufferByteAccess にキャスト
    let buffer: IBufferByteAccess = reader.ReadBuffer(size)?.cast()?;
    let ptr = unsafe { buffer.Buffer()? };

    // Rust の世界のスライスに変換
    let slice = unsafe { slice::from_raw_parts(ptr, size as usize) };
    
    // 保存
    fs::write(path, slice)?;
    
    Ok(())
}

fn main() -> Result<()> {
    // 「こんにちは」って喋る
    speech("こんにちは")?;

    // 「こんばんは」という WAV ファイルを指定したパスに保存
    save_to_wav("test.wav", "こんばんは")?;

    Ok(())
}

以上です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?