2017年10月1日日曜日

C#でWavの無音削除

 手伝ったイベントの運営で使ってた特小を録音してたんだけど、結構面白かったのでデータとして残したい。でも6時間分は長すぎる。ということで無音部分を削除したい。と思ったけど、ちょっと調べた限りだとSoundEngine Freeでは処理できないらしい。いちいち新しいソフトの使い方を調べるのも面倒。
 ということでC#でサクッと作ってみた。サクッとといっても、この程度のコードで1時間もかかってるのだが。

using (FileStream fsr = new FileStream(path, FileMode.Open, FileAccess.Read))
using (FileStream fsw = new FileStream(path + " sd.wav", FileMode.Create, FileAccess.Write))
{
 byte[] buff = new byte[36];

 fsr.Read(buff, 0, 36);
 fsw.Write(buff, 0, 36);

 if (Encoding.ASCII.GetString(buff, 0, 4) != "RIFF" ||
  Encoding.ASCII.GetString(buff, 8, 4) != "WAVE" ||
  Encoding.ASCII.GetString(buff, 12, 4) != "fmt ")
 {
  throw (new Exception());
 }

 if (
  BitConverter.ToUInt16(buff, 20) != 1 || // fmt id
  BitConverter.ToUInt16(buff, 22) != 1 || // ch
  BitConverter.ToUInt16(buff, 34) != 16 || // bit depth
  false)
 {
  throw (new Exception());
 }

 UInt32 SamplingRate = BitConverter.ToUInt32(buff, 24);

 while (true)
 {
  fsr.Read(buff, 0, 4);
  if (Encoding.ASCII.GetString(buff, 0, 4) == "data")
  {
   break;
  }

  fsr.Read(buff, 4, 4);
  int size = BitConverter.ToInt32(buff, 4);

  byte[] tmp = new byte[size];
  fsr.Read(tmp, 0, size);
 }

 fsr.Read(buff, 0, 4);
 UInt32 len = BitConverter.ToUInt32(buff, 0);

 fsw.Write(Encoding.ASCII.GetBytes("data"), 0, 4);
 long sizePos = fsw.Position;
 fsw.Write(BitConverter.GetBytes((UInt32)0), 0, 4);

 double thrStart = -30; // 録音開始レベル(dB)
 double thrEnd = -30; // 録音停止レベル(dB)
 double delay = 3; // 録音停止レベルを下回ってから、実際に録音を停止するまでの時間(秒)

 UInt32 delayCount = (UInt32)(delay * SamplingRate * 2 * 1);
 bool record = false;

 UInt32 count = 0;
 UInt32 end = 0;

 double fs = Math.Pow(2, 15);
 int thrStartInt = (int)(Math.Pow(10, thrStart / 20) * fs);
 int thrEndInt = (int)(Math.Pow(10, thrEnd / 20) * fs);

 // len = Math.Min(len, (UInt32)(1 * 60 * 60 * SamplingRate * 2 * 1)); // とりあえず1時間分だけ処理

 for (UInt32 i = 0; i < len; i += 2)
 {
  fsr.Read(buff, 0, 2);

  Int16 val = BitConverter.ToInt16(buff, 0);

  if (val < 0)
  {
   val *= -1;
  }

  if (record)
  {
   if (val > thrEndInt)
   {
    end = i + delayCount;
   }
   if (i == end)
   {
    record = false;
   }
  }
  else
  {
   if (val > thrStartInt)
   {
    record = true;
   }
  }

  if (record)
  {
   fsw.Write(buff, 0, 2);
   count++;
  }
 }

 fsw.Position = sizePos;
 fsw.Write(BitConverter.GetBytes((UInt32)(count * 2)), 0, 4);
}

 string pathにファイルパスを設定しておけば、thrStart[dB]を超えたタイミングで書き出しを開始し、thrEnd[dB]を下回ったタイミングから一定時間経過すると書き出しを停止する。これをずーっと繰り返す、という処理。
 入力されるファイル形式は16bit1chのみに対応。

 意外と時間がかかる。整数データと侮ってると、秒88.2kByteのデータ量に飲まれる。
 最初、forの中でvalをdBに変換し、thrStart/thrEndと比較していた。先にIntに変換して、forの中で浮動小数点演算を行わないようにすると、4倍近く高速化した。
 6時間分のWavを処理するのも、ちょっと待ってれば終わる程度。

 6時間分のWavは、delay=3で1時間分くらいになった。BGM代わりに聞けるくらいの量かな。

 某声優の声が空電ノイズ混じりに聞こえてくるのに興奮したりとか、やっぱ僕はこういうのが好きなんだなぁ。グフフ。

 録音は僕が背負ってたバックパックに入れたIC-R6にICレコーダーを接続して行ったので、僕から離れた位置で送信された声は聞き取りづらい。けどまぁ面白い。
 ちゃんと記録として残すなら、利得の高いアンテナを使ったりとかする必要があるんだろうけど、まぁ遊び半分だからね。
 サバゲ中の無線とか取れば面白そうだけど、僕が参加するサバゲだとTRは全然使わないからなぁ。
 スケルチが閉じて十分に音量が下がれば良いので、エアバンドとかにも使える。けどウチの地域はエアバンドほとんど入らないから使えない。
 もちろんアマチュア無線にも使えるけど、このあたりは違法無線しか無いので聞いても面白みも何もない。

0 件のコメント:

コメントを投稿