Alexaでもparタグ使いたい!〜Alexaで発話とサウンドを並列再生する〜 / Speak with Background Music on Alexa

Alexaでもparタグ使いたい!〜Alexaで発話とサウンドを並列再生する〜 / Speak with Background Music on Alexa

【AAJUG 京都】 スキル企画・開発アドバンスド で発表したLT資料です。発話とサウンドの並列再生について話しました。

https://aajug.connpass.com/event/152201/

5caee8d2ccdb42a940545c47c6c01373?s=128

Kuniaki Shimizu

November 09, 2019
Tweet

Transcript

  1. 4.

    My Skills & Actions 4 - Alexa (JP): 12 -

    Google: 1 - Clova: 1 #スキル開発100チャレンジ - Alexa (US): 1
  2. 11.

    <par>タグ 11 • Google Cloud Text-to-Speech で使える SSMLタグ • <par></par>で囲まれた中に<media>コンテナを

    複数並べると並列で再生される • <media>コンテナに<speak>や<audio>を入れる • <media>タグの属性で細かい制御が可能 ◦ xml:idでラベルを付けて、ラベルで指定 ◦ begin/endで開始・終了時間を設定 ◦ faceInDur/fadeOutDurでフェードイン・フェー ドアウト などなど
  3. 12.

    </speak> 日本昔ばなしスキルです。今日のお話は、桃太郎。<break time="1s"/> <par> <media xml:id="bgm" end="obaasan_story.end+2s" fadeOutDur="2s"> <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/momotaro.mp3"

    soundLevel="-20dB"/> </media> <media xml:id="intro" begin="0.7s"> <speak>むかーしむかし、あるところに、おじいさんとおばあさんが住んでいました。毎日、</speak> </media> <media xml:id="ojiisan_story" begin="intro.end+0.2s"> <speak>おじいさんは、山に芝刈りに、</speak> </media> <media xml:id="ojiisan_sound" begin="ojiisan_story.end-0.5s"> <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/shibakari.mp3" repeatCount="2"/> </media> <media xml:id="obaasan_story" begin="ojiisan_story.end+0.5s"> <speak>おばあさんは、川に洗濯に、いっていました。</speak> </media> <media xml:id="obaasan_sound" begin="obaasan_story.end-2.0s" fadeOutDur="2s"> <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/sentaku.mp3" clipEnd="3s"/> </media> </par> </speak>
  4. 13.
  5. 16.

    <par>タグで並列再生のメリット 16 • 表現力が豊か ◦ 楽しい・にぎやか・臨場感 ◦ ユースケースいろいろ • SSMLだけで書ける

    ◦ お手軽 ◦ 動的な発話生成とBGMをミックスできる • サウンド編集が不要 ◦ 音量や再生時間、フェードアウトやフェードイン をSSMLで制御
  6. 18.

    Alexaでは・・・ 18 • SSMLでは、発話とオーディオはシーケン シャルに再生される ◦ 発話→サウンド→効果音→発話 ◦ 並列で再生させる方法がない •

    どうするか? ◦ サウンドと発話をミックスしたmp3作成 ▪ Polly/GarageBandとか • でも発話は動的に生成したい ◦ 複数パターンのmp3を用意する? ◦ いくつ用意するんや・・・
  7. 26.

    26 alexa-polly-background-mixing-nodejs • digivoice.io の Daniel MittendorfさんによるPoC • Pollyで生成した発話mp3と予め用意したサウンドmp3を ミックスして、S3に生成

    ◦ SoX(Sound eXChange) → コマンドラインの音声加工ツール ◦ lambda-audio → SoXをLambdaで動かすためのライブラリ   - /tmpでバイナリを配置してを実行 • S3に生成して、URLをaudioタグで出力 • neuralにも対応(Joanna/Mathew, us-west-1, eu-west-1 のみ)
  8. 27.

    const LaunchHandler = { canHandle(handlerInput) { const request = handlerInput.requestEnvelope.request;

    return request.type === 'LaunchRequest'; }, async handle(handlerInput) { let pollyVoice = await generatePollyUrl("<speak>むかーしむかし、あるところに、 おじいさんとおばあさんが住んでいました。毎日、おじいさんは山に芝刈りに、おばあさんは 川に洗濯に、いっていました。 </speak>", "Mizuki", background_sfx); let speechOutput = '日本昔ばなしスキルです。今日のお話は、「桃太郎」。 ' + pollyVoice + 'おしまい。'; return handlerInput.responseBuilder .speak(speechOutput) .getResponse(); }, };
  9. 28.

    const mix_polly_with_background = (background_mp3, polly_voice_mp3, resulting_mp3, duration) => lambdaAudio.sox ('-m

    ' + background_mp3 + ' ' + polly_voice_mp3 + ' -C 48.01 ' + resulting_mp3 + ' rate 22050 gain -l 16 trim 0 '+duration).then(() => { return resulting_mp3 }).catch(err => console.error("mix error: "+err)) async function generatePollyUrl (ssml, voice, background_sound) { ・・・ try { ・・・ } catch (err) { let polly_voice = "polly_tmp_"+Math.round(+new Date() / 10)+".mp3"; if (fs.existsSync('/tmp/sox') && fs.existsSync('/tmp/lame')) { console.log('Found’); } else { await copyFiles(); } const pollyVoice = await generatePollyAudio(ssml, voice); await fs.outputFile('/tmp/'+polly_voice, pollyVoice.AudioStream); const duration = await mp3Duration('/tmp/'+polly_voice); var file = await mix_polly_with_background(background_sound, '/tmp/'+polly_voice, '/tmp/'+sound_mix_result, duration); const uploadFile = await fs.readFile(file); var writeToS3 = await writeAudioStreamToS3Bucket(uploadFile, sound_mix_result); return '<audio src="'+writeToS3.url+'"/>'; } }
  10. 29.
  11. 30.

    30

  12. 31.

    31 課題 • Pollyの声になる、Alexaの声ではない。 • audioタグはPollyでは使えない。BGMとのミックスが限界 • 初回アクセス時は、Polly mp3生成+BGMミックス処理が 行われるため、応答までに時間がかかる

    ◦ Lambda側はタイムアウトを緩和 ◦ Alexa側はプログレッシブ応答で緩和がベター ◦ 2回目以降はキャッシュを使う(即応答する) • 動的に発話を生成する場合 ◦ 毎回ミックスが発生するので応答に時間がかかる ◦ 溜まったS3上のmp3ファイルは一定期間で削除等必要 • 細かいタイミングまでは制御できない ◦ Pollyの発話時間に合わせてミックスされるが、最後は いきなり切れる(フェードアウト設定追加がベター)
  13. 39.

    39 方法は色々・・・ • Amazon Pollyで発話mp3生成、Amazon Elastic Trancoderでサウンドmp3とミックス • Google Cloud

    Text-to-Speech APIなら、SSML でparタグそのまま使えるけど、違和感 SSMLだけでサラッと・・・ というわけには行かない
  14. 44.

    44 parタグ • <par>, <media>, <seq>は、Google独自の規格ではない。 ◦ SSMLでは他のマークアップとの相互運用性のサポートが 明記されている(VoiceXML・SMIL等) •

    SMIL(Synchronized Multimedia Integration Language) ◦ WWWでマルチメディアコンテンツを表現するためのマーク アップ言語 ◦ 静止画/動画/音声/文字などの、位置及び時間軸での レイアウトをXMLで記述し再生 ◦ <par>, <media>, <seq>はSMIL3で定義されている ◦ Googleはこの仕様に素直に従っているだけに見える つまり・・・
  15. 48.

    48 VCの目線 • VUIアプリの普及に必要な要素 ◦ Anchor / GimletなどのPodcastサービスの次の波 ◦ パーソナライズ

    ▪ 位置情報。自宅の外 ▪ カスタマイズされたオーディオ体験 • Resemble.io / Descript ◦ 新しいインタフェースや挙動 ▪ airpodそのものではなく、airpod-firstなプロダクト ◦ マネタイズ ▪ 広告ではなく、サブスクリプションモデル • Shine / Headspace / Calm
  16. 50.

    50 まとめ • SSMLでサウンドと音声を並列再生できるparタグ はとても良い! • Alexaでもできなくはないけど、SSMLで手軽に できるのが理想 • Alexaでできない理由はない。要望あげよう!

    • 「カスタマイズされた音声コンテンツ」がVUI普及 の一つのキー 開発者が、豊かな音声コンテンツを 手軽に作れることが重要な要素の一つ
  17. 51.

    51 おまけ • 時間軸での制御は便利だけど・・・ ◦ 「アレクサ、ゆっくり話して」でどうなる? ▪ Googleにはその機能はない(と思う) ◦ 時間の動的制御とかやりだしたらSSML沼

    ◦ 予め作成したmp3だとこの影響受けない(はず) ▪ GarageBandワークショップの機運・・・! • AlexaとGoogleのSSMLの違いも興味深い ◦ GoogleだとPollyみたいな音声使い分けはできない? ◦ audioタグとかsay-asのオプションが豊富 ▪ audioタグ単体で再生時間の制御とかできる ▪ say-asでunitを使うと、単数形・複数形を吸収して くれる(Alexaもやってくれる?)