池と沼は同じだった

いつか札幌に移住したい26歳です。

カメラと技術と時々音楽

msplitter

100[ms]ごとにWAVEファイルを分割したファイルが研究で必要になって,WAVファイルをms単位で分割できるソフトを探してみたんだけど,なかなか見つからない.(単に探し方が下手なだけ)
sec単位とかならあるんだけどなー.


ということで,研究ほっぽり投げてがーっと書いた.
久々にWAVEヘッダーいじった.完全に忘れてた.これはあかん.
必要な部分しか書いてないので,8/16bit, RIFF/Linear-PCMのみ,factチャンク未対応.
エラー処理も未対策,テストもそんなにしていないのであくまでなんかの参考までに(情報系の大学生とか.ならんか)


参考:wav ファイルフォーマット

利用例

./msplitter -i input.wav -o output/out -t 100

オプション

ーi: 入力WAVEファイル名
ーo: 出力WAVEファイル名(拡張子含めない).連番で出力される
ーt: 分割するインターバル [ms]

// msplitter.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// めんどくさいので一箇所に集める
typedef struct {
  char inputfile[64], outputfile[64];
  unsigned long bytes, samplingrate, wavesize, dataspeed, sample, offset;
  unsigned short fmtid, channels, blocksize, bitsample;
} Wav;

// prototype call
int main(int argc, char *argv[]);
int cmdline_parser(int argc, char *argv[], Wav *wh);
void kill(char *msg, char *type);
void fread2(void *buf, size_t size, size_t n, FILE *fp);
void read_waveheader(Wav *wh, FILE *fp);
void write_waveheader(Wav *wh, FILE *fp);

int main(int argc, char *argv[]) {
  Wav wh;
  // get splittime and filename from commandline
  int splittime = cmdline_parser(argc, argv, &wh);
  int i, cnt=0, offset;
  FILE *fp;

  unsigned char *wavedata; // 8bit
  signed short *wavedata16; // 16bit
  
  char outputfile_number[64];
  setvbuf(stdout,0,_IONBF,0); // for console2, mintty

  printf("msplitter (c) 2012 hurikake\n");
  printf("*-------------------------------------------------*\n");
  
  printf("input: %s, output: %s*****.wav\n", wh.inputfile, wh.outputfile);
  printf("splittime: %d [ms]\n",splittime);
  
  if((fp = fopen(wh.inputfile, "rb")) == NULL)
    kill("open error","");

  // read waveheader
  read_waveheader(&wh, fp);
  
  // read wavedata
  if(wh.bitsample == 8) {
    if( (wavedata = (char *) malloc(wh.wavesize * sizeof(char))) == NULL)
      kill("malloc error","char");
  }
  else {
    if( (wavedata16 = (short *) malloc(wh.wavesize * sizeof(short))) == NULL)
      kill("malloc error","short");
  }
  
  printf("channel: %d\n",wh.channels);
  printf("sampling rate: %d [Hz]\n",wh.samplingrate);
  printf("bit: %d [bit]\n",wh.bitsample);
  printf("wavesize: %d [byte] \n",wh.wavesize);
  
  // 1 (or 2) byte(s) read for wavesize (times)
  if(wh.bitsample == 8)
    fread2(wavedata, sizeof(char), wh.wavesize, fp);
  else
    fread2(wavedata16, sizeof(short), wh.wavesize / sizeof(short), fp);
  fclose(fp);

  // 1 file sample: (splittime / 1000) * samplingrate [sample]
  wh.sample = (int) ( ((double)splittime / 1000.0) * wh.samplingrate);
  printf("frame ( * channel) : %d [sample]\n", wh.sample);
  
  // file save

  // 1 file size (without 44byte: wave header)
  wh.offset = wh.sample * wh.channels * (wh.bitsample / 8);
  printf("offset : %d [byte]\n", wh.offset);

  printf("*-------------------------------------------------*\n");
  
  for(i=0; i<wh.wavesize; i+=wh.offset) {
    // last part of file
    if( (wh.wavesize-i) < wh.offset)
      wh.offset = wh.wavesize - i;

    // make file name
    sprintf(outputfile_number, "%s%05d.wav", wh.outputfile, cnt);

    // file make
    if( (fp = fopen(outputfile_number, "wb")) == NULL)
      kill("filewrite error","fopen");

    write_waveheader(&wh, fp);
    
    // 8bit or 16bit
    if(wh.bitsample == 8)
      fwrite(&wavedata[i], wh.offset, 1, fp);
    else
      fwrite(&wavedata16[i/(wh.bitsample/8)], wh.offset, 1, fp);
    
    fclose(fp);
    
    if(cnt%10 == 0) printf("\n");
    cnt++;
    printf("(^^) ");
  }
  if(wh.bitsample == 8) free(wavedata);
  else free(wavedata16);
  
  printf("\n\nsuccess! %d file(s) created\n", cnt);
  return 0;
}

