/usr/local/CPAN/SWF-Builder/SWF/Builder/Character/Sound/MP3.pm
package SWF::Builder::Character::Sound::MP3;
use strict;
use Carp;
our $VERSION="0.01";
our @ISA = ('SWF::Builder::Character::Sound::Def');
my @bitrates =
(
[32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000 ],
[ 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000 ],
);
my @samplerates = ( 11025, 22050, 44100 );
my @size = ( 72, 72, 144 );
my @samplecount = ( 576, 576, 1152 );
my %channel =
(
'00' => 1,
'01' => 1,
'10' => 0, # ?
'11' => 0,
);
sub new {
my ($class, $filename, %param) = @_;
my $self = bless {
_filename => $filename,
}, $class;
open my $f, '<', $filename or croak "Can't find '$filename'";
binmode $f;
eval { $self->_file_check($f) };
if ($@) {
croak substr($@, 1) if $@ =~/^\*/;
die;
}
close $f;
$self->Latency($param{Latency}) if defined $param{Latency};
$self;
}
sub Latency {
my ($self, $msec) = @_;
$self->{_initial_seek} = $msec;
}
sub _get_frame {
my ($self, $f) = @_;
local $/ = "\xff";
while(<$f>) {
read $f, my $header, 1;
next unless (($header & "\xe0") eq "\xe0");
read $f, $header, 2, 1;
my (undef, $version, $layer, undef, $bitrate, $samplerate, $padding, undef, $channel) = unpack('A3A2A2A1 A4A2A1A1 A2A6', unpack('B*', $header));
die "*Invalid MP3 frame header" if ( $version eq '01' or
$layer ne '01' or
$bitrate eq '1111' or
$samplerate eq '11');
die "*Free bitrate is not supported" if $bitrate eq '0000';
die "*This sampling rate is not supported" unless $samplerate eq '00';
$version = oct("0b$version");
$version-- if $version >= 2;
$bitrate = $bitrates[$version != 2][oct("0b$bitrate")-1];
my $length = int($size[$version] * $bitrate / $samplerates[$version]) + $padding;
read $f, my $content, $length - 4;
return ("\xff$header$content", $version, $channel);
}
return; # end of file
}
*_file_check = \&_get_frame;
sub _set_initial_seek {
my ($self, $f) = @_;
my $msec = $self->{_initial_seek};
my $data = '';
my $seek = 0;
my $count = 0;
my ($content, $version, $channel);
while( ($content, $version, $channel) = $self->_get_frame($f)) {
my $fsize = $samplecount[$version];
my $fmsec = $fsize * 1000 / $samplerates[$version];
$data .= $content;
$count += $fsize;
if ($msec <= 0) {
$seek += int(-$msec * $samplerates[$version] / 1000);
last;
}
$msec -= $fmsec;
$seek += $fsize;
}
return ($data, $version, $channel, $count, $seek);
}
sub _pack {
my ($self, $stream) = @_;
my $filename = $self->{_filename};
my $tag = SWF::Element::Tag::DefineSound->new;
open my $f, '<', $filename or croak "Can't find '$filename'";
binmode $f;
eval {
my ($data, $version, $channel, $count, $seek) = $self->_set_initial_seek($f);
$tag->SoundFormat(2);
$tag->SoundRate($version+1);
$tag->SoundSize(1);
$tag->SoundType($channel{$channel});
my $sd = $tag->SoundData;
$sd->add(pack('v', $seek).$data);
while(my ($content, $version) = $self->_get_frame($f)) {
$sd->add($content);
$count += $samplecount[$version];
}
$tag->SoundSampleCount($count);
$tag->SoundID($self->{ID});
$tag->pack($stream);
};
if ($@) {
croak substr($@, 1) if $@ =~ /^\*/;
die;
}
}
sub _init_streaming {
my ($self, $framerate) = @_;
my $filename = $self->{_filename};
my $htag = SWF::Element::Tag::SoundStreamHead2->new;
open my $f, '<', $filename or croak "Can't find '$filename'";
binmode $f;
my ($data, $version, $channel, $count, $seek) = $self->_set_initial_seek($f);
$htag->StreamSoundCompression(2);
$htag->StreamSoundRate($version+1);
$htag->StreamSoundSize(1);
$htag->StreamSoundType($channel{$channel});
$htag->PlaybackSoundRate($version+1);
$htag->PlaybackSoundSize(1);
$htag->PlaybackSoundType($channel{$channel});
$htag->LatencySeek($seek);
my $sc = $samplerates[$version] / $framerate;
$htag->StreamSoundSampleCount($sc);
bless {
_sound => $self,
_file => $f,
_last_sc => $count - $seek,
_swfframe_sc => $sc,
_mp3frame_sc => $count,
_last_data => $data,
_framerate => $framerate,
_header_tag => $htag,
}, 'SWF::Builder::Character::Sound::MP3::Streaming';
}
####
package SWF::Builder::Character::Sound::MP3::Streaming;
sub header_tag {
shift->{_header_tag};
}
sub next_block_tag {
my $self = shift;
my $data = $self->{_last_data};
return unless defined $data; # return undef if EOF
my $f = $self->{_file};
my $version = $self->{_last_version};
my $last_sc = $self->{_last_sc};
my $swfframe_sc = $self->{_swfframe_sc};
my $mp3frame_sc = $self->{_mp3frame_sc};
my $blockdata;
my $sc = 0;
while ($last_sc <= $swfframe_sc) {
$blockdata .= $data;
($data, $version) = $self->{_sound}->_get_frame($f);
last unless defined $data;
$sc = $samplecount[$version];
$last_sc += $sc;
$mp3frame_sc += $sc;
}
return '' unless $blockdata; # return false (but defined) if SWF frames are short.
$blockdata = pack('vv', $mp3frame_sc - $sc, $swfframe_sc - $last_sc + $sc) . $blockdata;
$self->{_last_sc} = $last_sc;
$self->{_swfframe_sc} = $swfframe_sc + $samplerates[$version] / $self->{_framerate};
$self->{_mp3frame_sc} = $sc;
$self->{_last_data} = $data;
return SWF::Element::Tag::SoundStreamBlock->new(StreamSoundData => $blockdata);
}
sub __dump {
my $self = shift;
for my $k (qw/ _file _last_samplecount _frame_samplecount _framerate _header_tag/ ) {
print "$k => ", $self->{$k},"\n";
}
}
1;