音声ファイルの切貼り
2021.10.10
YouTube でも紹介しています。画像をクリックすると再生できます。
今回は音声ファイルを切り貼りして、喋らせてみました。また、音声ファイルへの別ファイル埋め込みも行っています。
音源には、音声合成ソフト「UTAU」用に様々な音声ファイルが公開されています。
この記事では音声ファイル集『小春音アミ』を利用しています。
■「UTAU」用音声ファイル

あみたろの声素材工房 小春音アミ
サイト左側のメニュー・音源ダウンロードから「単独音3.00」を選んでダウンロードします。
ダウンロードした koharuneami_0300.zip を解凍すると、小春音アミ・ディレクトリの下に四音階の声素材ディレクトリが展開されます。
<小春音アミ>
┣<B4>
┣<C4>
┣<E4>
┣<G4>
・・・・
|

4音階(C4低音、E4、G4、B4裏声)
|
例えば、E4ディレクトリの中には様々な声素WAVファイルがはいっています。
あ_E4.wav
い_E4.wav
う_E4.wav
・・・・
き_E4.wav
ぎ_E4.wav
きぇ_E4.wav
きゃ_E4.wav
・・・・
| UTAU 音声ファイルスペック |
| ファイル名 | あ.wav、い.wav、う.wav など |
| ファイル形式 | wav形式、PCM / 44100 / 16bit |
■声音源の確認
念のため、WAVファイルの形式を確認してみましょう。
Ref.音ファイル(拡張子:WAVファイル)のデータ構造について
WAVファイルの先頭44バイトのヘッダー情報を表示するプログラムです。
$ vi check_wavfile.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
int main(int argc, char** argv) {
FILE *infp;
WAVE_FORMAT audio;
char str[32];
if (!(infp=fopen(argv[1],"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
memset(str,0,sizeof(str));
memcpy(str,audio.ChunkID,sizeof(audio.ChunkID));
printf("ChunkID = %s\n",str);
printf("chunkSize = %d\n",audio.ChunkSize);
memcpy(str,audio.Format,sizeof(audio.Format));
printf("Format = %s\n",str);
memcpy(str,audio.Subchunk1ID,sizeof(audio.Subchunk1ID));
printf("Subchunk1ID = %s\n",str);
printf("Subchunk1Size = %d\n",audio.Subchunk1Size);
printf("AudioFormat = %d\n",audio.AudioFormat);
printf("NumChannels = %d\n",audio.NumChannels); // 1: mono or 2: stereo
printf("SampleRate = %d\n",audio.SampleRate); // 8kHz:8000 , 16kHz:16000, ...
printf("ByteRate = %d\n",audio.ByteRate);
printf("BlockAlign = %d\n",audio.BlockAlign);
printf("BitPerSample = %d\n",audio.BitPerSample); // 8bit or 16bit
memcpy(str,audio.Subchunk2ID,sizeof(audio.Subchunk2ID));
printf("Subchunk2ID = %s\n",str);
printf("Subchunk2Size = %d\n",audio.Subchunk2Size);
fclose(infp);
return 1;
}
$ gcc -Wall -o check_wavfile check_wavfile.c
$ ./check_wavfile あ_E4.wav
ChunkID = RIFF ← RIFF で固定
chunkSize = 64406 ←ファイルサイズ - (ChunkID[4]+chunkSize[4])
Format = WAVE
Subchunk1ID = fmt ← fmt で固定
Subchunk1Size = 16 ← リニアPCMの場合16
AudioFormat = 1 ← 非圧縮リニアPCMフォーマットは1
NumChannels = 1 ← モノラル:1/ステレオ:2
SampleRate = 44100
ByteRate = 88200 ← サンプリング周波数 x ブロックサイズ
BlockAlign = 2 ← モノラル16bit:2/ステレオ16bit:4
BitPerSample = 16 ← 1サンプルに必要なビット数
Subchunk2ID = data ← data で固定
Subchunk2Size = 64370 ← ファイルサイズ - 44
「あ」(あ_E4.wav)の声を聴いてみましょう。
$ aplay あ_E4.wav
0..73秒(Subchunk2Size/(SampleRate*BlockAlign))の長さがあります。
■WAVファイルの内容確認
次に、WAVファイル(あ_E4.wav)の中身をみてみます。
バイナリーのWAVファイルを配列に変換して出力しています。
$ vi bin2array.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
int main(int argc, char** argv) {
FILE *infp, *outfp;
WAVE_FORMAT audio;
unsigned int filesize, column;
char fname[16];
unsigned char byte;
if (!(infp=fopen(argv[1],"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
filesize = audio.ChunkSize + 8;
rewind(infp);
sprintf(fname,"%s.h",argv[2]);
outfp=fopen(fname,"w+");
fprintf(outfp, "unsigned char %s[%d] = {\n",argv[2],filesize);
for (column=0;filesize>0;column++) {
fread(&byte,sizeof(unsigned char),1,infp);
if ((--filesize)==0) {
fprintf(outfp,"0x%02x};\n",byte);
} else if ((column+1)%16) {
fprintf(outfp,"0x%02x,",byte);
} else {
fprintf(outfp,"0x%02x,\n",byte);
}
}
fclose(infp);
fclose(outfp);
return 1;
}
$ gcc -Wall -o bin2array bin2array.c
$ ./bin2array あ_E4.wav A
A.h が生成されます。
unsigned char A[64414] = {
0x52,0x49,0x46,0x46,0x96,0xfb,0x00,0x00,0x57,0x41,
0x56,0x45,0x66,0x6d,0x74,0x20,0x10,0x00,0x00,0x00,
0x01,0x00,0x01,0x00,0x44,0xac,0x00,0x00,0x88,0x58,
0x01,0x00,0x02,0x00,0x10,0x00,0x64,0x61,0x74,0x61,
0x72,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
~
0x00,0x00,0x00,0x00,0xf0,0xff,0xe5,0xff,0xe5,0xff,
0xde,0xff,0xd9,0xff,0xd4,0xff,0xcc,0xff,0xcd,0xff,
0xcc,0xff,0xda,0xff,0xee,0xff,0xf4,0xff,0x00,0x00,
~
0x7b,0x03,0xec,0x02,0x8c,0x02,0x03,0x02,0x33,0x01,
0x6c,0x00,0xf8,0xff};
44バイトのWAVヘッダーのあとに無音部分があり、
続いて、声のデータがあります。
■音声ファイルの部分抽出
この無音部分のあとの0.25秒分を切り出してみます。
ヘッダー情報のチャンクサイズとサブチャンク2サイズを変更して、ファイルに書き出します。
それに続けて、無音部分を飛ばして、抽出した(SampleRate/2)バイト分の音声データを出力します。
1サンプルに必要なビット数は16ビットなので、0.25秒分になります。
datasize = audio.SampleRate / 2;
audio.ChunkSize = datasize + sizeof(WAVE_FORMAT) - 8;
audio.Subchunk2Size = datasize;
fwrite(&audio,sizeof(WAVE_FORMAT),1,outfp);
$ vi extract.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
int main(int argc, char** argv) {
FILE *infp, *outfp;
WAVE_FORMAT audio;
unsigned int datasize;
char fname[16];
unsigned char byte;
if (!(infp=fopen(argv[1],"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
datasize = audio.Subchunk2Size;
sprintf(fname,"%s.wav",argv[2]);
outfp=fopen(fname,"w+b");
// 無音部分の読み飛ばし
while (1) {
fread(&byte,1,1,infp);
if ((byte==0x00)||(byte==0x01)) {
fread(&byte,1,1,infp);
} else {
fread(&byte,1,1,infp);
break;
}
}
datasize = audio.SampleRate / 2;
audio.ChunkSize = datasize + sizeof(WAVE_FORMAT) - 8;
audio.Subchunk2Size = datasize;
fwrite(&audio,sizeof(WAVE_FORMAT),1,outfp);
for (int cnt=0; cnt < datasize; cnt++) {
fread(&byte,1,1,infp);
fwrite(&byte,1,1,outfp);
}
fclose(infp);
fclose(outfp);
return 1;
}
$ gcc -Wall -o extract extract.c
$ ./extract あ_E4.wav A
$ aplay A.wav
■モノラル8ビット変換
モノラル16ビットから8ビットに落としてみました。
datasize = audio.SampleRate / 2;
audio.ChunkSize = datasize + sizeof(WAVE_FORMAT) - 8;
audio.ByteRate = audio.SampleRate;
audio.BlockAlign = 1;
audio.BitPerSample = 8;
audio.Subchunk2Size = datasize / 2;
fwrite(&audio,sizeof(WAVE_FORMAT),1,outfp);
for (int cnt=0; cnt < datasize; cnt+=2) {
fread(&byte2,2,1,infp);
byte = (byte2>>8)&0x00ff;
fwrite(&byte,1,1,outfp);
}
$ vi conv16to8.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
int main(int argc, char** argv) {
FILE *infp, *outfp;
WAVE_FORMAT audio;
unsigned int datasize;
char fname[16];
unsigned char byte;
unsigned short byte2;
if (!(infp=fopen(argv[1],"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
datasize = audio.Subchunk2Size;
sprintf(fname,"%s.wav",argv[2]);
outfp=fopen(fname,"w+b");
// 無音部分の読み飛ばし
while (1) {
fread(&byte,1,1,infp);
if ((byte==0x00)||(byte==0x01)) {
fread(&byte,1,1,infp);
} else {
fread(&byte,1,1,infp);
break;
}
}
datasize = audio.SampleRate / 2;
audio.ChunkSize = datasize + sizeof(WAVE_FORMAT) - 8;
audio.ByteRate = audio.SampleRate;
audio.BlockAlign = 1;
audio.BitPerSample = 8;
audio.Subchunk2Size = datasize / 2;
fwrite(&audio,sizeof(WAVE_FORMAT),1,outfp);
for (int cnt=0; cnt < datasize; cnt+=2) {
fread(&byte2,2,1,infp);
byte = (byte2>>8)&0x00ff;
fwrite(&byte,1,1,outfp);
}
fclose(infp);
fclose(outfp);
return 1;
}
$ gcc -Wall -o conv16to8 conv16to8.c
$ ./conv16to8 あ_E4.wav A8
$ aplay A8.wav
■声をつなぎ合わせます
声ファイルを切り貼りして、しゃべらせてみます。
$ vi speech.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
unsigned int pos = 0;
unsigned char *data;
WAVE_FORMAT audio;
int extractVoice(char *fname) {
FILE *infp;
unsigned char byte;
int blocksize;
if (!(infp=fopen(fname,"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
if (pos==0) {
blocksize = audio.SampleRate / 2;
data = (unsigned char *)malloc(blocksize*100);
} else {
blocksize = audio.SampleRate / 2;
}
// 無音部分の読み飛ばし
while (1) {
fread(&byte,1,1,infp);
if ((byte==0x00)||(byte==0x01)) {
fread(&byte,1,1,infp);
} else {
fread(&byte,1,1,infp);
break;
}
}
fread(&data[pos],blocksize,1,infp);
pos+=blocksize;
fclose(infp);
return 1;
}
int main(int argc, char** argv) {
FILE *outfp;
extractVoice("ど_E4.wav");
extractVoice("う_E4.wav");
extractVoice("も_E4.wav");
extractVoice("れ_E4.wav");
extractVoice("い_E4.wav");
extractVoice("む_E4.wav");
extractVoice("で_E4.wav");
extractVoice("す_E4.wav");
audio.ChunkSize = sizeof(WAVE_FORMAT) - 8 + pos;
audio.Subchunk2Size = pos;
outfp=fopen("speech.wav","w+b");
fwrite(&audio,sizeof(WAVE_FORMAT),1,outfp);
fwrite(data,pos,1,outfp);
free(data);
fclose(outfp);
return 1;
}
$ gcc -Wall -o speech speech.c
$ ./speech
$ aplay speech.wav
長音が含まれる場合は、その音に続けて、母音を、促音(そくおん)の場合は、さらに音を短くして、そのあとに、
無音部分を挿入すると、うまくいくかもしれません。
■応用例
音声ファイルに別のファイルを埋め込んでみます。
音声データの最下位1ビットを埋め込みファイル用に使用します。
$ vi implant.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
char Subchunk1ID[4];
unsigned int Subchunk1Size;
unsigned short AudioFormat;
unsigned short NumChannels;
unsigned int SampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short BitPerSample;
char Subchunk2ID[4];
unsigned int Subchunk2Size;
} WAVE_FORMAT;
int main(int argc, char** argv) {
FILE *infp, *textfp, *impfp;
WAVE_FORMAT audio;
unsigned int datasize;
long statsize;
unsigned short textsize, implantsize;
unsigned char byte, txbyte;
struct stat statBuf;
unsigned char id = 'X';
if (!(infp=fopen(argv[1],"rb"))) return 0;
fread(&audio,sizeof(WAVE_FORMAT),1,infp);
datasize = audio.Subchunk2Size;
// テキストファイルの埋め込み
if (argc==4) {
if (stat(argv[2], &statBuf) == 0)
statsize = statBuf.st_size;
textsize = (unsigned short)statsize;
implantsize = (textsize + 3) * 8; // 3: length + 識別子
if (datasize < implantsize) {
printf("WAV File is too short!\n");
return 0;
}
impfp=fopen(argv[3],"w+b");
fwrite(&audio,sizeof(WAVE_FORMAT),1,impfp);
// 識別子付加
for (int i=0; i < 8; i++, datasize--) {
fread(&byte,1,1,infp);
byte = (byte&0xfe) | ((id>>i)&0x01);
fwrite(&byte,1,1,impfp);
}
// 埋め込みファイルサイズ付加
for (int i=0; i < 16; i++, datasize--) {
fread(&byte,1,1,infp);
byte = (byte&0xfe) | (unsigned char)((textsize>>i)&0x0001);
fwrite(&byte,1,1,impfp);
}
// テキストファイル埋め込み
textfp=fopen(argv[2],"rb");
while (textsize>0) {
fread(&txbyte,1,1,textfp);
for (int i=0; i < 8; i++, datasize--) {
fread(&byte,1,1,infp);
byte = (byte&0xfe) | ((txbyte>>i)&0x01);
fwrite(&byte,1,1,impfp);
}
textsize--;
}
// 残りのWAVファイル出力
while (datasize>0) {
fread(&byte,1,1,infp);
fwrite(&byte,1,1,impfp);
datasize--;
}
fclose(textfp);
fclose(impfp);
// テキストファイルの取り出し
} else if (argc==2) {
// 識別子取得
txbyte = 0x00;
for (int i=0; i < 8; i++, datasize--) {
fread(&byte,1,1,infp);
txbyte |= (byte & 0x01)<<i;
}
if (txbyte!=id) {
printf("File not included!\n");
return 0;
}
// 埋め込みファイルサイズ取得
textsize = 0;
for (int i=0; i < 16; i++, datasize--) {
fread(&byte,1,1,infp);
textsize |= (byte & 0x01)<<i;
}
// テキストファイル取り出し
while (textsize>0) {
txbyte = 0x00;
for (int i=0; i < 8; i++) {
fread(&byte,1,1,infp);
txbyte |= (byte & 0x01)<<i;
}
putc(txbyte,stdout);
textsize--;
}
}
fclose(infp);
return 1;
}
$ gcc -Wall -o implant implant.c
音声ファイルあ_E4.wavに、プログラムimplant.cを埋め込んでみます。
埋め込み前後で音声ファイルのサイズは変わりません。
$ ./implant あ_E4.wav implant.c Aimp.wav
$ aplay Aimp.wav
音声ファイルから、埋め込んだプログラムを取り出してみます。
$ ./implant Aimp.wav
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
typedef struct {
char ChunkID[4];
unsigned int ChunkSize;
char Format[4];
~
あらかじめ相手に埋め込み・抽出プログラムを渡しておくと、ボイスメッセージとともに資料を閲覧するという使い方もできます。`
ここで、音声ファイルのノイズが気になる場合は、2バイト分の最下位1ビットをデータに使用するように改造するとよいと思います。
また、より複雑な埋め込みアルゴリズムを使うことで機密性が向上します。
■その他文献
・Julius 汎用大語彙連続音声認識エンジン
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|