int cmdline_parser(int argc, char *argv[], Wav *wh) {

	// -i input filename
	// -o output filename (without extension)
	// -t split time (ms)
  // return: splittime

  int i, splittime;
  
  for(i=1;i<argc;i++) {
    if(argv[i][0] != '-') break;
    if(++i>=argc) exit(0);

    switch(argv[i-1][1]) {
    case 'i':
      strcpy(wh->inputfile, argv[i]);
      break;

    case 'o':
      strcpy(wh->outputfile, argv[i]);
      break;
      
    case 't':
      splittime = atoi(argv[i]);
      break;

    default:
      fprintf(stderr,"unknown option: -%c\n", argv[i-1][1]);
      exit(0);
      break;
    }
  }
  return splittime;
}

// error
void kill(char *msg, char *type) {
  fprintf(stderr, "%s: %s\n", msg, type);
  exit(EXIT_FAILURE);
  return;
}

// fread (error check version)
void fread2(void *buf, size_t size, size_t n, FILE *fp) {
  int a;
  if((a=fread(buf, size, n, fp)) < n) {
    kill("fread error", "");
  }
  return;
}


void read_waveheader(Wav *wh, FILE *fp) {
  int i;
  unsigned char temp[4];
  char chkchunk[4][4] = {
    {'R','I','F','F'},
    {'d','u','m','m'}, // dummy
    {'W','A','V','E'},
    {'f','m','t',' '},
  };

  // ヘッダーの16byte分読み込み&存在チェック
  for(i=0;i<4;i++) {
    fread2(temp, 4, 1, fp);

    // i = 1: filesize
    if( (i != 1) && (memcmp(temp, chkchunk[i], 4) != 0) )
      kill("does not exist", chkchunk[i]);
  }

  // read bytes
  fread2(&wh->bytes, 4, 1, fp);
  // read fmtID
  fread2(&wh->fmtid, 2, 1, fp);
  // read channels
  fread2(&wh->channels, 2, 1, fp);
  // read sampling rate
  fread2(&wh->samplingrate, 4, 1, fp);
  // read dataspeed
  fread2(&wh->dataspeed, 4, 1, fp);
  // read blocksize
  fread2(&wh->blocksize, 2, 1, fp);
  // read bitsample
  fread2(&wh->bitsample, 2, 1, fp);
  // read 'data'
  fread2(temp, 4, 1, fp);
  if((memcmp(temp, "data", 4) != 0))
    kill("does not exist", "data");
  // read wavesize
  fread2(&wh->wavesize, 4, 1, fp);
  return;
}

void write_waveheader(Wav *wh, FILE *fp) {
  unsigned long byte = 44 + wh->offset - 8;
  fwrite("RIFF", 4, 1, fp);
  fwrite(&byte, 4, 1, fp);
  fwrite("WAVEfmt ", 8, 1, fp);
  fwrite(&wh->bytes, 4, 1, fp);
  fwrite(&wh->fmtid, 2, 1, fp);
  fwrite(&wh->channels, 2, 1, fp);
  fwrite(&wh->samplingrate, 4, 1, fp);
  fwrite(&wh->dataspeed, 4, 1, fp);
  fwrite(&wh->blocksize, 2, 1, fp);
  fwrite(&wh->bitsample, 2, 1, fp);
  fwrite("data", 4, 1, fp);
  fwrite(&wh->offset, 4, 1, fp);
  return;
}


改めて見るとめっちゃ読みにくい.
wh.sample が1分割分のサンプル数(チャンネル数やビット数は考慮していない),wh.offset が1分割分のバイト数(考慮後).すなわちwh.offset [byte]の波形サイズで,wh.offset + 44 [byte]のファイルが生成される.44byte分はWAVヘッダーの分.
ちなみにwh.offset = wh.sample * ビット数/8 * チャンネル数 である.8bit,monoのファイルなら1サンプル=1バイトだからそのまま.16bit,stereoなら1サンプル=4バイト.

汚いけどこれで一応8bit,stereoのファイルを100msごとに分割できた.
あと実行してみてるとわかるけど遊び心で進捗に顔文字入れたらめっちゃうざい感じになったので各自直してください.

実行例

$ ./msplitter -i apart.wav -o apart -t 20000
msplitter (c) 2012 hurikake
*-----------------------------------------*
input: apart.wav, output: apart*****.wav
splittime: 20000 [ms]
channel: 2
sampling rate: 44100 [Hz]
bit: 16 [bit]
wavesize: 68125680 [byte]
sample ( * channel) : 882000 [sample]
offset : 3528000 [byte]
*-----------------------------------------*

(^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^)
(^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^) (^^)

success! 20 file(s) created