目的
WebAPIを使用しないスタンドアロンで
(なるべく)自然な音声読み上げを行う
環境
- fedora 42
-
orca
f9c9267f99f5bf396ffd21bb2056dc5fb9d6777e
orcaは医療とかで使われているやつじゃなくて
picovoiceの音声読み上げライブラリの方です
事前準備
-
picovoiceのサイトで
startfreeをクリック -
I am working on a commercial project.のチェックを外すと
githubなどのアカウントでサインアップ出来る - 下3つのチェックを入れるとアカウントが作成出来て画面が遷移する
- アクセスキーが表示されているので保存しておく
- alsaのヘッダが無ければ
sudo yum install alsa-lib-devel.x86_64などでインストールしておく(*1)
(*1)
ファイル出力だけなら不要
ライブラリの作成
git clone --recurse-submodules --depth=1 https://github.com/Picovoice/orca.git
cd orca
# 以下で同梱デモの確認が出来る
cmake -S demo/c/ -B demo/c/build
cmake --build demo/c/build --target orca_demo
cmake --build demo/c/build --target orca_demo_streaming
./demo/c/build/orca_demo -l lib/linux/x86_64/libpv_orca.so -m lib/common/orca_params_fr_female.pv -a [自分のアクセスキー] -t "je m'appelle yano" -o output.wav
xdg-open output.wav
コード
main.c
自分のアクセスキーを記載すること
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include "pv_orca.h"
#define ORCA_BASE "../orca/"
#define ALSA_DEVICE "default"
static int setup_alsa(snd_pcm_t **pcm, unsigned int sample_rate)
{
snd_pcm_t *handle = NULL;
snd_pcm_hw_params_t *hw;
int rc;
rc = snd_pcm_open(&handle, ALSA_DEVICE, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
{
fprintf(stderr, "snd_pcm_open error: %s\n", snd_strerror(rc));
return rc;
}
snd_pcm_hw_params_malloc(&hw);
snd_pcm_hw_params_any(handle, hw);
snd_pcm_hw_params_set_access(handle, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, hw, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, hw, 1); // mono
snd_pcm_hw_params_set_rate_near(handle, hw, &sample_rate, 0);
// set a reasonable period size (might be adjusted)
snd_pcm_uframes_t period_size = 1024;
snd_pcm_hw_params_set_period_size_near(handle, hw, &period_size, 0);
rc = snd_pcm_hw_params(handle, hw);
if (rc < 0)
{
fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(rc));
snd_pcm_hw_params_free(hw);
snd_pcm_close(handle);
return rc;
}
snd_pcm_hw_params_free(hw);
*pcm = handle;
return 0;
}
static int play_pcm(snd_pcm_t *pcm, int16_t *buf, size_t samples)
{
size_t offset = 0;
while (offset < samples)
{
snd_pcm_sframes_t frames = snd_pcm_writei(pcm, buf + offset, samples - offset);
if (frames == -EPIPE)
{
// underrun
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(pcm);
}
else if (frames < 0)
{
fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(frames));
return -1;
}
else
{
offset += frames;
}
}
return 0;
}
int main(int argc, char **argv)
{
const char *access_key = "ここにアクセスキー";
const char *model_path = ORCA_BASE "lib/common/orca_params_fr_female.pv";
const char *text = "Je m'appelle yano.";
pv_orca_t *orca = NULL;
pv_status_t status;
/* init Orca */
status = pv_orca_init(access_key, model_path, &orca);
if (status != PV_STATUS_SUCCESS)
{
fprintf(stderr, "pv_orca_init failed: %d\n", status);
return 1;
}
/* prepare synth params (defaults) */
pv_orca_synthesize_params_t *synth_params = NULL;
pv_orca_synthesize_params_init(&synth_params);
int32_t num_samples = 0;
int16_t *synth_pcm = NULL;
int32_t num_alignments = 0;
pv_orca_word_alignment_t **alignments = NULL;
status = pv_orca_synthesize(
orca,
text,
synth_params,
&num_samples,
&synth_pcm,
&num_alignments,
&alignments);
if (status != PV_STATUS_SUCCESS)
{
fprintf(stderr, "pv_orca_synthesize failed: %d\n", status);
pv_orca_synthesize_params_delete(synth_params);
pv_orca_delete(orca);
return 1;
}
/* ALSA 初期化 */
unsigned int sample_rate = 0;
status = pv_orca_sample_rate(orca, &sample_rate);
snd_pcm_t *pcm = NULL;
if (setup_alsa(&pcm, sample_rate) != 0)
{
fprintf(stderr, "ALSA setup failed\n");
pv_orca_pcm_delete(synth_pcm);
pv_orca_word_alignments_delete(num_alignments, alignments);
pv_orca_synthesize_params_delete(synth_params);
pv_orca_delete(orca);
return 1;
}
/* 再生 */
if (play_pcm(pcm, synth_pcm, (size_t)num_samples) != 0)
{
fprintf(stderr, "Playback error\n");
}
/* drain & cleanup */
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
pv_orca_pcm_delete(synth_pcm);
pv_orca_word_alignments_delete(num_alignments, alignments);
pv_orca_synthesize_params_delete(synth_params);
pv_orca_delete(orca);
return 0;
}
makefile
# こういう構造の場合
[workdir]
├─ [program]
│ ├─ main.c
│ └─ Makefile
└─ orca
makefile
ORCA_BASE := ../orca
CC := gcc
INCLUDE := -I$(ORCA_BASE)/include/
CFLAGS := -g -O2 $(INCLUDE)
LIBS := -L$(ORCA_BASE)/lib/linux/x86_64 \
-lpv_orca \
-lasound -ldl -lm \
-Wl,-rpath=$(CURDIR)/$(ORCA_BASE)/lib/linux/x86_64
SRCS := main.c
EXE := orca_sample_alsa
all:
$(CC) $(CFLAGS) $(LIBS) $(SRCS) -o $(EXE)
実行
こんな感じで実行すると読み上げてくれる
tyano@fedora:sample$ make
gcc -g -O2 -I../orca/include/ -L../orca/lib/linux/x86_64 -lpv_orca -lasound -ldl -lm -Wl,-rpath=/home/tyano/workspace/français/parle/sample/../orca/lib/linux/x86_64 main.c -o orca_sample_alsa
tyano@fedora:sample$ ./orca_sample_alsa
その他
商用とかで使うならモデル使用料が要るらしい
対応デバイスはこんな感じ
$ ls ../orca/lib/ | cat
android
common
ios
linux
mac
node
raspberry-pi
wasm
windows
モデルはこんな感じ
$ ls ../orca/lib/common/ | cat
orca_params_de_female.pv
orca_params_de_male.pv
orca_params_en_female.pv
orca_params_en_male.pv
orca_params_es_female.pv
orca_params_es_male.pv
orca_params_fr_female.pv
orca_params_fr_male.pv
orca_params_it_female.pv
orca_params_it_male.pv
orca_params_ja_female.pv
orca_params_ja_male.pv
orca_params_ko_female.pv
orca_params_pt_female.pv
orca_params_pt_male.pv
日本語対応してるのに中国語が無いという攻めた仕様
韓国語はなぜか男性が無い