Unity(C#)におけるdocomoの音声合成apiによるwavデータの取得方法
解決済
回答 4
投稿
- 評価
- クリップ 0
- VIEW 3,652
現状
先日投稿した,
https://teratail.com/questions/73993
に近い質問です.
docomoの音声合成APIを使用して,Unity上のキャラクタに話させたいと思っています.
このAPIから出力された音声データ(バイナリデータ)が
以下のプログラムを通して,www.bytesに格納されています(と思っています).
string url = "https://api.apigw.smt.docomo.ne.jp/aiTalk/v1/textToSpeech?APIKEY=" + docomoApiKey;
Dictionary<string, string> aiTalksParams = new Dictionary<string, string> ();
aiTalksParams["speaker"] = "nozomi";
aiTalksParams["pitch"] = "1";
aiTalksParams["range"] = "1";
aiTalksParams["rate"] = "1";
aiTalksParams["volume"] = "1.5";
string text = inputText;
var postData = createSsml(text, aiTalksParams);
var data = System.Text.Encoding.UTF8.GetBytes(postData);
Dictionary<string, string> headers = new Dictionary<string, string> ();
headers["Content-Type"] = "application/ssml+xml";
headers["Accept"] = "audio/L16";
headers["Content-Length"] = data.Length.ToString();
WWW www = new WWW(url, data, headers);
yield return www;
if (www.error != null) {
Debug.LogError(www.error);
yield break;
}
音声データの形式がBinaryで
音声データのフォーマットは下記の通りです.
【符号化方式】
リニアPCM
【チャネル数】
1(モノラル)
【サンプル周波数】
16000
【ビット深度】
16bit(ビッグエンディアン)
問題点
www.bytesのbyte[]型のデータをwavファイルに変換して保存し,それをUnityのAudioClipにして再生したいと考えています.
ビッグエンディアンからリトルエンディアンへの変換は
byte[] lEndianBytes = ConvertBytesEndian(www.bytes);
// http://wawatete.ddo.jp/exec/program/cs/binary_convertendian.html
static byte[] ConvertBytesEndian(byte[] bytes) {
// 引数の配列と同じサイズの配列を宣言
byte[] newBytes = new byte[bytes.Length];
// 配列のコピーを作成
bytes.CopyTo(newBytes, 0);
// 反転
System.Array.Reverse(newBytes);
return newBytes;
}
としてます.
また,そのデータをちゃんとしたヘッダ情報のあるwavファイルにするために,
[StructLayout(LayoutKind.Sequential)]
public class WAVHDR
{
[MarshalAs(UnmanagedType.I4)]
public UInt32 riff = 0x46464952; /* RIFF */
[MarshalAs(UnmanagedType.I4)]
public UInt32 fileSize;
[MarshalAs(UnmanagedType.I4)]
public UInt32 wave = 0x45564157; /* WAVE */
[MarshalAs(UnmanagedType.I4)]
public UInt32 fmt = 0x20746D66; /* fmt */
[MarshalAs(UnmanagedType.I4)]
public UInt32 fmtbytes = 16;
[MarshalAs(UnmanagedType.I2)]
public UInt16 formatid;
[MarshalAs(UnmanagedType.I2)]
public UInt16 channel;
[MarshalAs(UnmanagedType.I4)]
public UInt32 fs;
[MarshalAs(UnmanagedType.I4)]
public UInt32 bytespersec;
[MarshalAs(UnmanagedType.I2)]
public UInt16 blocksize;
[MarshalAs(UnmanagedType.I2)]
public UInt16 bitspersample;
[MarshalAs(UnmanagedType.I4)]
public UInt32 data = 0x61746164; /* data */
[MarshalAs(UnmanagedType.I4)]
public UInt32 size;
//convert the struct to byte array
public byte[] getByteArray()
{
int len = Marshal.SizeOf(this);
byte[] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(this, ptr, true/*or false*/);
Marshal.Copy(ptr, arr, 0, len /*or arr.Length*/);
Marshal.FreeHGlobal(ptr);
return arr;
}
}
WAVHDR wavHdr = new WAVHDR();
// wavファイルに格納
uint fs = 16000; // 16K
wavHdr.formatid = 0x0001; //PCM 非圧縮
wavHdr.channel = 1; // ch=1 モノラル
wavHdr.fs = fs; //
wavHdr.bytespersec = fs * 2; // 16bit 16K
wavHdr.blocksize = 2; // ブロックサイズ (Byte/sample×チャンネル数)->→16ビットモノラルなので 2
wavHdr.bitspersample = 16; // サンプルあたりのビット数 (bit/sample)
wavHdr.size = 10 * fs * 2; // 波形データのバイト数
wavHdr.fileSize = wavHdr.size + (uint)Marshal.SizeOf(wavHdr); // 全体のバイト数
としてます.
そのヘッダ情報と音声データをwavに保存するために,
PrepareFile(wavHdr, lEndianBytes);
private void PrepareFile(WAVHDR hdr, byte[] databuf)
{
using (FileStream fs = new FileStream("sample.wav", FileMode.Create, FileAccess.Write))
using (BinaryWriter bWriter = new BinaryWriter(fs)) {
// ヘッダ書きだし
foreach(byte b in hdr.getByteArray()) {
bWriter.Write(b);
}
// 波形書きだし
foreach(Int16 data in databuf) {
bWriter.Write(data);
}
bWriter.Flush();
bWriter.Close();
fs.Close();
}
}
つぎはぎのコードになってしまい,すみません.
問題点
これにより,実際にsample.wavは生成されますが,雑音しか聞こえてきません.
・PCMデータとして取得
・ビッグエンディアンからリトルエンディアンへの変換
・wavファイルへの保存
という流れは,pythonで書いたものは動いていたので流れはこれで良いと思います.それをUnityで使用するC#に移行しようとした際にうまく言っていない状況です.
ただ,目的達成には,wavファイルとして保存する必要がないので,それを飛ばせる方法等があればそれでもいいです.
お力添え願います.
補足情報(言語/FW/ツール等のバージョンなど)
OS: Ubuntu14.04
Unity: 5.4
Language: C#
API: docomo音声合成API
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
HiroshiWatanabeさんのご指摘に加え、もう一つ怪しい箇所として、foreach(Int16 data in databuf)で書き出しを行っているようですが、この記述ですとdatabufからbyte単位で要素を列挙した上でInt16として書き込みを行っているように見えます。この周辺は意図通りの動作をしていますでしょうか?
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
System.Array.Reverse() では全体が逆順になるんじゃないですか?
(例えば 1,2,3,4 を 2,1,4,3 にしたいのに 4,3,2,1 にしてる…的な)
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
下記のように修正することで期待通りのエンディアンの変換が出来ます。
byte[] ConvertBytesEndian(byte[] bytes)
{
byte[] newBytes = new byte[bytes.Length];
for (int i = 0; i < bytes.Length; i += 2)
{
newBytes[i] = bytes[i + 1];
newBytes[i + 1] = bytes[i];
}
return newBytes;
}
エンディアンそのものについては、HiroshiWatanabeさんとBongoさんの説明のとおりです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
みなさま本当に親切に回答頂きありがとうございました.
Bongoさんのお返事で主となる問題が解決できましたので,ベストアンサーにさせていただきました.
kokeiro001さんは具体的なコードを記載いただきありがとうございます.実際に使わせて頂いており,正常に動作しております.
HiroshiWatanabeさんは素早い御返事有難うございました.ビックエンディアンとリトルエンディアンは波形そのものでなく,
「波形を構成する各16bitの数値をどう格納しているかという実装部分に適用可能な考え」
であるという点を混同していたので,良い気づきをいただきました.
kokeiro001さんのコードでリトルエンディアンに変換したdatabufをヘッダ情報hdrと合わせてファイルに保存することで解決しました.
private void PrepareFile(WAVHDR hdr, byte[] databuf) {
System.IO.FileStream fs = new System.IO.FileStream("sample.wav",
System.IO.FileMode.Append,
System.IO.FileAccess.Write);
// ヘッダ書き出し
fs.Write(hdr.getByteArray(), 0, hdr.getByteArray().Length);
// 波形書き出し
fs.Write(databuf, 0, databuf.Length);
fs.Close();
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.13%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/05/16 06:32
pythonで書いたものは意図どおりに動いていて,それはバイナリデータdataを
# ビッグエンディアン->リトルエンディアン変換
lst = unpack('>{}h'.format(int(len(data)/2)), data)
data = pack('{}h'.format(len(lst)), *lst)
と変換しています.フォーマット指定のhの記号が,cにおけるshortに相当してます.
ですので,C#ではInt16として出力するのではないかと思ってそうしています.
http://www.atmarkit.co.jp/fdotnet/csharp_abc/csharp_abc_005/csharp_abc01.html
ただ,正常に動作していないので,問題はどこかにあると思うのですが.
2017/05/16 07:31 編集
C#版で怪しいと申し上げた箇所の場合、次のような動作をしているのではないかと思いました。
- foreachでdatabufを列挙する。databufはbyte[]なので、取り出される値はbyteとなる。
- 取り出されたデータは変数dataに格納される。dataはInt16と宣言されているので、暗黙的型変換によりInt16になる。
- dataをbWriter経由で書き込む。dataはInt16なので、Int16として2バイト分書き込まれる。
結果として、元々のbyte[]が倍の長さに引き伸ばされて書き込まれた状態にならないでしょうか。
bWriter.Write(data)の直前でDebug.Log(data)などでdataの内容を見てみた場合、Int16なのに0〜255の値しか出てこない、というようなことになるような気がします。
あるいは、できあがったノイズだけのwavファイルをバイナリファイルエディタ等で開いてみて、中程までスクロールしてみると、1バイトおきに0x00が出現するような状態になっていないでしょうか?