##------------------------------------------------------------------------
##  Package: MPEG::Info::Audio
##   Author: Benjamin R. Ginter
##   Notice: Copyright (c) 2001 Benjamin R. Ginter
##  Purpose: Parse audio streams
## Comments: None
##      CVS: $Id: Audio.pm,v 1.4 2002/02/13 04:42:40 synaptic Exp $
##------------------------------------------------------------------------

package MPEG::Info::Audio;
use 5.006;
use strict;
use warnings;
use MPEG::Info qw( $AUDIO_BITRATE );
use MPEG::Info::Constants;
use Data::Dumper;

require Exporter;

our @ISA = qw( MPEG::Info Exporter);

our $AUDIO_BITRATE;
our $AUDIO_SAMPLING_RATE;

##------------------------------------------------------------------------
## Preloaded methods go here.
##------------------------------------------------------------------------
1;

##------------------------------------------------------------------------
## new()
##
## override superclass constructor
##------------------------------------------------------------------------
sub new {
    my $proto = shift;
    my $class = ref( $proto ) || $proto;
    my $self  = { version => 0, };
    bless( $self, $class );

    return $self;
}

##------------------------------------------------------------------------
## parse()
##
## Parse an audio packet.  Since this is in the context of a video stream,
## we only care about the MPEG version, layer, bitrate, sampling rate,
## channels, and emphasis.  
##
##------------------------------------------------------------------------
sub parse {
    my($self,$offset) = @_;
  
    $offset = 0 if !defined $offset;
    $self->{offset} = $offset;

    $self->{_bytes} = $self->get_header();
    # printf "0x%08x\n", unpack( "N", pack( "C*", @{$self->{_bytes}} ) );

    #print "parse audio: $offset\n";
    $self->is_audio() or return 0;

    $self->get_version && $self->get_layer         or return 0;
    $self->get_bitrate && $self->get_sampling_freq or return 0;
    $self->get_protect;
    $self->get_audio_mode();
    $self->get_copyright();
    $self->get_padding();
    $self->get_emphasis();
    # $self->get_frame_length();

    #if we made it this far, assume a bona fide MPEG
    $self->type('MPEG');

    if ( 0 ) { 
	  print '-' x 74, "\n", 'Parse Audio', "\n", '-' x 74, "\n";

	  print "MPEG-$self->{version} Layer $self->{layer}\n";
	  print "         MODE: $self->{mode}\n";
	  print "      BITRATE: $self->{bitrate}\n";
	  print "     BYTERATE: $self->{byterate}\n";
	  print "SAMPLING RATE: $self->{sampling}\n";
	  print "      PADDING: $self->{padding}\n";
	  print "     EMPHASIS: $self->{emphasis}\n";
	  print "    COPYRIGHT: $self->{copyright}\n";
	  print "      PROTECT: $self->{protect}\n";
	  # print " FRAME_LENGTH: $self->{frame_length}\n";
	  print "Audio : Mpeg $self->{version} layer $self->{layer}\n";
	  print "$self->{bitrate} kbps  $self->{sampling} Hz\n";
	  print "$self->{mode}, $self->{emphasis}\n";
    }
	
    ## Save off some information to a format Video::Info expects.
    ## The $self-> hash remains available for the user if needed.

    $self->arate    ( $self->{byterate} * 8 );
    $self->copyright( $self->{copyright} );

    return 1;
}

##------------------------------------------------------------------------
## is_audio()
##
## Verify we have the proper MPEG audio packet start codes
##------------------------------------------------------------------------
sub is_audio {
    my $self  = shift;
    my $bytes = $self->{_bytes};

    ## ensure that the first two bytes are FFFx
    return 0 if $bytes->[0] != 0xFF;

    if ( ( $bytes->[1] & 0xF0 ) != 0xF0 ) {
	## Doesn't start with 12 bits set
	
	if ( ( $bytes->[1] & 0xE0 ) != 0xE0 ) {
	    ## Doesn't start with 11 bits set either -- give up
	    return 0;
	}
#	else {
	    ## starts with 11 bits set
	    $self->{version} = 2.5;
#	}
    }
    return 1;

}

##------------------------------------------------------------------------
## get_version()
##
## Determine the MPEG Version
##------------------------------------------------------------------------
sub get_version {
    my $self = shift;

    ## find mpeg version 1.0 or 2.0
    
    if ( $self->{_bytes}->[1] & 0x08 ) {
	if ( $self->{version} != 2.5 ) {
	    $self->{version} = 1;
		$self->acodec(0x50);
	}
	else {
	    ## invalid 01 encountered
	    return 0;
	}
    }
    else {
	
	if ( $self->{version} != 2.5 ) {
	    $self->{version} = 2;
		$self->acodec(0x50);
	}
	else {
	  ## err, isn't this set?
	  $self->{version} = 3; 
	  $self->acodec(0x55);
	}
  }
    return 1;
}

##------------------------------------------------------------------------
## get_layer()
##
## Determine the MPEG layer
##------------------------------------------------------------------------
sub get_layer {
    my $self = shift;

    ## Find layer
    my $layer = ( $self->{_bytes}->[1] & 0x06 ) >> 1;
    if ( $layer == 0 ) {
	$self->{layer} = -1;
	return 0;	
    }
    elsif ( $layer == 1 ) {
	$self->{layer} = 3;
    }
    elsif ( $layer == 2 ) {
	$self->{layer} = 2;
    }
    elsif ( $layer == 3 ) {
	$self->{layer} = 1;
    }
    else {
	$self->{layer} = $layer;
	print "Unknown audio layer index: $layer\n";
	return 0;
    }
    # undef $layer;

    return 1;
}

##------------------------------------------------------------------------
## get_audio_mode()
##
## Determine the audio mode (channels, etc.)
##------------------------------------------------------------------------
sub get_audio_mode {
    my $self = shift;

    ## Get the raw audio mode
    $self->{mode_raw} = $self->{_bytes}->[3] >> 6;
    $self->{mode_raw} == 1 ? $self->{modext} = ( $self->{_bytes}->[3] >> 4 ) & 0x03 : $self->{modext} = 1;

    $self->achans( 2 );

    ## Now decode it
    if ( $self->{mode_raw} == 0 ) {
	  $self->{mode} = 'Stereo';
	  $self->achans(2);
    }
    elsif ( $self->{mode_raw} == 1 ) {
	if ( $self->{layer} == 1 || $self->{layer} == 2 ) {
	    if ( $self->{modext} == 0 ) {
		$self->{mode} = 'Intensity stereo on bands 4-31/32';
	    }
	    elsif ( $self->{modext} == 1 ) {
		$self->{mode} = 'Intensity stereo on bands 8-31/32';
	    }
	    elsif ( $self->{modext} == 2 ) {
		$self->{mode} = 'Intensity stereo on bands 12-31/32';
	    }
	    elsif ( $self->{modext} == 3 ) {
		$self->{mode} = 'Intensity stereo on bands 16-31/32';
	    }
	    else {
		$self->{mode} = "Unknown audio mode extension.  Mode=$self->{mode_raw}  Ext: $self->{modext}";
		return 0;
	    }
	}
	else {
	    ## mp3
	    if ( $self->{modext} == 0 ) { 
		$self->{mode} = 'Intensity stereo off, M/S stereo off';
	    } 
	    elsif ( $self->{modext} == 1 ) { 
		$self->{mode} = 'Intensity stereo on, M/S stereo off';
	    } 
	    elsif ( $self->{modext} == 2 ) { 
		$self->{mode} = 'Intensity stereo off, M/S stereo on';
	    } 
	    elsif ( $self->{modext} == 3 ) {
		$self->{mode} = 'Intensity stereo on, M/S stereo on';
	    } 
	    else {
		$self->{mode} = "Unknown audio mode extension.  Mode=$self->{mode_raw}  Ext: $self->{modext}";
		return 0;
	    }
	    
        }
    }
    elsif ( $self->{mode_raw} == 2 ) {
	  $self->{mode} = 'Dual Channel';
	  $self->achans(2); #not stereo, but still 2, right?  brg: yes
    }
    elsif ( $self->{mode_raw} == 3 ) {
	  $self->{mode} = 'Mono';
	  $self->achans(1);
    }
    else {
	$self->{mode} = "Unknown audio mode.  Mode=$self->{mode_raw}  Ext: $self->{modext}";
	$self->achans(0);
	return 0;
    }

    return 1;
}

##------------------------------------------------------------------------
## get_copyright()
##------------------------------------------------------------------------
sub get_copyright {
    my $self = shift;

    ## Set original/copyright bit
    $self->{_bytes}->[3] & 0x04 ? $self->{copyright} = 1 : $self->{copyright} = 0;
}

##------------------------------------------------------------------------
## get_protect()
##
## Extract the protection bit
##------------------------------------------------------------------------
sub get_protect {
    my $self = shift;

    ## Get protection bit
    $self->{_bytes}->[1] & 0x01 ? $self->{protect} = 0 : $self->{protect} = 1;

}

##------------------------------------------------------------------------
## get_bitrate()
##------------------------------------------------------------------------
sub get_bitrate {
    my $self = shift;

    ## Bitrate index and sampling index to pass through the array
    my $bitrate_index  = $self->{_bytes}->[2] >> 4;
    return 0 if $bitrate_index == 15;

    $self->{bitrate}  = $AUDIO_BITRATE->{ $self->{version} }->{ $self->{layer} }->[ $bitrate_index ];
    $self->{byterate} = ( $self->{bitrate} * 1000 ) / 8.0;

    return 1;
}

##------------------------------------------------------------------------
## get_sampling_freq()
##------------------------------------------------------------------------
sub get_sampling_freq {
    my $self = shift;

    my $sampling_index = ( $self->{_bytes}->[2] & 0x0F ) >> 2;
    # print "sampling_index: $sampling_index\n";

    return 0 if $sampling_index == 3;

    $self->{sampling} = $AUDIO_SAMPLING_RATE->{ $self->{version} }->[ $sampling_index ];
    return 1;
}

##------------------------------------------------------------------------
## get_padding()
##------------------------------------------------------------------------
sub get_padding {
    my $self = shift;

    ## Get padding bit
    $self->{_bytes}->[2] & 0x02 ? $self->{padding} = 1 : $self->{padding} = 0;
}

##------------------------------------------------------------------------
## get_emphasis()
##------------------------------------------------------------------------
sub get_emphasis {
    my $self = shift;

    ## Get emphasis
    my $emphasis_index = $self->{_bytes}->[3] & 0x03;

    if ( $emphasis_index == 0 ) {
	$self->{emphasis} = 'No Emphasis';
    }
    elsif ( $emphasis_index == 1 ) {
	$self->{emphasis} = '50/15us';
    }
    elsif ( $emphasis_index == 2 ) {
	$self->{emphasis} = 'Unknown';
    }
    elsif ( $emphasis_index == 3 ) {
	$self->{emphasis} = 'CCITT J 17';
    }
    else {
	$self->{emphasis} = 'Undefined';
    }

}

##------------------------------------------------------------------------
## get_frame_length()
##------------------------------------------------------------------------
sub get_frame_length {
    my $self = shift;

    ## Get frame-length
    if ( $self->{version} == 1 ) {
	if ( $self->{layer} == 1 ) {
	    $self->{frame_length} = int( ( 48000 * $self->{bitrate} ) / $self->{sampling} ) + 4 * $self->{padding};
	}
	else {
	    $self->{frame_length} = int( ( 72000 * $self->{bitrate} ) / $self->{sampling} ) + $self->{padding};
	}
    }
    else {
	print "Audio layer invalid : should be 1 or 2\n";
	return 0;
    }

    if ( $self->{protect} ) {
	$self->{frame_length} += 2;
    }
}

##eof
