/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;