質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.13%

Unity(C#)におけるdocomoの音声合成apiによるwavデータの取得方法

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 3,652

score 14

現状

先日投稿した,
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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+1

HiroshiWatanabeさんのご指摘に加え、もう一つ怪しい箇所として、foreach(Int16 data in databuf)で書き出しを行っているようですが、この記述ですとdatabufからbyte単位で要素を列挙した上でInt16として書き込みを行っているように見えます。この周辺は意図通りの動作をしていますでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 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 編集

    pythonについてはうといのですが、pythonにおいてpack/unpackでバイト列・16ビット整数列の相互変換を行うことは問題ないアプローチだと思います。実際にpython版では正しい音声データが出力されているようですね。

    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が出現するような状態になっていないでしょうか?

    キャンセル

+1

System.Array.Reverse() では全体が逆順になるんじゃないですか?
(例えば 1,2,3,4 を 2,1,4,3 にしたいのに 4,3,2,1 にしてる…的な)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/16 06:26

    御返事有難うございます.
    知識不足で申し訳ないのですが,

    ビッグエンディアンからリトルエンディアンに変換するには,すべてを逆順にするのではないのでしょうか?
    http://www.ertl.jp/~takayuki/readings/info/no05.html

    私の思い違いでしたら,ご指摘ください.

    キャンセル

  • 2017/05/16 10:24

    PCMの波形データ自体にはビッグエンディアンとかリトルエンディアンとかの発想(仕様)はありません。それは波形を構成する各16bitの数値をどう格納しているかという実装部分に適用可能な考えです。「波形データ全体を逆向き」にするという事は逆再生する理屈にならないでしょうか?CPUレベルの実装的な話しとCPUやメモリに何の関係もない波形データの話しをごっちゃにしてしまっているようです。

    キャンセル

  • 2017/05/20 09:31

    ご参考にされたサイトの図は64ビットで(8バイトで)ひとかたまりのデータがメモリ上にどう格納されるか、を解説されているようです。図だけ見ると全体が反転しているように見えるかもしれませんが、今回の場合、書き出したいのは2バイトでひとかたまりのデータが連なった配列です。そのため、kokeiro001さんのご回答のように2バイトで一つのデータであることを意識して、個々のデータについて逆転させるということが必要になるのかと思います。
    このあたり、Cなどのメモリと密接に関連したプログラミングが必要な言語ですと、簡単にいろいろな実験ができて面白いとは思うのですが...

    キャンセル

+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%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る