#!/usr/bin/perl -w package MPEG::Audio::Frame; # BLECH! With 5.005_04 compatibility the pretty 0b000101001 notation went away, # and now we're stuck using hex. Phooey! use strict; #use warnings; use integer; # fields::new is not used because it is very costly in such a tight loop. about 1/4th of the time, according to DProf #use fields qw/ # headhash # binhead # header # content # length # bitrate # sample # offset # crc_sum # calculated_sum # broken #/; use overload '""' => \&asbin; use vars qw/$VERSION $free_bitrate $lax $mpeg25/; $VERSION = 0.09; $mpeg25 = 1; # normally support it # constants and tables BEGIN { if ($] <= 5.006){ require Fcntl; Fcntl->import(qw/SEEK_CUR/); } else { require POSIX; POSIX->import(qw/SEEK_CUR/); } } my @version = ( 1, # 0b00 MPEG 2.5 undef, # 0b01 is reserved 1, # 0b10 MPEG 2 0, # 0b11 MPEG 1 ); my @layer = ( undef, # 0b00 is reserved 2, # 0b01 Layer III 1, # 0b10 Layer II 0, # 0b11 Layer I ); my @bitrates = ( # 0/free 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 # bits [ # mpeg 1 [ undef, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 ], # l1 [ undef, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 ], # l2 [ undef, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 ], # l3 ], [ # mpeg 2 [ undef, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 ], # l1 [ undef, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # l3 [ undef, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # l3 ], ); my @samples = ( [ # MPEG 2.5 11025, # 0b00 12000, # 0b01 8000, # 0b10 undef, # 0b11 is reserved ], undef, # version 0b01 is reserved [ # MPEG 2 22050, # 0b00 24000, # 0b01 16000, # 0b10 undef, # 0b11 is reserved ], [ # MPEG 1 44100, # 0b00 48000, # 0b01 32000, # 0b10 undef, # 0b11 is reserved ], ); # stolen from libmad, bin.c my @crc_table = ( 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 ); sub CRC_POLY () { 0x8005 } ### my @protbits = ( [ 128, 256 ], # layer one undef, [ 136, 256 ], # layer three ); my @consts; sub B ($) { $_[0] == 12 ? 3 : (1 + ($_[0] / 4)) } sub M ($) { my $s = 0; $s += $consts[$_][1] for (0 .. $_[0]-1); $s%=8; my $v = ''; vec($v,8-$_,1) = 1 for $s+1 .. $s+$consts[$_[0]][1]; "0x" . unpack("H*", $v); } sub R ($) { my $i = 0; my $m = eval "M_$consts[$_[0]][0]()"; $i++ until (($m >> $i) & 1); $i; } BEGIN { @consts = ( # [ $name, $width ] [ SYNC => 3 ], [ VERSION => 2 ], [ LAYER => 2 ], [ CRC => 1 ], [ BITRATE => 4 ], [ SAMPLE => 2 ], [ PAD => 1 ], [ PRIVATE => 1 ], [ CHANMODE => 2 ], [ MODEXT => 2 ], [ COPY => 1 ], [ HOME => 1 ], [ EMPH => 2 ], ); my $i = 0; foreach my $c (@consts){ my $CONST = $c->[0]; eval "sub $CONST () { $i }"; # offset in $self->{header} eval "sub M_$CONST () { " . M($i) ." }"; # bit mask eval "sub B_$CONST () { " . B($i) . " }"; # offset in read()'s @hb eval "sub R_$CONST () { " . R($i) . " }"; # amount to right shift $i++; } } # constructor and work horse sub read { my $pkg = shift || return undef; my $fh = shift || return undef; local $/ = "\xff"; # get readline to find 8 bits of sync. my $offset; # where in the handle my $header; # the binary header data... what a fabulous pun. my @hr; # an array of integer OUTER: { while (defined(<$fh>)){ # readline, readline, find me a header, make me a header, catch me a header. somewhate wasteful, perhaps. But I don't want to seek. $header = "\xff"; (read $fh, $header, 3, 1 or return undef) == 3 or return undef; # read the rest of the header my @hb = unpack("CCCC",$header); # an array of 4 integers for convenient access, each representing a byte of the header # I wish vec could take non powers of 2 for the bit width param... *sigh* # make sure there are no illegal values in the header ($hr[SYNC] = ($hb[B_SYNC] & M_SYNC) >> R_SYNC) != 0x07 and next; # see if the sync remains ($hr[VERSION] = ($hb[B_VERSION] & M_VERSION) >> R_VERSION) == 0x00 and ($mpeg25 or next); ($hr[VERSION]) == 0x01 and next; ($hr[LAYER] = ($hb[B_LAYER] & M_LAYER) >> R_LAYER) == 0x00 and next; ($hr[BITRATE] = ($hb[B_BITRATE] & M_BITRATE) >> R_BITRATE) == 0x0f and next; ($hr[SAMPLE] = ($hb[B_SAMPLE] & M_SAMPLE) >> R_SAMPLE) == 0x03 and next; ($hr[EMPH] = ($hb[B_EMPH] & M_EMPH) >> R_EMPH) == 0x02 and ($lax or next); # and drink up all that we don't bother verifying $hr[CRC] = ($hb[B_CRC] & M_CRC) >> R_CRC; $hr[PAD] = ($hb[B_PAD] & M_PAD) >> R_PAD; $hr[PRIVATE] = ($hb[B_PRIVATE] & M_PRIVATE) >> R_PRIVATE; $hr[CHANMODE] = ($hb[B_CHANMODE] & M_CHANMODE) >> R_CHANMODE; $hr[MODEXT] = ($hb[B_MODEXT] & M_MODEXT) >> R_MODEXT; $hr[COPY] = ($hb[B_COPY] & M_COPY) >> R_COPY; $hr[HOME] = ($hb[B_HOME] & M_HOME) >> R_HOME; # record the offset $offset = tell($fh) - 4; last OUTER; # were done reading for the header } seek $fh, -3, SEEK_CUR; return undef; } my $sum = ''; if (!$hr[CRC]){ (read $fh, $sum, 2 or return undef) == 2 or return undef; } my $bitrate = $bitrates[$version[$hr[VERSION]]][$layer[$hr[LAYER]]][$hr[BITRATE]] || $free_bitrate or return undef; my $sample = $samples[$hr[VERSION]][$hr[SAMPLE]]; my $use_smaller = $hr[VERSION] == 2 || $hr[VERSION] == 0; # FIXME VERSION == 2 means no support for MPEG2 multichannel my $length = $layer[$hr[LAYER]] ? (($use_smaller ? 72 : 144) * ($bitrate * 1000) / $sample + $hr[PAD]) # layers 2 & 3 : ((($use_smaller ? 6 : 12 ) * ($bitrate * 1000) / $sample + $hr[PAD]) * 4); # layer 1 my $clength = $length - 4 - ($hr[CRC] ? 0 : 2); (read $fh, my($content), $clength or return undef) == $clength or return undef; # appearantly header length is included... learned this the hard way. my $self = bless {}, $pkg; %$self = ( binhead => $header, # binary header header => \@hr, # array of integer header records content => $content, # the actuaol content of the frame, excluding the header and crc length => $length, # the length of the header + content == length($frame->content()) + 4 + ($frame->crc() ? 2 : 0); bitrate => $bitrate, # the bitrate, in kilobits sample => $sample, # the sample rate, in Hz offset => $offset, # the offset where the header was found in the handle, based on tell crc_sum => $sum, # the bytes of the network order short that is the crc sum ); $self; } # methods sub asbin { # binary representation of the frame my $self = shift; $self->{binhead} . $self->{crc_sum} . $self->{content} } sub content { # byte content of frame, no header, no CRC sum my $self = shift; $self->{content} } sub header { # array of records in list context, binary header in scalar context my $self = shift; wantarray ? @{ $self->{header} } : $self->{binhead} } sub crc { # the actual sum bytes my $self = shift; $self->{crc_sum} } sub has_crc { # does a crc exist? my $self = shift; not $self->{header}[CRC]; } sub length { # length of frame in bytes, including header and header CRC my $self = shift; $self->{length} } sub bitrate { # symbolic bit rate my $self = shift; $self->{bitrate} } sub free_bitrate { my $self = shift; $self->{header}[BITRATE] == 0; } sub sample { # symbolic sample rate my $self = shift; $self->{sample} } sub channels { # the data we want is the data in the header in this case my $self = shift; $self->{header}[CHANMODE] } sub stereo { my $self = shift; $self->channels == 0; } sub joint_stereo { my $self = shift; $self->channels == 1; } sub dual_channel { my $self = shift; $self->channels == 2; } sub mono { my $self = shift; $self->channels == 3; } sub modext { my $self = shift; $self->{header}[MODEXT]; } sub _jmodes { my $self = shift; $self->layer3 || die "Joint stereo modes only make sense with layer III" } sub normal_joint_stereo { my $self = shift; $self->_jmodes && $self->joint_stereo && !$self->intensity_stereo && !$self->ms_stereo; } sub intensity_stereo { my $self = shift; $self->_jmodes and $self->joint_stereo and $self->modext % 2 == 1; } sub intensity_stereo_only { my $self = shift; $self->_jmodes && $self->intensity_stereo && !$self->ms_stereo; } sub ms_stereo { my $self = shift; $self->_jmodes and $self->joint_stereo and $self->modext > 1; } sub ms_stereo_only { my $self = shift; $self->_jmodes and $self->ms_stereo && !$self->intensity_stereo; } sub ms_and_intensity_stereo { my $self = shift; $self->_jmodes and $self->ms_stereo && $self->intensity_stereo; } *intensity_and_ms_stereo = \&ms_and_intensity_stereo; sub _bands { my $self = shift; !$self->layer3 || die "Intensity stereo bands only make sense with layers I I"; } sub band_4 { my $self = shift; $self->_bands and $self->modext == 0; } sub band_8 { my $self = shift; $self->_bands and $self->modext == 1; } sub band_12 { my $self = shift; $self->_bands and $self->modext == 2; } sub band_16 { my $self = shift; $self->_bands and $self->modext == 3; } sub any_stereo { my $self = shift; $self->stereo or $self->joint_stereo; } sub seconds { # duration in floating point seconds my $self = shift; no integer; $layer[$self->{header}[LAYER]] ? (($version[$self->{header}[VERSION]] == 0 ? 1152 : 576) / $self->sample()) : (($version[$self->{header}[VERSION]] == 0 ? 384 : 192) / $self->sample()) } sub framerate { no integer; 1 / $_[0]->seconds(); } sub pad { my $self = shift; $self->{header}[PAD]; } sub home { my $self = shift; $self->{header}[HOME]; } sub copyright { my $self = shift; $self->{header}[COPY]; } sub private { my $self = shift; $self->{header}[PRIVATE]; } sub version { my $self = shift; $self->{header}[VERSION]; } sub mpeg1 { my $self = shift; $self->version == 3; } sub mpeg2 { my $self = shift; $self->version == 2; } sub mpeg25 { my $self = shift; $self->version == 0; } sub layer { my $self = shift; $self->{header}[LAYER]; } sub layer1 { my $self = shift; $self->layer == 3; } sub layer2 { my $self = shift; $self->layer == 2; } sub layer3 { my $self = shift; $self->layer == 1; } sub emph { my $self = shift; $self->{header}[EMPH]; } *emphasize = \&emph; *emphasise = \&emph; *emphasis = \&emph; sub offset { # the position in the handle where the frame was found my $self = shift; $self->{offset} } sub crc_ok { not shift->broken; } sub broken { # was the crc broken? my $self = shift; if (not defined $self->{broken}){ return $self->{broken} = 0 unless $self->has_crc; # we assume it's OK if we have no CRC at all return $self->{broken} = 0 unless (($self->{header}[LAYER] & 0x02) == 0x00); # can't sum my $bits = $protbits[$layer[$self->{header}[LAYER]]][$self->{header}[CHANMODE] == 0x03 ? 0 : 1 ]; my $i; my $c = 0xffff; $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ord((substr($self->{binhead},2,1)))) & 0xff]; $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ord((substr($self->{binhead},3,1)))) & 0xff]; for ($i = 0; $bits >= 32; do { $bits-=32; $i+=4 }){ my $data = unpack("N",substr($self->{content},$i,4)); $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ($data >> 24)) & 0xff]; $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ($data >> 16)) & 0xff]; $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ($data >> 8)) & 0xff]; $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ ($data >> 0)) & 0xff]; } while ($bits >= 8){ $c = ($c << 8) ^ $crc_table[(($c >> 8) ^ (ord(substr($self->{content},$i++,1)))) & 0xff]; } continue { $bits -= 8 } $self->{broken} = (( $c & 0xffff ) != unpack("n",$self->{crc_sum})) ? 1 : 0; } return $self->{broken}; } # tie hack sub TIEHANDLE { bless \$_[1],$_[0] } # encapsulate the handle to save on unblessing and stuff sub READLINE { (ref $_[0])->read(${$_[0]}) } # read from the encapsulated handle 1; # keep your mother happy __END__ =pod =head1 NAME MPEG::Audio::Frame - a class for weeding out MPEG audio frames out of a file handle. =head1 SYNOPSIS use MPEG::Audio::Frame; open FILE,"file.mp3"; while(my $frame = MPEG::Audio::Frame->read(\*FILE)){ print $frame->offset(), ": ", $frame->bitrate(), "Kbps/", $frame->sample()/1000, "KHz\n"; # or something. } =head1 DESCRIPTION A very simple, pure Perl module which allows parsing out data from mp3 files, or streams, and chunking them up into different frames. You can use this to accurately determine the length of an mp3, filter nonaudio data, or chunk up the file for streaming via datagram. Virtually anything is possible. =head1 MPEG FORMAT SUPPORT L supports various types of MPEG data. =over 4 =item MPEG-1 Any type of MPEG1 audio is supported, including the backwards compatible multichannel extensions to MPEG-1. See F. =item MPEG-2 Multichannel MPEG 2 has a variation on the multichannel extension, which is not backwards compatible. It still structurally resembles MPEG 1, but there is no publically documented method for telling apart MPEG-2 MC from MPEG-2 LSF. =item MPEG-2 LSF There is another type of MPEG 2, which is more similar to MPEG 2.5, called MPEG-2 low sampling frequency. It the only type of MPEG-2 supported. See F. =item MPEG-2.5 This unofficial standard is also supported. Since it is unofficial, and sometimes causes problems when streams contain garbage between frames, it can be disabled. There is a test, F, which fails if MPEG 2.5 is allowed. It is not included in the distribution because MPEG-2 multichannel is not supported and those tests are just failing bloat. Ask me about darcs access if you're curious. =back =head1 METHODS There are two types of methods in this module, aside from the constructor C. The first kind are accessors, which return the value of a certain header field, like C, which will return the actual integer value of the bits of that field. The second kind is sorts of queries on the accessible data. For example, C checks to see of the C accessor returns the value for mono. =over 4 =item read GLOB This is the constructor method. It receives a reference to a filehandle, and reads the next (hopefully) valid frame it can find on the stream. Please make sure use binmode if you're on a funny platform - the module doesn't know the difference, and shouldn't change stuff, IMHO. =item offset The offset where the frame was found in the handle, as reported by tell(). =item asbin Returns the binary data extracted from the handle. This is (definately|probably) a valid MPEG 1 or 2 audio frame. asbin is also called via the overloaded operator "", so if you treat the frame object like a string, you'd get the binary data you'd get by calling asbin directly. =item content Returns the content of the frame, minus the header and the crc. This is (definately|probably) a valid MPEG 1 or 2 audio frame entity. =item header Returns a list of integers in list context, or a 4 byte long binary string in scalar context. The hash represents the header, split into it's parts, with bits translated into '0' and '1'. The binary string is (definately|probably) a valid MPEG 1 or 2 audio frame header. =item crc Returns the bytes of the checksum, as extracted from the handle. This is (definately) a valid checksum, unless there was none in the frame, in which case it will be undef. It (definately|probably) applies to the frame. =item version Returns the value of the MPEG version bits. =item layer Returns the value of the layer bits. =item length Returns the length, in bytes, of the entire frame. This is the length of the content, plus the four bytes of the header, and the two bytes of the crc, if applicable. =item bitrate Returns the bitrate in kilobits. Note that 128Kbps means 128000, and not 131072. This not the actual integer value from the header. =item free_bitrate This returns true if the bit rate is free. Since free bit rate files have a constant rate, that is anything. The bitrate must be known in advance for the file to be parsable. See C<$MPEG::Audio::Frame::free_bitrate>. =item sample Returns the sample rate in Hz, not the integer value from the header. =item seconds Returns the length, in floating seconds, of the frame. =item framerate Given a constant rate file, of frames like this one, the value returned would be the number of such frames needed to get one second of audio. =item channels Returns the integer value of the channels bits. =item pad Wether or not the frame was padded. =item private Returns the value of the private bit. =item copyright Returns the vavlue of the copyright bit. =item home Returns the value of the original home bit. =item modext The mode extension bits. It is used to specify type of joint stereo to use in layer III, and the bands to apply intensity stereo to on layers I and II. =item emphasis =item emph =item emphasize =item emphasise Returns the value of the emphasis bits, =item broken =item crc_ok This returns true if the CRC computation failed for a protected layer I or III frame. It will always return false on unprotected frames, because we can't know if they're bad or not. C is the opposite. =item has_crc Whether not the frame has a checksum. =item mono =item dual_channel Dual channel is not stereo, but two separate mono channels. =item stereo Stereo means 'normal' stereo... That is two channels. =item any_stereo Some kind of variation on two channel audio that isn't C. =item joint_stereo Joint stereo is a type of stereo. =item normal_joint_stereo Neither of the below joint stereo variations. Intensity and MS stereo apply to layer III. =item intensity_stereo Intensity stereo is a type of joint stereo. Note that this method also dies on layers I or II. This is behavior might change if I change my mind and decide that this is stupid. Since layers I and II joint stereo is always implemented in terms of intensity stereo, however, I reckoned it was more appropriate to make this apply only to the layer III case, to provide stricter error checking. =item intensity_and_ms_stereo Both intensity and ms stereo. =item intensity_stereo_only Intensity stereo without ms stereo. =item ms_stereo =item ms_and_intensity_stereo =item ms_stereo_only See above. =item band_4 =item band_8 =item band_12 =item band_16 These methods all query whether the mode extension field specifies band N to 31. They only apply to layers I and II. =item mpeg1 =item mpeg2 =item mpeg25 The whether the version is equal to N. =item layer1 =item layer2 =item layer3 Same for layers. =back =head1 PACKAGE VARIBLES =over 4 =item $MPEG::Audio::Frame::lax This tells L to let non-critical data invalidations pass. The L value, for example, has an invalid value, C<2>, which is meaningless to L. This does not apply to any of the header values used to actually mine the data, and any invalid values in these fields (like the MPEG version, or the bitrate) cause the current header start to be skipped under the assumption that it is simply not MPEG data. This really only applies to the emphasis header field. =item $MPEG::Audio::Frame::free_bitrate This is the bitrate to assume when frames are marked with the 'free' bit rate. This is undefined by default. =item $MPEG::Audio::Frame::mpeg25 When true, as is the default, MPEG-2.5 is supported. If that causes trouble, it can be disabled. =back =head1 TIED HANDLE USAGE You can also read frame objects via the readline operator by tying a filehandle to this package in the following manner: tie *MP3, 'MPEG::Audio::Frame',*FH; while(){ print "frame at ", $_->offset(), "\n"; } Way cool. =head1 HISTORY =head2 0.09 January 13th 2005 Yasuhiro Sasama submitted a patch to properly handle MPEG2.5 files. Dropped support for MPEG-2 multichannel in favour of MPEG-2 LSF. No one seems to support MC. If you know how to tell them apart, or have the money to pay the ISO guys for the MC/LSF docs, and would like support for them in this module, contact me. Otherwise it doesn't look like it's going to happen. Completely rehauled everything internal. Now nearly 50% faster (thanks L and L). B<< NOTE! This introduces an incompatible change! $frame->header will no longer return a hash of string values, but an array of integers >>. Test suite redone and expanded. Some "official" test files included. Repackaged with L, and various goodnesses. Trashed the useless README Perl 5.005_04 compatibility. Wrapped pod. =head2 0.08 October 21st 2003 Johan Vromans cought a glitch in asbin, which surfaced in 0.07 - now fixed. =head2 0.07 October 19th 2003 Made broken compute the CRC on demand instead of always. Cryptographically signed distribution. =head2 0.06 October 17th 2003 Fixed some doc errors, thanks to Nikolaus Schusser and Suleyman Gulsuner. Fixed CRC computation on little endian machines. =head2 0.05 August 3rd 2003 Added overloading of object to asbin by default. Added real CRC checking for layers III and I (layer II is a longer story). =head2 0.04 August 2nd 2003 Fixed the calculation of frame lengths when a CRC is present, thanks to Johan Vromans. =head2 0.03 April 19th 2003 Reimplemented C method, which came out of sync whilst working on various copies, thanks to Jeff Anderson. =head2 0.02 April 18th 2003 Some minor documentation and distribution fixes were made. =head1 AUTHOR Yuval Kojman =head1 COPYRIGHT Copyright (c) 2003 Yuval Kojman. All rights reserved This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut