Perlでバイナリファイルをいじる場合にいろいろと同じ問題に遭遇するので、忘れないようにメモ。
バイナリファイルの読み込み
読み込む場合に”getc”使うと、ファイルが終わってないのに終了してしまうことがある。
正しくは”read”を使う。
my $filename = $ARGV[0];
open my $file,'<',$filename or die;
binmode $file;
my $val;
while(read($file, $val, 1)){
}
これで1byteづつ処理できる。実際はバイナリの構造にあわせてreadするバイト数を変える。
読んだバイナリの処理
バイナリを数値として処理するためには、その構造にあわせてunpackする。
unpack("C",$val); #unsigned byte
unpack("S",$val); #unsigned short
unpack("L",$val); #unsigned long
unpack("c",$val); #signed byte
unpack("s",$val); #signed short
unpack("l",$val); #signed long
bit処理の場合は、いろいろ問題がある。
ビット反転”~”は、ビット長にあわせてpackしてから処理する。
~pack("c",$i); # $iは数値。一旦byteにしてからビット反転
ビットの”&(and),|(or),^(xor)”は、両方同じビット長にする。
もしくはunpackしてから処理する(unpackしても同じビット長(環境依存)になるからね)
pack("s",$j) ^ pack("s",$i); # $i,jは数値。両方shortにしている
unpack("s",$val) ^ $i; # $valは読んだバイナリ,$iは数値。
ビットシフト”«”, “»“は、数値化(unpack)してからじゃないと動かない。。。(何故?)
unpack("S",$i) << 1
と、いうことで、実は「ビット反転」以外はunpackして処理する。ということ。
例として、μ-lawとPCMの変換(なんのこっちゃ?という人はwavファイルの勉強を。。)
my $mantissa = unpack("C",~$val); #$valが読み込んだバイナリ
my $sign = (unpack("C",$val) < (0x0080))? -1:1;
my $exponent = ($mantissa >> 4) & (0x0007);
my $segment = $exponent + 1;
$mantissa = $mantissa & (0x000F);
my $step = (0x0004) << $segment;
my $g191_value = pack("S",$sign * (((0x0080) << $exponent) + $step*$mantissa + $step/2 - 4*33));
#最後に"S"でpackしてunsigned shortに
バイナリの書き出し
書き出したい構造にあわせてpackしてやる。
wavファイルのヘッダとかだとこうなる。
my $form = "";
$form .= "RIFF";
$form .= pack("L",$size + 36); #data size + headersize(-8)
$form .= "WAVE";
$form .= "fmt ";
$form .= pack("L",16); #fmt chunk size
$form .= pack("S",1); # pcm
$form .= pack("S",1); # mono
$form .= pack("L",8000); # 8k sampling
$form .= pack("L",16000); # Byte/sec = sampling * 2byte(16bit 量子化) * 1(mono)
$form .= pack("S",2); # Byte/sample(8bit量子化) * channel(mono)
$form .= pack("S",16); # 8bit量子化
$form .= "data";
$form .= pack("L",$size); #data size
おまけ
signedとunsignedを変換するにはpackしてunpackというややこしい方法が必要。
my $i = -100;
my $unsigned_value = unpack("C",pack("c",$i));
wav関連でもぞもぞしてるのバレバレですね。
ツイート