summaryrefslogtreecommitdiff
path: root/scripts/vdrsync.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/vdrsync.pl')
-rwxr-xr-xscripts/vdrsync.pl2806
1 files changed, 2806 insertions, 0 deletions
diff --git a/scripts/vdrsync.pl b/scripts/vdrsync.pl
new file mode 100755
index 0000000..bae489e
--- /dev/null
+++ b/scripts/vdrsync.pl
@@ -0,0 +1,2806 @@
+#!/usr/bin/perl
+
+#
+# vdrsync (c) 2003 by Dr. Peter Sebbel, a perl script to demux VDR recordings and
+# correcting for missing/additional Audio frames to ensure sync of audio and
+# video streams. Contact: peter@vdr-portal.de
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+
+use strict;
+use warnings;
+
+
+
+my $debug = 0;
+my $path_param = "./";
+my @parameter_list = @ARGV;
+my $tcmplex = "";
+my $tcmplex_panteltje= "";
+my $panteltje = 0; #uses tcmplex-panteltje instead of tcmplex
+my $mplex = 0;
+my $mpeg2 = 0;
+my $transcode = 0;
+my $info = 0;
+my $remuxfilename = "";
+my @ignore_streams;
+my $dump_packets = 0;
+my $dump_payload = 0;
+my $stop_flag = 0;
+my $audio_only = 0;
+my $script_output = 0;
+my $divx = 0;
+my $divxac3 = 0;
+my $divx_param = " -V -y divx4 -w 1000 "; #enter additional parameters for divx here
+my $divx_ac3_param = " -V -y divx4 -b 256 -w 1000 "; #enter additional parameters for divx with ac3 sound here
+my $master_dvd = 0;
+my $master_dvd_param = "-c 0,10:00,20:00,30:00,40:00,50:00,01:00:00,01:10:00,01:20:00,01:30:00,01:40:00,01:50:00,02:00:00,02:10:00,02:20:00,02:30:00,02:40:00,02:50:00,03:00:00";
+my $show_drift = 0;
+my $postexec = 0;
+my $dump_buffer = 0;
+my $mkiso = 0;
+
+
+$|=1; # prevent line buffering
+if (-e "/usr/bin/tcmplex") {
+ $tcmplex = "/usr/bin/tcmplex";
+}
+elsif (-e "/usr/local/bin/tcmplex") {
+ $tcmplex = "/usr/local/bin/tcmplex";
+}
+if (-e "/vdr/bin/tcmplex-panteltje"){
+ $tcmplex_panteltje = "/vdr/bin/tcmplex-panteltje";
+}
+elsif (-e "/usr/local/bin/tcmplex-panteltje") {
+ $tcmplex_panteltje = "/usr/local/bin/tcmplex-panteltje";
+}
+elsif (-e "/usr/bin/tcmplex-panteltje") {
+ $tcmplex_panteltje = "/usr/bin/tcmplex-panteltje";
+}
+
+if (-e "/usr/bin/transcode") {
+ $transcode = "/usr/bin/transcode";
+}
+elsif (-e "/usr/local/bin/transcode") {
+ $transcode = "/usr/local/bin/transcode";
+}
+
+
+sub parse_parameters {
+ my $parameter = shift;
+ if ($parameter eq "-d") {
+ $debug = 1;
+ }
+ elsif ($parameter eq "-o") {
+ $path_param = shift @parameter_list;
+ if (!($path_param =~ /\/$/)) {
+ $path_param .= "/";
+ }
+ }
+ elsif ($parameter eq "-m") {
+ $mplex = 1;
+ if ($parameter_list[0] && ($parameter_list[0] eq "panteltje")){
+ $panteltje = (shift @parameter_list);
+ }
+ }
+ elsif ($parameter eq "-mpeg2") {
+ $mpeg2 = 1;
+ $master_dvd = 0;
+ $mplex = 1;
+ $mkiso = 0;
+ }
+ elsif ($parameter eq "-i") {
+ $info = 1;
+ }
+ elsif ($parameter eq "-ignore") {
+ @ignore_streams = split /,/,(shift @parameter_list);
+ }
+ elsif ($parameter eq "-dump-packets"){
+ $dump_packets = (shift @parameter_list);
+ }
+ elsif ($parameter eq "-show-time-drift"){
+ $show_drift = 1;
+ }
+ elsif ($parameter eq "-dump-buffer-on-error"){
+ $dump_buffer = 1;
+ }
+ elsif ($parameter eq "-dump-payload"){
+ $dump_payload = (shift @parameter_list);
+ }
+ elsif ($parameter eq "-audio-only") {
+ $audio_only = 1;
+ }
+ elsif ($parameter eq "-script-output") {
+ $script_output = 1;
+ }
+ elsif ($parameter eq "-divx") {
+ $divx = 1;
+ if ($parameter_list[0] eq "ac3"){
+ $divxac3 = (shift @parameter_list);
+ }
+ }
+ elsif ($parameter eq "-master-dvd"){
+ $master_dvd = 1;
+ $mplex = 1;
+ if ( ! $parameter_list[0] =~/^-/ ){
+ $master_dvd_param = (shift @parameter_list);
+ }
+ }
+ elsif ($parameter eq "-mkiso"){
+ $mkiso = 1;
+ $mplex = 1;
+ $master_dvd = 1;
+ }
+ elsif ($parameter eq "-postexec"){
+ $postexec = (shift @parameter_list);
+ }
+ else {
+ print "Unknown parameter $parameter\n";
+ exit;
+ }
+}
+
+my @files;
+if (scalar(@ARGV) == 0)
+ {
+ print "\nVDRsync Version 0.1.2.2\n";
+ print "\nUsage: vdrsync.pl /path/to/vdr/recording/\n";
+ print "or : vdrsync.pl vdr-file1 .... vdr-fileN\n\n";
+ print "Optional -d print strange debug messages\n";
+ print "Optional -o output_dir sets the directory where the result files\n";
+ print " are written to\n";
+ print "Optional -m tries to multiplex the demuxed streams into a dvd compatible\n";
+ print " stream (uses tcmplex)\n";
+ print "Optional -m panteltje uses tcmplex-panteltje instead of tcmplex\n";
+ print "Optional -i just tries to analyse the .vdr file and exits. -m is ignored\n";
+ print "Optional -ingore stream1[,stream2]... tells the script to ignore stream1..n \n";
+ print " Useful for deliberately omitting streams (like AC3) or if you have \n";
+ print " trouble with one stream\n";
+ print "Optional -divx tries to transcode the demuxed streams into a divx movie\n";
+ print " For DIVX with AC3 (if present in the recording) type -divx ac3\n";
+ print " (uses transcode, adjust transcode settings at the beginning of\n";
+ print " vdrsync.pl)\n";
+ print "Optional -dump-packets NNNN tells the script to dump the first NNNN\n";
+ print " full PES-packets (in file STREAM_ID.pes_dump) Useful for debugging \n";
+ print " (if you want to extract a stream that does not work and mail it to me)\n";
+ print "Optional -dump-payload NNNN tells the script to dump the raw payload of the \n";
+ print " first NNNN PES-packets (in file STREAM_ID.dump) Useful for debugging\n";
+ print " (if you want to extract a stream-payload that does not work and \n";
+ print " test it with other tools)\n";
+ print "Optional -postexec executes the command specified after finishing\n";
+ #print " some Dummy variables like VSlength are replaced by actual values \n";
+ print "Optional -master-dvd creates a DVD structure in a subdirectory of outputpath\n";
+ print "Optional -audio-only discards all video streams and just writes audio\n";
+ print "Optional -script-output writes a special output format about the movie at\n";
+ print " the end of the vdrsync run. Useful for scripting, contains corrected \n";
+ print " info about the streams\n";
+ print "Optional -show-time-drift prints information about the difference between\n";
+ print " timestamps and actual video / audio data found in the recording\n";
+ print "Optional -dump-debug-buffer in case of a problem with one of the streams 3 MB\n";
+ print " of the recording are dumped to the file debug.buffer\n";
+ print "Optional -mkiso creates an ISO Image suitable for burning a DVD\n";
+ print "Optional -mpeg2 creates a generic MPEG2 File (tcmplex -m 2)\n";
+ exit;
+ }
+
+
+while (my $param = shift(@parameter_list)) { # check whether all files hat we got as parameters exist
+ print "Got parameter $param\n";
+ if ($param =~ /^-/) {parse_parameters($param); next}
+ if (-d $param) {
+ print "got a directory on the command line\n";
+ my @dir_files = @{ get_file_list($param) };
+ foreach (@dir_files) {
+ push @files, "$param/$_";
+ }
+ next;
+ }
+ push @files, $param;
+}
+
+print "Output files will be stored in $path_param\n" if $debug;
+
+if (!(-e $path_param)) {print "Output Directory $path_param does not exist\n"; exit 1}
+
+
+
+print "Found " . (scalar(@ignore_streams)) ." to ignore\n" if $debug;
+
+my %ignore_hash;
+foreach (@ignore_streams) {
+ $ignore_hash{$_} = 1;
+}
+
+
+my $PES_Obj = MPEGSTREAM->new
+ (
+ streamcode =>"PES_stream",
+ debug =>$debug,
+ outputpath =>$path_param,
+ files =>\@files,
+ ignore_streams => \%ignore_hash,
+ dump_packets => $dump_packets,
+ dump_payload => $dump_payload,
+ audio_only => $audio_only,
+ show_drift => $show_drift,
+ script_output => $script_output,
+ dump_buffer => $dump_buffer,
+ );
+
+if (!($PES_Obj)) {
+ print "could not create the PES Stream Obj\n";
+ exit 1;
+}
+if ($info) {
+ $PES_Obj->print_stats();
+ exit;
+}
+
+
+$PES_Obj->process_PES_packets();
+
+
+if ((!$mplex) && (!$divx ) && (! $postexec) && (! $master_dvd)) {exit}
+
+my @filestomux = @{$PES_Obj->output_files()};
+
+print "Got " . scalar(@filestomux) ." files back\n" if $debug;
+
+my @basename_list;
+foreach (@filestomux) {
+ my $base_name = (split /\//,$_)[-1];
+ $base_name =~ s/\.//;
+ push @basename_list, $base_name;
+}
+
+if ($divx == 1) {
+ if (scalar(@filestomux) < 2) {print "Need at least two streams for multiplexing\n";exit}
+ if (($filestomux[1] =~ /.ac3$/) && ($divxac3)) {
+ #transcode -i e0.mpv -A -N 0x2000 -p bd.ac3 -y divx4 -o test.avi
+ my $command = "nice -19 $transcode -i $filestomux[0] -A -N 0x2000 -p $filestomux[1] ";
+ $remuxfilename .= $path_param . "/$basename_list[0]_$basename_list[1]_";
+ $command .= " $divx_ac3_param -o $remuxfilename" . "divx.avi";
+ print "executeing $command\n";
+ system $command;
+ exit;
+ }
+ else {
+ #transcode -i e0.mpv -p c0.mpa -y divx4 -o test.avi
+ my $command = "nice -19 $transcode -i $filestomux[0] -p $filestomux[1] ";
+ $remuxfilename .= $path_param . "/$basename_list[0]_$basename_list[1]_";
+ $command .= " $divx_param -o $remuxfilename" . "divx.avi";
+ print "executeing $command\n";
+ system $command;
+ exit;
+ }
+}
+
+my $mplex_command;
+
+if ($mplex == 1) {
+ if (scalar(@filestomux) < 2) {print "Need at least two streams for multiplexing\n";exit}
+ if (! $panteltje){
+ $mplex_command = "nice -19 $tcmplex -i $filestomux[0] -p $filestomux[1]";
+ }
+ else {
+ $mplex_command = "nice -19 $tcmplex_panteltje -i $filestomux[0] -0 $filestomux[1]";
+ }
+ $remuxfilename .= $path_param . "/$basename_list[0]_$basename_list[1]_";
+
+ if (scalar(@filestomux) > 2) {
+ if (! $panteltje){
+ $mplex_command .= " -s $filestomux[2]";
+ $remuxfilename .= "$basename_list[2]_";
+ }
+ else {
+ print "Mplexing using tcmplex-panteltje\n";
+ my $counter = 2;
+ while (scalar(@filestomux) > $counter) {
+ print "adding to $counter $filestomux[$counter]\n";
+ $mplex_command .= " -" . ($counter-1) . " $filestomux[$counter]";
+ $remuxfilename .= "$basename_list[$counter]_";
+ $counter++;
+ }
+ }
+ }
+ $remuxfilename .= "remux.mpg";
+
+ if ($mpeg2) {
+ $mplex_command .= " -m 2 ";
+ }
+ else {
+ $mplex_command .= " -m d ";
+ }
+
+ print "$remuxfilename as outputname\n";
+
+ if (! $master_dvd) {
+ $mplex_command .= "-o $remuxfilename";
+ print "executeing $mplex_command\n";
+ system $mplex_command;
+ }
+}
+
+if ($master_dvd) {
+ print "we got a the master_dvd parameter\n";
+ create_dvd_image();
+}
+
+
+if ($postexec) {
+ print "Executing: $postexec\n";
+ if ($postexec =~ /VSlength/) {
+ my $length = $PES_Obj->get_movie_length();
+ $postexec =~ s/VSlength/$length/g;
+ }
+ exec $postexec;
+}
+
+if ($mplex) {
+ clean_up();
+}
+
+
+exit;
+
+sub create_dvd_image {
+ my $pid;
+ my $result = system "mkfifo $path_param"."remuxfifo";
+ print "the attempt to create FIFO returnd $result\n";
+ my $asp_ratio = $PES_Obj->get_aspect_ratio();
+
+ if ($pid = fork) {
+ my $command = "dvdauthor -v $asp_ratio $master_dvd_param -o $path_param" . "/DVD $path_param" . "remuxfifo";
+ print "starting $command\n";
+ system $command;
+ $command = "dvdauthor -T -o $path_param" . "/DVD";
+ print "starting $command\n";
+ system $command;
+ sleep 1;
+ }
+ else {
+ print "mplexing file to fifo\n";
+ print "should execute $mplex_command and put it to the fifo\n";
+ $mplex_command .= " -o $path_param" . "remuxfifo";
+ print "now the command is $mplex_command\n";
+ system $mplex_command;
+ print "finished mplexing\n";
+ exit;
+ }
+ if (! $mkiso) {
+ print "***********************************************************\n";
+ print "vdrsync finished\n";
+ print "***********************************************************\n";
+ print "now you can burn the Image to a DVD-R(W) using the command:\n\n";
+ print "mkisofs -dvd-video $path_param" . '/DVD | dvdrecord tsize=$(echo "`mkisofs -dvd-video --print-size ' .$path_param . '/DVD 2>/dev/null`*2048" | bc -l ) dev=1,0,0 -v -dao -' . "\n";
+ print "NOTE: Of course you have to adjust the dev=N,N,N parameter to match your device settings\n\n";
+ print "NOTE: It might be a good idea to check it first with:\n\n";
+ print "mplayer -dvd 1 -dvd-device $path_param" . "/DVD\n\n";
+ }
+ else {
+ my $command = "mkisofs -dvd-video $path_param" . '/DVD > ' . " $path_param/DVDimage.iso\n";
+ system $command;
+ print "***********************************************************\n";
+ print "vdrsync finished\n";
+ print "***********************************************************\n";
+ print "now you can burn the Image to a DVD-R(W) using the command:\n\n";
+ print "dvdrecord dev=1,0,0 -v -dao $path_param" . "DVDimage.iso\n";
+ print "NOTE: Of course you have to adjust the dev=N,N,N parameter to match your device settings\n\n";
+ }
+}
+
+sub clean_up {
+ print "Deleting temp files\n";
+ for my $delfile (@filestomux) {
+ if (-e $delfile){
+ print "$delfile\n";
+ unlink($delfile);
+ }
+ }
+ if (-e "$path_param"."remuxfifo") {
+ unlink ("$path_param"."remuxfifo");
+ }
+ if ($mkiso) {
+ my $command = "rm -r $path_param" . "DVD";
+ print "Deleting DVD Files with: $command\n";
+ system $command;
+
+ }
+}
+
+sub get_file_list {
+ my $indir = shift;
+ my $DIR;
+ opendir $DIR, $indir || die "Can not open $indir $!\n";
+ print "trying to open $indir\n";
+ if (! $DIR){
+ die "did not get a handle back\n";
+ }
+ my @allfiles = grep { ! /^\./ } readdir $DIR;
+ my @vdrfiles = sort (grep { /\d{3}.vdr$/ } @allfiles);
+ return \@vdrfiles;
+ #$summary_file = $indir . "summary.vdr" if (-e "$indir/summary.vdr");
+ #$marks_file = $indir . "marks.vdr" if (-e "$indir/marks.vdr");
+ #$index_file = $indir . "index.vdr" if (-e "$indir/index.vdr");
+}
+
+
+
+BEGIN
+{
+package MPEGSTREAM;
+require Exporter;
+# A place available to all instances for storing the cuts in the file...
+our @cutlist;
+our $kill_me = "";
+our $total_size = 0;
+our $bytes_read = 0;
+our %bitrates = (
+ MPEG1_Layer_1 => {
+ "0001" => "32",
+ "0010" => "64",
+ "0011" => "96",
+ "0100" => "128",
+ "0101" => "160",
+ "0110" => "192",
+ "0111" => "224",
+ "1000" => "256",
+ "1001" => "288",
+ "1010" => "320",
+ "1011" => "352",
+ "1100" => "384",
+ "1101" => "416",
+ "1110" => "448",
+ },
+ MPEG1_Layer_2 => {
+ "0001" => "32",
+ "0010" => "48",
+ "0011" => "56",
+ "0100" => "64",
+ "0101" => "80",
+ "0110" => "96",
+ "0111" => "112",
+ "1000" => "128",
+ "1001" => "160",
+ "1010" => "192",
+ "1011" => "224",
+ "1100" => "256",
+ "1101" => "320",
+ "1110" => "384",
+ },
+ MPEG1_Layer_3 => {
+ "0001" => "32",
+ "0010" => "40",
+ "0011" => "48",
+ "0100" => "56",
+ "0101" => "64",
+ "0110" => "80",
+ "0111" => "96",
+ "1000" => "112",
+ "1001" => "128",
+ "1010" => "160",
+ "1011" => "192",
+ "1100" => "224",
+ "1101" => "256",
+ "1110" => "320",
+ },
+ MPEG2_Layer_1 => {
+ "0001" => "32",
+ "0010" => "64",
+ "0011" => "96",
+ "0100" => "128",
+ "0101" => "160",
+ "0110" => "192",
+ "0111" => "224",
+ "1000" => "256",
+ "1001" => "288",
+ "1010" => "320",
+ "1011" => "352",
+ "1100" => "384",
+ "1101" => "416",
+ "1110" => "448",
+ },
+ MPEG2_Layer_2 => {
+ "0001" => "32",
+ "0010" => "48",
+ "0011" => "56",
+ "0100" => "64",
+ "0101" => "80",
+ "0110" => "96",
+ "0111" => "112",
+ "1000" => "128",
+ "1001" => "160",
+ "1010" => "192",
+ "1011" => "224",
+ "1100" => "256",
+ "1101" => "320",
+ "1110" => "384",
+ },
+ MPEG2_Layer_3 => {
+ "0001" => "8",
+ "0010" => "16",
+ "0011" => "24",
+ "0100" => "32",
+ "0101" => "64",
+ "0110" => "80",
+ "0111" => "56",
+ "1000" => "64",
+ "1001" => "128",
+ "1010" => "160",
+ "1011" => "112",
+ "1100" => "128",
+ "1101" => "256",
+ "1110" => "320",
+ },
+);
+our %freqs =(
+ MPEG1 => {
+ "00" => "44100",
+ "01" => "48000",
+ "10" => "32000",
+ },
+ MPEG2 => {
+ "00" => "22050",
+ "01" => "24000",
+ "10" => "16000",
+ },
+);
+
+our %AC3_frame_info = (
+ "32000" => {
+ "000000" => "192",
+ "000001" => "192",
+ "000010" => "240",
+ "000011" => "240",
+ "000100" => "288",
+ "000101" => "288",
+ "000110" => "336",
+ "000111" => "336",
+ "001000" => "384",
+ "001001" => "384",
+ "001010" => "480",
+ "001011" => "480",
+ "001100" => "576",
+ "001101" => "576",
+ "001110" => "672",
+ "001111" => "672",
+ "010000" => "768",
+ "010001" => "768",
+ "010010" => "960",
+ "010011" => "960",
+ "010100" => "1152",
+ "010101" => "1152",
+ "010110" => "1344",
+ "010111" => "1344",
+ "011000" => "1536",
+ "011001" => "1536",
+ "011010" => "1920",
+ "011011" => "1920",
+ "011100" => "2304",
+ "011101" => "2304",
+ "011110" => "2688",
+ "011111" => "2688",
+ "100000" => "3072",
+ "100001" => "3072",
+ "100010" => "3456",
+ "100011" => "3456",
+ "100100" => "3840",
+ "100101" => "3840",
+ },
+ "44100" => {
+ "000000" => "138",
+ "000001" => "140",
+ "000010" => "194",
+ "000011" => "196",
+ "000100" => "208",
+ "000101" => "210",
+ "000110" => "242",
+ "000111" => "244",
+ "001000" => "278",
+ "001001" => "280",
+ "001010" => "348",
+ "001011" => "350",
+ "001100" => "416",
+ "001101" => "418",
+ "001110" => "486",
+ "001111" => "488",
+ "010000" => "556",
+ "010001" => "558",
+ "010010" => "696",
+ "010011" => "698",
+ "010100" => "834",
+ "010101" => "836",
+ "010110" => "974",
+ "010111" => "976",
+ "011000" => "1114",
+ "011001" => "1116",
+ "011010" => "1392",
+ "011011" => "1394",
+ "011100" => "1670",
+ "011101" => "1672",
+ "011110" => "1950",
+ "011111" => "1952",
+ "100000" => "2228",
+ "100001" => "2230",
+ "100010" => "2506",
+ "100011" => "2508",
+ "100100" => "2786",
+ "100101" => "2788",
+ },
+ "48000" => {
+ "000000" => "128",
+ "000001" => "120",
+ "000010" => "160",
+ "000011" => "160",
+ "000100" => "192",
+ "000101" => "192",
+ "000110" => "224",
+ "000111" => "224",
+ "001000" => "256",
+ "001001" => "256",
+ "001010" => "320",
+ "001011" => "320",
+ "001100" => "384",
+ "001101" => "384",
+ "001110" => "448",
+ "001111" => "448",
+ "010000" => "512",
+ "010001" => "512",
+ "010010" => "640",
+ "010011" => "640",
+ "010100" => "768",
+ "010101" => "768",
+ "010110" => "896",
+ "010111" => "896",
+ "011000" => "1024",
+ "011001" => "1024",
+ "011010" => "1280",
+ "011011" => "1280",
+ "011100" => "1336",
+ "011101" => "1336",
+ "011110" => "1792",
+ "011111" => "1792",
+ "100000" => "2048",
+ "100001" => "2048",
+ "100010" => "2304",
+ "100011" => "2304",
+ "100100" => "2560",
+ "100101" => "2560",
+ },
+ "bitrate" => {
+ "000000" => "32000",
+ "000001" => "32000",
+ "000010" => "40000",
+ "000011" => "40000",
+ "000100" => "48000",
+ "000101" => "48000",
+ "000110" => "56000",
+ "000111" => "56000",
+ "001000" => "64000",
+ "001001" => "64000",
+ "001010" => "80000",
+ "001011" => "80000",
+ "001100" => "96000",
+ "001101" => "96000",
+ "001110" => "112000",
+ "001111" => "112000",
+ "010000" => "128000",
+ "010001" => "128000",
+ "010010" => "160000",
+ "010011" => "160000",
+ "010100" => "192000",
+ "010101" => "192000",
+ "010110" => "224000",
+ "010111" => "224000",
+ "011000" => "256000",
+ "011001" => "256000",
+ "011010" => "320000",
+ "011011" => "320000",
+ "011100" => "384000",
+ "011101" => "384000",
+ "011110" => "448000",
+ "011111" => "448000",
+ "100000" => "512000",
+ "100001" => "512000",
+ "100010" => "576000",
+ "100011" => "576000",
+ "100100" => "640000",
+ "100101" => "640000",
+ },
+);
+
+our $mp2_regex =
+ #(pack ("B16", "1111111111110000")) . "|" .
+ #(pack ("B16", "1111111111110001")) . "|" .
+ #(pack ("B16", "1111111111110010")) . "|" .
+ #(pack ("B16", "1111111111110011")) . "|" . These are Mpeg 2 Audio Headers,
+ #(pack ("B16", "1111111111110100")) . "|" . They should not occur
+ #(pack ("B16", "1111111111110101")) . "|" .
+ #(pack ("B16", "1111111111110110")) . "|" .
+ #(pack ("B16", "1111111111110111")) . "|" .
+
+ #(pack ("B16", "1111111111111000")) . "|" .
+ #(pack ("B16", "1111111111111001")) . "|" .
+ #(pack ("B16", "1111111111111010")) . "|" . These are Layer I and III headers
+ #(pack ("B16", "1111111111111011")) . "|" .
+ #(pack ("B16", "1111111111111110")) . "|" .
+ #(pack ("B16", "1111111111111111")) . "|" .
+
+ (pack ("B16", "1111111111111100")) . "|" .
+ (pack ("B16", "1111111111111101"))
+ ;
+
+$mp2_regex = qr/$mp2_regex/;
+
+our $AC3_regex =
+ (pack("B16", "0000101101110111")); # AC3 Frames always have this 16 Bit Syncword at the beginning
+
+$AC3_regex = qr/$AC3_regex/;
+
+
+sub get_cutlist { return @cutlist; }
+
+our @ISA = qw(Exporter);
+our @EXPORT = qw( add_PES_packet
+ bitrate
+ streamtype
+ streamcode
+ print_stats
+ output_files
+ process_PES_packets
+ );
+
+our $VERSION = 0.1.2.2; # Version number
+
+
+
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+
+ my $self = {
+ streamcode => "PES_stream", #will be overridden
+ streamtype => undef, #
+ bitrate => undef, # only for Audio
+ freq => undef, # only for Audio
+ copyright => undef, # only for Audio
+ frame_bytes => undef, # only for Audio
+ frame_ticks => undef, # one tick is 1 / 90000 of a second
+ frameno => 0, # READ frames counter, not written frames
+ GOPno => 0, # just for video stats
+ packet_start => undef, # a signature that is found a the beginning of a frame, should read frame_start
+ packet_rest => "", # only audio, if a frame does not finish in a PES-Packet, the first part of the frame is stored here
+ check_frames => undef, # a placeholder for the sub that actually manages the frames of a given type (mp2 / ac3 / mpv)
+ analyse_frames => undef, # a placeholder for the sub that actually analyses the frames of a given type (mp2 / ac3 )
+ masterstream => "", # here the stream_id of the videostream is stored (it is not always e0 as I thought first)
+ outputpath => "./", # where to store the result files
+ outfilename => "", # The name of the output file
+ cutcounter => 0, # used as an index to the cuts list (see below), for sync we need the current and the previous cut
+ last_ts => 0, # last timestamp that was read
+ final_desync => [],
+ GOP_buffer => "", # A GOP is buffered here before analysed and written
+ GOP_ts => 0, # Here we store the first GOP ts, for the strrange recordings that have no ts at GOP start
+ frame_buffer => [], # an array that holds the buffered audio frames
+ frame_times => [], # an array that holds the timestamp for each audio frame
+ frame_lengths => [], # an array that holds the length for each audio frame
+ frames_written => 0, # used only for stats
+ timestamps => [], # all timesstamps are stored here, probably a waste of memory, but maybe used for stricter checks
+ written_chunks => [], # an array of scalars for debugging, from where to where were Audio STreams written?
+ final_desync => 0, # how many ticks desync after correction? Used in the next sync attempt
+ show_drift => 0, # if set to 1, info about timedrift will be printed
+ time_drift => 0, # The current mismatch between TS and actual Pakets found
+ audio_only => 0, # if this is set, all video Streams are discarded and audio ist just written out
+ script_output => 0,
+ Unit_analysis_counter => 0,
+ Unit_analysis_hash => {}, # Here the different settings for apsepect_ratio etc are collected for later print
+ first_flush => 1, # This flag indicates that it is the first time that the audio buffer is flushrd, an a start sync is needed
+ @_, # Override previous attributes
+ };
+ if ($self->{streamcode} eq "PES_stream") {
+
+ if (scalar(@{$self->{files}}) == 0) {
+ print "No input files specified, exiting\n"; exit 1 ;
+ }
+
+ foreach (@{$self->{files}}) {# check whether all files that we got as parameters exist
+ if (!(-e $_)) {print "Input file $_ not found\n"; exit 1 }
+ }
+ $self->{packets_before_sync} = 0;
+ print "dump only the first $self->{dump_packets} packets\n" if $self->{dump_packets};
+ foreach (keys (%{$self->{ignore_streams}})) {print "Ignoring stream $_\n";}
+ print "Lots of debug stuff will be printed\n" if $self->{debug};
+ print "Printing Information about the time drift\n" if $self->{show_drift};
+ init_PES_stream($self);
+ foreach(@{$self->{files}}) {
+ $total_size += -s $_;
+ }
+ print "Total Input Size is $total_size\n";# if $self->{debug};
+ }
+ return bless $self, $class;
+}
+
+
+sub init_stream {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+ my $decimal_code = hex($pes_header{stream_id});
+ print "we got the decimal code $decimal_code for $pes_header{stream_id}\n"if $self->{debug};
+ if (($decimal_code > 191) && ($decimal_code < 224)) {
+ print "we got the stream $decimal_code ($pes_header{stream_id}), checking Audio\n"if $self->{debug};
+ analyse_audio_stream($self, $payload, \%pes_header);
+ #$self->{streamtype} = "audio";
+ }
+ elsif (($decimal_code > 223) && ($decimal_code < 240)) {
+ print "we got the stream $decimal_code ($pes_header{stream_id}), checking Video\n"if $self->{debug};
+ analyse_video_stream($self, $payload, \%pes_header);
+ $self->{streamtype} = "video";
+ }
+ elsif ($decimal_code == 189) {
+ analyse_ac3_stream($self, $payload, \%pes_header);
+ $self->{streamtype} = "AC3_audio";
+ }
+ else {
+ print "unknown stream type found, skipping contents of stream $pes_header{stream_id}\n";
+ }
+}
+
+
+sub output_files {
+ my $self = shift;
+
+ if ($self->{streamcode} ne "PES_stream") {return}
+
+ my @audiofiles;
+ my $moviefile;
+ my @filelist;
+
+ foreach(keys(%{$self->{streams}})) {
+ if ($self->{ignore_streams}{$_}){
+ next;
+ }
+ if ( $_ =~ /(c|b)/ ) {
+ push @audiofiles, $self->{streams}{$_}{outfilename};
+ }
+ else {
+ $moviefile = $self->{streams}{$_}{outfilename};
+ }
+ }
+ @audiofiles = sort(@audiofiles);
+ @filelist = ($moviefile, @audiofiles);
+ return \@filelist;
+}
+
+sub analyse_ac3_stream {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+ $self->{frame_regex} = $AC3_regex;
+ ($payload, my %frame_info) = analyse_ac3_frame($self, $payload);
+ if ($payload eq "-1") {
+ print "AC3 Packet could not be analysed, skipping\n";
+ return;
+ }
+
+ $self->{packet_start} = substr($payload,0,2);
+
+
+ $self->{freq} = $frame_info{freq};
+
+ $self->{frame_bytes} = $frame_info{frame_bytes};
+ $self->{bitrate} = $frame_info{bitrate};
+ $self->{mode} = $frame_info{mode};
+
+ ##################################################################
+ ############ ONE FRAME is 32 ms
+ ##################################################################
+
+ $self->{frame_ticks} = $frame_info{frame_ticks};
+
+
+ $self->{outfilename} = "$self->{outputpath}/$self->{streamcode}.ac3";
+ open $self->{outfile}, ">$self->{outputpath}/$self->{streamcode}.ac3" || die "Can not open file for ac3: $!\n" if (!($self->{outfile}));
+ binmode $self->{outfile};
+ $self->{check_frames} = \&check_audio_frames;
+ $self->{analyse_frames} = \&analyse_ac3_frame;
+ $self->{frame_regex} = $AC3_regex;
+}
+
+sub analyse_ac3_frame {
+ my $self = shift;
+ my $payload = shift;
+ my $bits = unpack("B64", substr($payload, 0, 8));
+ my %frame_info;
+ if (!(substr($payload, 0, 2) =~ /^$AC3_regex$/)) {
+ #if (substr($bits,0,16) ne "0000101101110111") {
+ print "No Audio syncword found for stream $self->{streamcode}, searching for audio sync\n" if $self->{debug};
+ print substr($bits,0,16) . " found, 0000101101110111 expected\n" if $self->{debug};
+ if ($payload =~ /$AC3_regex/g) {
+ print "\n AC3 regex matched at " . pos($payload) . "\n" if $self->{debug};
+ $payload = substr($payload, (pos($payload)-2));
+ }
+ else {
+ print "No audio frame found in this paket\n";
+ return -1;
+ }
+ $bits = unpack("B64", substr($payload, 0, 8));
+ }
+
+ my $fscod = substr($bits,32,2);
+
+ if ($fscod == "00") {$frame_info{freq} = 48000}
+ elsif ($fscod == "01") {$frame_info{freq} = 44100}
+ elsif ($fscod == "11") {$frame_info{freq} = 32000}
+ elsif ($fscod == "10") {print "Illeagal AC3 freq\n"; return -1}
+
+ my $frmsizecod = substr($bits,34,6);
+ $frame_info{frame_bytes} = $AC3_frame_info{$frame_info{freq}}{$frmsizecod};
+ $frame_info{bitrate} = $AC3_frame_info{bitrate}{$frmsizecod};
+
+ my $acmod = substr($bits, 48, 3);
+ if ($acmod == "000") {$frame_info{mode} = "1+1"}
+ elsif ($acmod == "001") {$frame_info{mode} = "1/0"}
+ elsif ($acmod == "010") {$frame_info{mode} = "2/0"}
+ elsif ($acmod == "011") {$frame_info{mode} = "3/0"}
+ elsif ($acmod == "100") {$frame_info{mode} = "2/1"}
+ elsif ($acmod == "101") {$frame_info{mode} = "3/1"}
+ elsif ($acmod == "110") {$frame_info{mode} = "2/2"}
+ elsif ($acmod == "111") {$frame_info{mode} = "3/2"}
+
+ ##################################################################
+ ############ ONE FRAME is 32 ms
+ ##################################################################
+ if ($fscod ne "00") {print "not an 48 KHz stream, exiting\n"; return -1}
+ $frame_info{frame_ticks} = 8 * $frame_info{frame_bytes} * 90000 / $frame_info{bitrate};
+ $frame_info{streamtype} = "AC3_Audio";
+ if ($self->{Unit_analysis_counter}++ > 20) {
+ $self->{Unit_analysis_hash}{mode}{$frame_info{mode}}++;
+ }
+
+
+ return $payload, %frame_info;
+}
+
+sub check_audio_frames {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+ my $counter = 0;
+ my $FH = $self->{outfile};
+
+
+ if ($self->{change_message}) {return}
+
+ if ($self->{dump_payload}) {
+ print "Dumping payload of stream $self->{streamcode}, ($self->{dump_payload} to dump left)\n" if $self->{debug};
+ my $DUMPFH;
+ if (! $self->{"$self->{streamcode}.dump"}) {
+ print "Trying to open dumpfile\n";
+ open $DUMPFH, ">$self->{streamcode}.dump" || die "Can not open dumpfile: $!\n";
+ binmode $DUMPFH;
+ $self->{"$self->{streamcode}.dump"} = $DUMPFH;
+ print "opened dumpfile\n";
+ }
+ $DUMPFH = $self->{"$self->{streamcode}.dump"};
+ print $DUMPFH $payload;
+ if ($self->{dump_payload}-- == 1) {
+ exit;
+ }
+ return;
+ }
+
+ $payload = $self->{packet_rest} . $payload;
+
+ ($payload, my %frame_info) = &{$self->{analyse_frames}}($self, $payload) ;
+ if (!$frame_info{frame_ticks}) {
+ print "Analysis failed in audio stream $self->{streamcode}, returning from check_audio_frames\n" if $self->{debug};
+ return;
+ }
+ if ((!($self->{frame_bytes} eq $frame_info{frame_bytes}) || (!($self->{bitrate} eq $frame_info{bitrate}))) && ($self->{streamcode} ne "bd")) {
+ if ($self->{audio_only}){
+ $self->{copyright} = $frame_info{copyright};
+ $self->{bitrate} = $frame_info{bitrate};
+ $self->{freq} = $frame_info{freq};
+ $self->{padding_bit} = $frame_info{padding_bit};
+ $self->{streamtype} = $frame_info{streamtype};
+ $self->{frame_bytes} = $frame_info{frame_bytes};
+ $self->{frame_ticks} = $frame_info{frame_ticks};
+ $self->{mode} = $frame_info{mode};
+ print "\nAudio Format Changed:\n";
+ print "\naudio stream $self->{streamcode} info ($self->{streamtype}):\n";
+ print "Sample frequency: $self->{freq}\n"; # only for Audio
+ print "Bitrate: $self->{bitrate}\n"; # only for Audio
+ print "Mode: $self->{mode}\n"; # only for Audio
+ print "Copyright: $self->{copyright}\n" if $self->{copyright}; # only for Audio
+ print "Frame length (bytes) $self->{frame_bytes}\n"; # only for Audio
+ print "Frame length (ticks) $self->{frame_ticks} (90000 / sec)\n\n\n"; # one tick is 1 / 90000 of a second
+ }
+
+
+ elsif (!$self->{frames_written}) {
+ @{$self->{frame_times}} = ();
+ $self->{frame_times}[0] = 0;
+ @{$self->{frame_buffer}} = ();
+ @{$self->{frame_length}} = ();
+ $self->{frameno} = 0;
+ $self->{copyright} = $frame_info{copyright};
+ $self->{bitrate} = $frame_info{bitrate};
+ $self->{freq} = $frame_info{freq};
+ $self->{padding_bit} = $frame_info{padding_bit};
+ $self->{streamtype} = $frame_info{streamtype};
+ $self->{frame_bytes} = $frame_info{frame_bytes};
+ $self->{frame_ticks} = $frame_info{frame_ticks};
+ $self->{mode} = $frame_info{mode};
+
+ $self->{single_frame} = get_silent_frame($self, \%frame_info);
+ if ($self->{single_frame} eq "-1") {
+ $self->{single_frame} = substr($payload, 0, $frame_info{frame_bytes});
+ print "Defined first audio frame as single frame for later sync in stream $self->{streamcode}\n" if $self->{debug};
+ print "Frame has a length of " . length($self->{single_frame}) . "\n" if $self->{debug};
+ }
+
+ print "\nAUDIO FORMAT CHANGED, new parameters:\n";
+ print "\naudio stream $self->{streamcode} info ($self->{streamtype}):\n";
+ print "Sample frequency: $self->{freq}\n"; # only for Audio
+ print "Bitrate: $self->{bitrate}\n"; # only for Audio
+ print "Mode: $self->{mode}\n"; # only for Audio
+ print "Copyright: $self->{copyright}\n" if $self->{copyright}; # only for Audio
+ print "Frame length (bytes) $self->{frame_bytes}\n"; # only for Audio
+ print "Frame length (ticks) $self->{frame_ticks} (90000 / sec)\n\n\n";
+ }
+ elsif ($total_size - $bytes_read < 10000000) {
+ if (! $self->{change_message}) {
+ print "\nAudio Format changed " . int(($total_size - $bytes_read)/1000000) ." MBytes before end, continuing...\n";
+ $self->{change_message} = 1;
+ }
+ return;
+ }
+ else {
+ print "Change in Audio Mode from $self->{mode} to $frame_info{mode}, this will not work during remux, stopping!\n";
+ print "Change in Audio bitrate from $self->{bitrate} to $frame_info{bitrate}, this will not work during remux, stopping!\n";
+ print "You should cut the movie at this position and process the pieces independently\n";
+ my $frame_number = $self->{frames_written} + scalar(@{$self->{frame_buffer}});
+ my $seconds = $frame_number * $self->{frame_ticks} / 90000; #/
+ print "Current position: $seconds sec.\n";
+ $kill_me = $self->{streamcode};
+ }
+ }
+
+ if ((! $self->{frame_times}[0]) && ( $pes_header{PTS_decimal}) && ($self->{frameno} > 0)){
+ print "\nFirst audio timestamp at $self->{frameno} for stream $self->{streamcode}\n" if $self->{debug};
+ print "First Audio Timestamp is $pes_header{PTS_decimal}\n" if $self->{debug};
+ for (my $i = 1; $i <= $self->{frameno}; $i++) {
+ $self->{frame_times}[$self->{frameno} - $i] = $pes_header{PTS_decimal} - ($i * $self->{frame_ticks});
+ }
+ print "\nAudio now starting at $self->{frame_times}[0]\n" if $self->{debug};
+ }
+
+ my $first_timestamp = 0;
+
+ if ($pes_header{PTS_decimal}) {
+ $first_timestamp = $pes_header{PTS_decimal};
+ }
+ else {
+ $first_timestamp = ${$self->{frame_times}}[-1] + $frame_info{frame_ticks} if (${$self->{frame_times}}[-1]);
+ }
+
+ if (!($self->{single_frame})) {
+
+ $self->{single_frame} = get_silent_frame($self, \%frame_info);
+ if ($self->{single_frame} eq "-1") {
+ $self->{single_frame} = substr($payload, 0, $frame_info{frame_bytes});
+ print "\nDefined first audio frame as single frame for later sync in stream $self->{streamcode}\n" if $self->{debug};
+ print "Frame has a length of " . length($self->{single_frame}) . "\n" if $self->{debug};
+ }
+ }
+
+ my $packet_offset = 0;
+ while ($packet_offset + $frame_info{frame_bytes} +2 <= (length($payload))) {
+
+ if ((substr($payload, $packet_offset, 2) =~ /^$self->{frame_regex}$/)) {
+ push @{$self->{frame_buffer}}, substr($payload, $packet_offset, $frame_info{frame_bytes});
+
+ push @{$self->{frame_times}}, $first_timestamp + $counter * $frame_info{frame_ticks};
+ push @{$self->{frame_length}}, $frame_info{frame_ticks};
+ $packet_offset += $frame_info{frame_bytes};
+ $counter++;
+ $self->{frameno}++;
+ }
+ else {
+ print "\n$self->{frame_regex} did not match against " . (substr($payload,$packet_offset,2)) . "\n" if $self->{debug};
+ if ($counter == 0) {
+ $payload = search_audio_sync($self, substr($payload, $packet_offset));
+ $self->{packet_rest} = "";
+ $packet_offset = 0;
+ }
+ else {
+ print "\nAudio Format change within packet, skipping \n" if $self->{debug};
+ $self->{packet_rest} ="";
+ return;
+ }
+ }
+ }
+
+ if ($packet_offset < (length($payload))) {
+ $self->{packet_rest} = substr($payload, $packet_offset);
+ }
+ else {
+ $self->{packet_rest} = "";
+ }
+ if (scalar(@{$self->{frame_times}}) < 100) {
+ return;
+ }
+ new_flush($self);
+ return;
+}
+
+sub new_flush {
+ my $self = shift;
+ my $FH = $self->{outfile};
+
+ if ($self->{audio_only}) {
+ while (scalar(@{$self->{frame_times}}) > 50) {
+ $self->{last_frame_time} = shift @{$self->{frame_times}};
+ shift @{$self->{frame_length}};
+ print $FH shift @{$self->{frame_buffer}};
+ $self->{frames_written} ++;
+ }
+ return;
+ }
+
+ my ($chunk_start, $chunk_end, $video_frameno) = split /::/,$cutlist[$self->{cutcounter}];
+ if ($self->{first_flush}){
+ sync_audio_start($self, $chunk_start);
+ $self->{first_flush} = 0;
+ }
+
+ #
+ # Here the new time drift correction is performed. Note that this is performed wthe the beginning of a Aufio flush NOT when it occurs
+ # This should be ok, since a) The shift builds up very slowly and b) a buffer flush is only 60 Frames (max 1,8 sec) long
+ #
+ if (abs($self->{time_drift}) > ($self->{frame_ticks} * 1.5)) {
+ if (abs($self->{time_drift}) > 900000) {
+ print "Timedrift bigger than 10 Seconds, killing stream $self->{streamcode}\n";
+ $kill_me = $self->{streamcode};
+ return;
+ }
+ print "Timedrift is too big, trying to correct $self->{time_drift} ticks\n";
+ #sleep 1;
+ if ($self->{time_drift} < 0) {
+ while ($self->{time_drift} < ($self->{frame_ticks} * -1.5)) {
+ shift @{$self->{frame_buffer}};
+ shift @{$self->{frame_times}};
+ $self->{time_drift} += shift @{$self->{frame_length}};
+ print "Dropped frame to correct time drift EXPERIMENTAL!!!\n";
+ if (@{$self->{frame_buffer}} == 0) {
+ print "could not drop enough Audio frames in stream $self->{streamcode}to compensate for timedrift, still $self->{time_drift} left\nContinuing anyway...\n";
+ # sleep 1;
+ if ($self->{dump_buffer}) {$kill_me = "all"}
+ return;
+ }
+ }
+ }
+ else {
+ my $ins_frames = insert_frames($self, $self->{time_drift});
+ my $ins_time = $ins_frames * $self->{frame_ticks};
+ print "Inserted $ins_frames Audio Frames ($ins_time ticks) to correct a drift of $self->{time_drift}. EXPERIMENTAL\n";
+ $self->{time_drift} -= $ins_time;
+ }
+ }
+
+ #
+ #If the currenct chunk's end has not been determined yet, then we just flush 60 frames and calculate the timedrift
+ #
+
+ if (! $chunk_end) {
+ #my $frame;
+ #my $time;
+ #print "No Chunk end, just flushing\n";
+ my $counter = 0;
+ my $bufferstart = ${$self->{frame_times}}[0];
+ my $drift;
+ my $calc_end = $bufferstart;
+ while (scalar(@{$self->{frame_times}}) > 40) {
+ $self->{last_frame_time} = shift @{$self->{frame_times}};
+ $calc_end += shift @{$self->{frame_length}};
+ print $FH shift @{$self->{frame_buffer}};
+ $counter++;
+ $self->{frames_written}++;
+ }
+ $drift = $self->{last_frame_time} - $calc_end + $self->{frame_ticks};
+
+ #This should not be necessary, since a PTS Overflow will be treated as a cut...
+ #
+ #if ($drift < -8000000000) {
+ # print "Time drift indicates a PTS overflow, correcting\n";
+ # $drift += 8589934592;
+ # print "Now time drift will be " . ($self->{time_drift} + $drift) . "\n";
+ #}
+ $self->{time_drift} += $drift;
+ if ($self->{show_drift} && (abs($drift) > 0)) {
+ print "\nAUDIO TIMEDRIFT for stream $self->{streamcode}: $self->{time_drift}\n" if (abs($self->{time_drift}) > 100);
+ }
+ return;
+ }
+
+ #
+ #If the currenct chunk's end has been determined, but is at the end of the buffer, only 20 Frames are flushed to "move" the cut to the middle of the buffer
+ #Thereby we ensure that enough Audioframes for syncing are in the buffer before AND after the cut
+ #
+ my $cut_diff = $chunk_end - $self->{last_frame_time};
+
+ if (60 * ${$self->{frame_length}}[0] + $self->{last_frame_time} < $chunk_end) {
+
+ print "delaying sync to next run, chunk_end is $chunk_end\n" if $self->{debug};
+ my $calc = 60 * ${$self->{frame_length}}[0] + $self->{last_frame_time};
+ while (scalar(@{$self->{frame_times}}) > 80) {
+ $self->{last_frame_time} = shift @{$self->{frame_times}};
+ shift @{$self->{frame_length}};
+ print $FH shift @{$self->{frame_buffer}};
+ $self->{frames_written}++;
+ }
+ return;
+ }
+
+ # Then we test wether the cut is no cut but a PTS overflow .... If yes, we just dump the buffer, increase the cutcount and continue.
+ # No Timedrift is computed for this buffer
+ if ($chunk_end > 8589889595) {
+ print "syncing at PTS Overflow, special care is taken!\n";
+ while (scalar(@{$self->{frame_times}}) > 20) {
+ $self->{last_frame_time} = shift @{$self->{frame_times}};
+ shift @{$self->{frame_length}};
+ print $FH shift @{$self->{frame_buffer}};
+ $self->{frames_written}++;
+ }
+ $self->{cutcounter}++;
+ return;
+ }
+
+
+ #
+ # Finally we sync the end of the Audio chunk
+ #
+
+
+ sync_audio_end($self, $chunk_end, $video_frameno);
+ $self->{cutcounter}++;
+ # Extract the beginning of the next chunk
+ ($chunk_start, $chunk_end) = split /::/,$cutlist[$self->{cutcounter}];
+ if (! $chunk_start) {
+ print "Could not extract chunk start from $cutlist[$self->{cutcounter}]\n";
+ $kill_me = $self->{streamcode};
+ }
+ # And sync the start of the next chunk
+ sync_audio_start($self, $chunk_start);
+}
+
+sub sync_audio_end {
+ my $self = shift;
+ my $chunk_end = shift;
+ my $video_frameno = shift;
+
+ my $FH = $self->{outfile};
+ my $counter = 0;
+ my $cut_frame = 0;
+
+ for (my $i = 0; $i < scalar(@{$self->{frame_times}}); $i++) {
+ if (abs(${$self->{frame_times}}[$i + 1] - ${$self->{frame_times}}[$i]) > 90000) {
+ $cut_frame = $i;
+ #print "The end of the chunk is at frame $cut_frame of the buffer\n";
+ last;
+ }
+ }
+ print "\nsyncing end of chunk in stream $self->{streamcode}...\n" if $self->{debug};
+ my $bufferstart = ${$self->{frame_times}}[0];
+ my $calc_end = $bufferstart;
+ while ((${$self->{frame_times}}[0] < $chunk_end - $self->{final_desync} + ($self->{frame_ticks} * 0.5))
+ && ($counter < $cut_frame)){
+
+ $self->{last_frame_time} = shift @{$self->{frame_times}};
+ $calc_end += shift @{$self->{frame_length}};
+ print $FH shift @{$self->{frame_buffer}};
+ $self->{frames_written}++;
+ $counter++;
+ if (scalar(@{$self->{frame_times}}) == 0) {
+ print "syncing failed for stream $self->{streamcode} while trying to write Frames until $chunk_end\n";
+ print "time of last frame written was $self->{last_frame_time}\n";
+ $kill_me = $self->{streamcode};
+ }
+ }
+ print "written $self->{frames_written} frames, time $self->{last_frame_time}\n"if $self->{debug};
+ #
+ # We dont need a timedrift calc at the end of a cunk, it is not corrected anyway...At the end the real length correction is performed
+ #
+ #my $drift = $self->{last_frame_time} - $calc_end + ${$self->{frame_length}}[0];
+ #if ($drift < -8000000000) {
+ # print "Time drift indicates a PTS overflow, correcting\n";
+ # $drift += 8589934592;
+ # print "Now time drift will be " . ($self->{time_drift} + $drift) . "\n";
+ #}
+ #$self->{time_drift} += $drift;
+ #if ($self->{show_drift} && (abs($drift) > 0)) {
+ # print "\nAUDIO TIMEDRIFT for stream $self->{streamcode}: $self->{time_drift}\n" if (abs($self->{time_drift}) > 100);
+ #}
+ #my $diff = $chunk_end - ($self->{last_frame_time} + $self->{frame_ticks});
+ my $vid_time = $video_frameno * 3600;
+ my $aud_time = $self->{frames_written} * $self->{frame_ticks};
+ my $frame_diff = $vid_time - $aud_time;
+ print "For the end sync $self->{cutcounter} of $self->{streamcode} frames were printed up to $self->{last_frame_time} to match $chunk_end of chunk $self->{cutcounter} leaving: $frame_diff)\n"if $self->{debug};
+ my $ins_frames = insert_frames($self, $frame_diff);
+ $self->{frames_written} += $ins_frames;
+ $aud_time = $self->{frames_written} * $self->{frame_ticks};
+ $self->{final_desync} = $vid_time - $aud_time;
+ $self->{time_drift} = 0;
+ $self->{written_chunks}[$self->{cutcounter}] .= ($self->{last_frame_time} + $self->{frame_ticks} * ($ins_frames+1));
+}
+
+sub sync_audio_start {
+ my $self = shift;
+ my $chunk_start = shift;
+ print "\nFirst packet ts for stream $self->{streamcode} in buffer $self->{frame_times}[0] last:$self->{frame_times}[-1]\n" if $self->{debug};
+ print "\nIn stream $self->{streamcode}: new start $chunk_start\n" if $self->{debug};
+ my $counter = 0;
+ while ((${$self->{frame_times}}[0] < $chunk_start - $self->{frame_ticks} * 0.5)
+ || ( (${$self->{frame_times}}[0] - $chunk_start) > 90000 )) { # For PTS overflows and "catted" movies
+ #print "\ndropping frame with time ${$self->{frame_times}}[$counter]\n" if $self->{debug};
+ shift @{$self->{frame_times}};
+ shift @{$self->{frame_buffer}};
+ shift @{$self->{frame_length}};
+ $counter++;
+ if (scalar(@{$self->{frame_times}}) == 0) {
+ print "\nsyncing failed for stream $self->{streamcode} while trying to drop Frames until $chunk_start\n";
+ #print "time of last frame dropped was $time_drop\n";
+ $kill_me = $self->{streamcode};
+ }
+ }
+ my $diff = $chunk_start - ${$self->{frame_times}}[0];
+ my $ins_frames = insert_frames($self, $diff);
+ print "IN SYNC AUDIO START ($self->{streamcode}): diff is now: $diff\n" if $self->{debug};
+ $self->{frames_written} += $ins_frames;
+ $self->{last_frame_time} = ${$self->{frame_times}}[0];
+ $self->{written_chunks}[$self->{cutcounter}] = (${$self->{frame_times}}[0] - $self->{frame_ticks} * $ins_frames) . "::";
+ print "\nCHUNKSTART: dropped $counter frames at the beginning of $self->{streamcode}, start is now ${$self->{frame_times}}[0] matching $chunk_start leaving $diff\n" if $self->{debug};
+}
+
+
+
+sub insert_frames {
+ my $self = shift;
+ my $diff = shift;
+
+ $diff += $self->{final_desync};
+ my $FH = $self->{outfile};
+ my $abs_diff = abs($diff);
+
+ my $counter = 0;
+ if ($abs_diff > 900000) {
+ print "more than 10 seconds auf Audio missing, killing stream $self->{streamcode}\n";
+ $kill_me = $self->{streamcode};
+ return;
+ }
+ while ($abs_diff > ($self->{frame_ticks} * 0.5 )) {
+ print $FH $self->{single_frame};
+ $abs_diff -= $self->{frame_ticks};
+ $counter++;
+ }
+ if ($diff < 0) {$abs_diff *= -1}
+ print "INSERT FRAMES:wrote additional $counter frames to close $diff ticks, leaving $abs_diff\n" if $self->{debug};
+ $self->{final_desync} = $abs_diff;
+ #print "Final desync is now $self->{final_desync}\n";
+ return $counter;
+}
+
+sub analyse_video_stream {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+
+ $self->{packet_start} = pack("H8", "00000100");
+ my $seqstart = pack("H8", "000001b3");
+ if (!($payload =~ /$seqstart(.{8})/s)) {print "No SeqHeader in the first Frame, exiting\n"; $kill_me = "all"}
+
+ # Here we used to kill the stream if no PTS is found, that changed... We kill the strream if we find a GOP withou ANY ts
+ $self->{check_frames} = \&check_GOPs;
+
+ my $bitview = unpack("B64", $1);
+ print "Matched: $& at $-[0] bitview of the header\n$bitview \n" if $self->{debug};
+ my $hor_size = "0b" . substr($bitview,0,12);
+ $self->{horizontal_size} = oct $hor_size;
+ my $ver_size = "0b" . substr($bitview,12,12);
+ $self->{vertical_size} = oct $ver_size;
+ my $asp_ratio = substr($bitview,24,4);
+
+ if ($asp_ratio eq "0001"){
+ $self->{aspect_ratio} = "1:1";
+ }
+ elsif ($asp_ratio eq "0010") {
+ $self->{aspect_ratio} = "4:3";
+ }
+ elsif ($asp_ratio eq "0011") {
+ $self->{aspect_ratio} = "16:9";
+ }
+ elsif ($asp_ratio eq "0111") {
+ $self->{aspect_ratio} = "2.21:1";
+ }
+ else {
+ $self->{aspect_ratio} = "$asp_ratio";
+ }
+
+ my $fps = substr($bitview,28,4);
+ if ($fps eq "0001") {
+ $self->{fps} = 23.967;
+ }
+ elsif ($fps eq "0010") {
+ $self->{fps} = 24;
+ }
+ elsif ($fps eq "0011") {
+ $self->{fps} = 25;
+ }
+ elsif ($fps eq "0100") {
+ $self->{fps} = 29.97;
+ }
+ elsif ($fps eq "0101") {
+ $self->{fps} = 30.97;
+ }
+
+ $self->{bitrate_value} = 400 * ( oct ("0b" . substr($bitview,32, 18)));
+ my $marker_bit = substr($bitview,50, 1);
+ print "we got for fps: $fps\n" if $self->{debug};
+ $self->{frame_ticks} = 90000 / $self->{fps};
+ $self->{outfilename} = "$self->{outputpath}/$self->{streamcode}.mpv";
+ open $self->{outfile}, ">$self->{outputpath}/$self->{streamcode}.mpv" || die "Can not open file for m2v: $!\n";
+ binmode $self->{outfile};
+}
+
+
+sub analyse_GOP {
+ my $self = shift;
+
+
+ my %GOP_info;
+ my $seqstart = pack("H8", "000001b3");
+ if (!($self->{GOP_buffer} =~ /$seqstart(.{8})/s)) {
+
+ if (! $self->{GOPno}) {
+ print "\nNo Seq Header in the GOP, exiting\n";
+ $kill_me = "all";
+ }
+ else {
+ print "\nA GOP without Sequence Header. Weird recording\n";
+ open DFH, ">./strange_gop.mpg";
+ print DFH $self->{GOP_buffer};
+ close DFH;
+ return;
+ }
+ }
+
+ my $bitview = unpack("B64", $1);
+ my $hor_size = "0b" . substr($bitview,0,12);
+ $GOP_info{horizontal_size} = oct $hor_size;
+ $self->{Unit_analysis_hash}{horizontal_size}{$GOP_info{horizontal_size}}++;
+ my $ver_size = "0b" . substr($bitview,12,12);
+ $GOP_info{vertical_size} = oct $ver_size;
+ $self->{Unit_analysis_hash}{vertical_size}{$GOP_info{vertical_size}}++;
+ my $asp_ratio = substr($bitview,24,4);
+
+ if ($asp_ratio eq "0001"){
+ $GOP_info{aspect_ratio} = "1:1";
+ }
+ elsif ($asp_ratio eq "0010") {
+ $GOP_info{aspect_ratio} = "4:3";
+ }
+ elsif ($asp_ratio eq "0011") {
+ $GOP_info{aspect_ratio} = "16:9";
+ }
+ elsif ($asp_ratio eq "0111") {
+ $GOP_info{aspect_ratio} = "2.21:1";
+ }
+ else {
+ $GOP_info{aspect_ratio} = "$asp_ratio";
+ }
+
+
+ my $fps = substr($bitview,28,4);
+ if ($fps eq "0001") {
+ $GOP_info{fps} = 23.967;
+ }
+ elsif ($fps eq "0010") {
+ $GOP_info{fps} = 24;
+ }
+ elsif ($fps eq "0011") {
+ $GOP_info{fps} = 25;
+ }
+ elsif ($fps eq "0100") {
+ $GOP_info{fps} = 29.97;
+ }
+ elsif ($fps eq "0101") {
+ $GOP_info{fps} = 30.97;
+ }
+
+
+ $GOP_info{bitrate_value} = 400 * ( oct ("0b" . substr($bitview,32, 18)));
+ $self->{Unit_analysis_hash}{bitrate_value}{$GOP_info{bitrate_value}}++;
+ $self->{Unit_analysis_hash}{aspect_ratio}{$GOP_info{aspect_ratio}}++;
+ $self->{Unit_analysis_hash}{fps}{$GOP_info{fps}}++;
+
+}
+
+
+sub check_GOPs {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+ my $FH = $self->{outfile};
+ my $gopstart = pack("H8", "000001b8");
+ # First we just grep for the GOP start sequence
+ if ((! $self->{GOPno}) && (! $self->{GOP_buffer} )) {
+ $cutlist[$self->{cutcounter}] = "$pes_header{PTS_decimal}";
+ push @{$self->{timestamps}}, $pes_header{PTS_decimal};
+ my $seqstart = pack("H8", "000001b3");
+ $payload =~ /$seqstart/g;
+ if (pos($payload)-4 != 0) {
+ print "The very first GOP did not start at 0, but at " . (pos($payload)-4) ."\n";
+ $payload = substr($payload, (pos($payload)-4));
+ }
+ else {
+ print "Yes, it did start at 0\n" if $self->{debug};
+ }
+ $self->{GOP_buffer} .= $payload;
+ return;
+ }
+
+ if (! ($payload =~ /$gopstart/gm) ){
+ $self->{GOP_buffer} .= $payload;
+ #print "no GOPstart found, returning\n";
+ return;
+ }
+
+ $self->{GOPno}++;
+
+ my @pics = $self->{GOP_buffer} =~ /$self->{packet_start}/gm;
+ my $frames_in_GOP = scalar(@pics);
+
+
+ $self->{frameno} += $frames_in_GOP;
+
+ if ($self->{Unit_analysis_counter}++ > 9) {
+ $self->{Unit_analysis_counter} = 1;
+ analyse_GOP($self);
+ }
+
+
+ if (! $pes_header{PTS_decimal}) {die "Found no ts at GOP start\n";}
+
+ push @{$self->{timestamps}}, $pes_header{PTS_decimal};
+
+ my $pts_diff = ${$self->{timestamps}}[-1] - ${$self->{timestamps}}[-2];
+ my $calc = $self->{frame_ticks} * $frames_in_GOP;
+ #print "Timestamp diff is $pts_diff and frame time is $calc\n";
+
+ if ($calc != $pts_diff) {
+ my $ts_shift = $pts_diff - $calc;
+ #print "TS not as expected, diff is $ts_shift\n";
+ if (abs($ts_shift) > 90000) {
+ my $chunk_end = ${$self->{timestamps}}[-2] + $frames_in_GOP * $self->{frame_ticks};
+ if ($ts_shift > 0) {
+ print "\nCut detected in Video at $chunk_end\n";
+ }
+ else {
+ print "\nCut detected at $chunk_end, possibly a PTS Overflow\n";
+ }
+ $cutlist[$self->{cutcounter}] .= "::$chunk_end" . "::$self->{frameno}";
+ print "Chunk entry is $cutlist[$self->{cutcounter}]\n"if $self->{debug};
+ $self->{cutcounter}++;
+ $cutlist[$self->{cutcounter}] = "$pes_header{PTS_decimal}";
+ $self->{time_drift} = 0;
+ }
+ else {
+ #print "shift is too small for a cut, storing the shift for later use\n";
+ $self->{time_drift} += $ts_shift;
+ if (abs($self->{time_drift}) > 1000) {
+ print "\nTime Drift in VIDEO is now $self->{time_drift}, ts: ${$self->{timestamps}}[-2] and ${$self->{timestamps}}[-1] \n" if $self->{show_drift};
+ }
+ }
+ }
+ print $FH $self->{GOP_buffer};
+ $self->{GOP_buffer} = $payload;
+}
+
+
+sub search_audio_sync {
+
+ my $self = shift;
+ my $payload = shift;
+ my $new_offset = -1;
+ print "Searching for audio sync\n"if $self->{debug};
+ while ($payload =~ /$self->{frame_regex}/g) {
+ my $potential_start = (pos($payload)-2);
+ print "\tFound a potential audio syncword at $potential_start: " . unpack("H*" ,substr($payload,$potential_start,3)) . "\n" if $self->{debug};
+ (my $dummy_payload, my %frame_info) = analyse_audio_frame($self, substr($payload,$potential_start));
+ if (($potential_start + $frame_info{frame_bytes}) < length($payload)) {
+ print "\twe should have enough payload left to verify...\n" if $self->{debug};
+ #(my $dummy_payload, my %frame_info) = analyse_audio_frame($self, substr($payload,$potential_start));
+
+ print "\tchecking " . unpack("H4",substr($payload,($potential_start + $frame_info{frame_bytes}),2)) ."\n" if $self->{debug};
+
+ if ($frame_info{packet_start} eq substr($payload,($potential_start + $frame_info{frame_bytes}),2)) {
+ print "\tfound an additional audio sync in the right distance\n\tAll should be fine\n" if $self->{debug};
+ $new_offset = $potential_start;
+ print "\tNew attempt will be started at $new_offset\n" if $self->{debug};
+ $self->{packet_rest} ="";
+ last;
+ }
+ else {
+ print "\tNext audio start not found, continuing to check...\n" if $self->{debug};
+ }
+ }
+ else {
+ print "\tnot enough payload left to verify\n\tGood Luck\n\tSkipping to next packet\n" if $self->{debug};
+ last;
+ }
+ }
+
+ if ($new_offset != -1) {
+ return (substr($payload,$new_offset));
+ # &{$self->{check_frames}} ($self, substr($payload,$new_offset), \%pes_header);
+ }
+ else {
+ print "\tCould not find anything usefull in this packet, contents dropped, maybe next packet?\n" if $self->{debug};
+ return (-1);
+ }
+}
+
+sub print_stats {
+ my $self = shift;
+
+ foreach (keys(%{$self->{streams}})) {
+ print "$self->{streams}{$_}{streamtype} for stream $_\n" if $self->{debug};
+ if ($self->{ignore_streams}{$_}) {
+ print "Ignoring stream $_\n";
+ next;
+ }
+
+ if (($self->{streams}{$_}{streamtype} =~ /audio/) || ($self->{streams}{$_}{streamtype} =~ /Layer/)) {
+ print "\naudio stream $_ info ($self->{streams}{$_}{streamtype}):\n";
+ print "Sample frequency: $self->{streams}{$_}->{freq}\n"; # only for Audio
+ print "Bitrate: $self->{streams}{$_}->{bitrate}\n"; # only for Audio
+ print "Mode: $self->{streams}{$_}->{mode}\n"; # only for Audio
+ print "Copyright: $self->{streams}{$_}->{copyright}\n" if $self->{streams}{$_}->{copyright}; # only for Audio
+ print "Frame length (bytes) $self->{streams}{$_}->{frame_bytes}\n"; # only for Audio
+ print "Frame length (ticks) $self->{streams}{$_}->{frame_ticks} (90000 / sec)\n\n\n"; # one tick is 1 / 90000 of a second
+ }
+ if ($self->{streams}{$_}{streamtype} eq "video") {
+ print "video stream $_ info:\n";
+ print "Frame length (ticks) $self->{streams}{$_}->{frame_ticks} (90000 / sec)\n"; # one tick is 1 / 90000 of a second
+ print "Aspect ratio $self->{streams}{$_}->{aspect_ratio}\n";
+ print "Horizontal size $self->{streams}{$_}->{horizontal_size} \n";
+ print "Vertical size $self->{streams}{$_}->{vertical_size} \n";
+ print "Frames per Second $self->{streams}{$_}->{fps} \n";
+ print "Bitrate: $self->{streams}{$_}{bitrate_value}\n\n\n";
+ }
+ }
+}
+
+sub analyse_audio_stream {
+ my $self = shift;
+ my $payload = shift;
+ my %pes_header = %{shift @_};
+
+ #$self->{cuts}[0] = "new start: $self->{first_ts} at 0\t"; #{frameno} : $pes_header{PTS_decimal} : -1 : -1 : $self->{streamcode} : ";
+
+ $self->{frame_regex} = $mp2_regex;
+
+ ($payload, my %frame_info) = analyse_audio_frame($self, $payload);
+
+ if ($payload eq "-1") {
+ print "Audio Packet could not be analysed, skipping\n" if $self->{debug};
+ return;
+ }
+ $self->{copyright} = $frame_info{copyright};
+ $self->{bitrate} = $frame_info{bitrate};
+ $self->{freq} = $frame_info{freq};
+ $self->{padding_bit} = $frame_info{padding_bit};
+ $self->{streamtype} = $frame_info{streamtype};
+ $self->{frame_bytes} = $frame_info{frame_bytes};
+ $self->{frame_ticks} = $frame_info{frame_ticks};
+ $self->{mode} = $frame_info{mode};
+ print "streamtype is: $self->{streamtype}\nbitrate is $self->{bitrate}\nfreq is $self->{freq}\ncopyright is $self->{copyright}\nframelength in byte is $self->{frame_bytes}\nin sec: " . ($self->{frame_ticks}/90000) . "\n" if $self->{debug};#/
+
+ if ($self->{streamtype} eq "MPEG1_Layer_2") {
+ $self->{check_frames} = \&check_audio_frames;
+ $self->{analyse_frames} = \&analyse_audio_frame;
+ $self->{frame_regex} = $mp2_regex;
+ }
+ $self->{outfilename} = "$self->{outputpath}/$self->{streamcode}.mpa";
+ open $self->{outfile}, ">$self->{outputpath}/$self->{streamcode}.mpa" || die "Can not open file for mpa: $!\n";
+ binmode $self->{outfile};
+}
+
+sub analyse_audio_frame {
+ my $self = shift;
+ my $frame = shift;
+ #my %pes_header = %{shift @_};
+
+ my %frame_info;
+ my $streamtype = "";
+ if (!(substr($frame, 0, 2) =~ /^$mp2_regex$/)) {
+ print "No Audio syncword at the beginning of the frame, searching\n" if $self->{debug};
+ if ($frame =~ /$mp2_regex/g) {
+ $frame = substr($frame, (pos($frame) - 2))
+ }
+ else {
+ print "No Audio Sync found, returning -1\n" if $self->{debug};
+ return -1;
+ }
+ print "found sync for mp2 stream $self->{streamcode}\n" if $self->{debug};
+ }
+ if (length($frame) < 8) {print "Frame too small to analyse, returning for stream $self->{streamcode}\n"; return;}
+ my $BITS = unpack("B32", $frame);
+ if (substr($BITS,12,1) == 1) {$streamtype = "MPEG1"} else {$streamtype = "MPEG2"}
+
+ if (substr($BITS,13,2) eq "01") {$streamtype .= "_Layer_3"}
+ elsif (substr($BITS,13,2) eq "10") {$streamtype .= "_Layer_2"}
+ elsif (substr($BITS,13,2) eq "11") {$streamtype .= "_Layer_1"}
+ else {print "Streamtype invalid, returning -1\n"; return -1}
+
+ $frame_info{copyright} = substr($BITS,15,1);
+ if ($bitrates{$streamtype}{substr($BITS,16,4)}) {
+ $frame_info{bitrate} = $bitrates{$streamtype}{substr($BITS,16,4)} . "000"
+ }
+ else {
+ print "\nBitrate of the audio stream $self->{streamcode} could not be determined, skipping packet and trying again later\n";
+ print "Bitrate bits are:" . substr($BITS,14,4) . "\n" if $self->{debug};
+ return -1;
+ }
+
+ $frame_info{freq} = $freqs{substr($streamtype,0,5)}{substr($BITS,20,2)};
+ if (! $frame_info{freq}) {
+ print "\nFrequence of the audio stream $self->{streamcode} could not be determined, skipping packet and trying again later\n";
+ print "Freq bits are:" . substr($BITS,20,2) . "\n" if $self->{debug};
+ return -1;
+ }
+ $frame_info{padding_bit} = substr($BITS,22,1);
+
+ $frame_info{streamtype} = $streamtype;
+
+
+ if (substr($BITS,24,2) eq "00") {$frame_info{mode} = "stereo"}
+ elsif (substr($BITS,24,2) eq "01") {$frame_info{mode} = "joint_stereo"}
+ elsif (substr($BITS,24,2) eq "10") {$frame_info{mode} = "dual_channel"}
+ elsif (substr($BITS,24,2) eq "11") {$frame_info{mode} = "mono"}
+ #print "Mode: $frame_info{mode}\nbitrate: $frame_info{bitrate}, freq: $frame_info{freq}" if $self->{debug};
+
+
+ $frame_info{frame_bytes} = ($frame_info{bitrate} * 144 / $frame_info{freq}) + $frame_info{padding_bit};
+ $frame_info{frame_ticks} = 8 * $frame_info{frame_bytes} * 90000 / $frame_info{bitrate};
+
+ #print "Mode: $frame_info{mode}\nbytes: $frame_info{frame_bytes}\nticks: $frame_info{frame_ticks}\nbitrate: $frame_info{bitrate}\n";
+ $frame_info{packet_start} = substr($frame,0,2);
+ #print "The start of the packet is: $frame_info{packet_start}\n";
+ return ($frame, %frame_info);
+ #print "frame analysed\n";
+}
+
+sub find_next_pes_packet {
+ my $self = shift;
+ my $buffer = shift;
+ my $old_offset = shift;
+ my $search_buffer;
+ if ($old_offset > 0) {
+ $search_buffer = substr($buffer, $old_offset);
+ }
+ else {
+ $search_buffer = $buffer;
+ }
+ my $packet_start = pack("H6", "000001");
+ print "TRYING TO FIND A NEW PES PACKET START AFTER $old_offset\n";
+ print "Got a buffer of length " . length($buffer) . " to match against\n";
+ open DOF, ">./debug_buffer.vdr" || die "Can not open File to dump buffer\n";
+ binmode DOF;
+ print DOF $buffer;
+ close DOF;
+ my $counter = 0;
+ while ($search_buffer =~ /$packet_start/g) {
+
+ print "Now we found the new start at " . (pos($search_buffer)-3) . " \n";
+ return (pos($search_buffer) - 3 + (length($buffer) - length($search_buffer)));
+ }
+
+ print "did not find a new start\n";
+ return -1;
+}
+
+sub final_flush {
+ my $self = shift;
+ my $FH = $self->{outfile};
+ print "should do the final flush for stream $self->{streamcode}\n"if $self->{debug};
+ if ($self->{streamcode} eq $self->{masterstream}) {
+ print "Flushing Video Buffer, " . (length($self->{GOP_buffer})) . " bytes left\n"if $self->{debug};
+ my @pics = $self->{GOP_buffer} =~ /$self->{packet_start}/gm;
+ print "The last GOP contains " . (scalar(@pics)) . " pics\n"if $self->{debug};
+ print $FH $self->{GOP_buffer};
+ $self->{GOP_buffer} = "";
+ $self->{frameno} += scalar(@pics);
+ my $chunk_end = ${$self->{timestamps}}[-1] + (scalar(@pics)) * $self->{frame_ticks};
+ $cutlist[$self->{cutcounter}] .= "::$chunk_end" . "::$self->{frameno}";
+ if ($self->{debug}) {
+ foreach(@cutlist) {print "FINAL: $_\n";}
+ }
+ $self->{frames_written} = $self->{frameno};
+ return;
+ }
+ print $FH @{$self->{frame_buffer}};
+ $self->{frames_written} += scalar(@{$self->{frame_buffer}});
+ @{$self->{frame_buffer}} = "";
+ if (! $self->{audio_only}) {
+ my ($chunk_start, $chunk_end, $video_frameno) = split /::/,$cutlist[-1];
+ my $vid_time = $video_frameno * 3600;
+ my $aud_time = $self->{frames_written} * $self->{frame_ticks};
+ my $diff = $vid_time - $aud_time;
+ $self->{frames_written} += insert_frames($self, $diff);
+ }
+}
+
+
+sub process_PES_packets {
+ my $self = shift;
+ my $limit = shift;
+ my $offset = 0; # stores the position we are at in the current buffer
+ $self->{IFH} = undef; # Stores the filehandle for the current file
+ $self->{EOF} = 0; # All files processed ?
+ $self->{total_input} = 0;
+ my $lengthcounter = 0;
+ my $packetcounter = 0;
+
+ my %pes_header;
+ my $head_tmpl = 'H6 H2 n1 B8 B8 C1 B40';
+ my $dts_tmpl = 'B40';
+
+ my $buffer = ${ readNextChunk($self) }; # First we read the first chunk of data
+
+ ( $pes_header{startcode}, $pes_header{stream_id},
+ $pes_header{PES_packet_length}, $pes_header{eflags1},
+ $pes_header{eflags2}, $pes_header{header_length},
+ $pes_header{PTS_raw_bits})
+ = unpack ( $head_tmpl, substr($buffer, 0, 12) );
+
+ while (($self->{EOF} eq 0 || $offset + $pes_header{PES_packet_length} + 1) < (length($buffer))) {
+ if ($kill_me) {
+ my @kill_list;
+ if ($kill_me eq "all") { @kill_list = keys(% {$self->{streams} })}
+ else { push @kill_list, $kill_me }
+ if ($self->{dump_buffer}) {
+ print "Something nasty happend, dumping debug.buffer and exit\n";
+ my $start = 0;
+ if ($offset < 10000) {
+ print "Unfortunately the nasty thing happend at the start of a buffer \n";
+ print "offset is only $offset; Dumping anyway...\n";
+ }
+ if ((length($buffer) - $offset )< 1000000) {
+ print "Unfortunately the nasty thing happend at the end of a buffer \n";
+ print "offset is already $offset Dumping anyway...\n";
+ }
+ if ($offset > 1000000) {$start = $offset - 1000000}
+ open DFH, ">./debug.buffer";
+ binmode DFH;
+ print DFH substr($buffer, $start, $start + 2000000);
+ close DFH;
+ print "please mail the file and a short description of the recording to \n";
+ print "vdrsync\@gmx.net, then I will try to fix the bug\n";
+ exit 1;dump_buffer($self)
+ }
+ print "got the kill list @kill_list\n";
+ foreach (@kill_list) {
+ $self->{ignore_streams}{$_} = 1;
+ my $FH = $self->{streams}{$_}->{outfile};
+ close $FH;
+ unlink $self->{streams}{$_}->{outfilename};
+ print "Stream $_ was killed due to an error\n";
+ }
+ if ($kill_me eq "all") {die "Skript stopped due to an error\n";}
+ $kill_me="";
+
+ }
+
+
+ ( $pes_header{startcode}, $pes_header{stream_id},
+ $pes_header{PES_packet_length}, $pes_header{eflags1},
+ $pes_header{eflags2}, $pes_header{header_length},
+ $pes_header{PTS_raw_bits})
+ = unpack ( $head_tmpl, substr($buffer, $offset, 14) );
+
+ if ($pes_header{startcode} ne "000001") {
+ print "No 0x000001 found at current packet, found $pes_header{startcode} instead\noffset is $offset\n";
+ $offset = find_next_pes_packet($self, $buffer, $offset);
+
+ next;
+ }
+ my $decimal_code = hex($pes_header{stream_id});
+ if ((!(191 < $decimal_code ) && (240 > $decimal_code)) && (! $decimal_code == 189)) {
+ print "unknown Streamtype $pes_header{stream_id}, decimal $decimal_code ignoring\n" if $self->{debug};
+ $offset += $pes_header{PES_packet_length} + 6;
+ next;
+ }
+ # MPEG2 Audio and Video must have an extended Header as well as AC3
+ $pes_header{payload_start} = (9 + $pes_header{header_length});
+ # We check whether a TimeStamp is present:
+ $pes_header{PTS_DTS_flags} = substr($pes_header{eflags2}, 0, 2);
+
+ #FIXME: overflow of PTS not checked
+ if (($pes_header{PTS_DTS_flags} eq "10") || ($pes_header{PTS_DTS_flags} eq "11")) {
+ $pes_header{PTS_value_bits} = substr($pes_header{PTS_raw_bits},4,3) . substr($pes_header{PTS_raw_bits},8,15) . substr($pes_header{PTS_raw_bits},24,15);
+ # decode the timestamp
+ $pes_header{PTS_decimal} = oct("0b" . substr($pes_header{PTS_value_bits},1));
+ $pes_header{PTS_decimal} += 4294967296 if (substr($pes_header{PTS_value_bits},0,1) == 1);
+ }
+ else {
+ $pes_header{PTS_decimal} = 0;
+ }
+ $pes_header{data_align} = substr($pes_header{eflags1},6,1);
+
+ if ((($offset + $pes_header{PES_packet_length} + 150) > (length($buffer))) && ($self->{EOF} == 0)) {
+ my $helpbuffer = substr($buffer, $offset);
+ $buffer = $helpbuffer . ${ readNextChunk($self) };
+ $offset = 0;
+ }
+
+ my $packet = substr($buffer, $offset, $pes_header{PES_packet_length} + 6);
+ $offset += $pes_header{PES_packet_length} + 6;
+ $packetcounter++;
+ $lengthcounter += ($pes_header{PES_packet_length} + 6);
+
+
+ if ($self->{ignore_streams}{$pes_header{stream_id}}) {
+ print "ignoring packet for stream $pes_header{stream_id}\n" if $self->{debug};
+ next;
+ }
+ if (!($self->{streams}{$pes_header{stream_id}})) {
+ print "\nNew Stream with id $pes_header{stream_id}. Ignoring the stream\n" if (! $self->{audio_only});
+ $self->{ignore_streams}{$pes_header{stream_id}} = 1;
+ next;
+ }
+ if ($self->{dump_packets}) {
+ print "Dumping packet of stream $pes_header{stream_id} ($self->{dump_packets} to dump left)\n";
+ my $DUMPFH;
+ if (! $self->{"$pes_header{stream_id}.pes_dump"}) {
+ open $DUMPFH, ">$pes_header{stream_id}.pes_dump" || die "Can not open dumpfile: $!\n";
+ binmode $DUMPFH;
+ $self->{"$pes_header{stream_id}.pes_dump"} = $DUMPFH;
+ }
+ $DUMPFH = $self->{"$pes_header{stream_id}.pes_dump"};
+ print $DUMPFH $packet;
+ if ($self->{dump_packets}-- == 1) {
+ exit;
+ }
+ next;
+ }
+ &{$self->{streams}{$pes_header{stream_id}}->{check_frames}} (
+ $self->{streams}{$pes_header{stream_id}},
+ substr($packet,$pes_header{payload_start}),
+ \%pes_header
+ );
+ }
+
+ print "\n $packetcounter PES packets processed\n";
+
+
+ if (! $self->{audio_only}) {
+ $self->{streams}{$self->{masterstream}}->final_flush($self->{streams}{$self->{masterstream}});
+ }
+ foreach (keys (%{$self->{streams}})) {
+ if ($self->{ignore_streams}{$_}) {next}
+ if (! ($self->{masterstream} eq $_)) {
+ print "Final flush for stream $_\n"if $self->{debug};
+ $self->{streams}{$_}->final_flush($self->{streams}{$_});
+ }
+ my $seconds = $self->{streams}{$_}{frames_written} * $self->{streams}{$_}{frame_ticks} / 90000; #/
+ print "$self->{streams}{$_}{frames_written} frames written for stream $_ ($seconds sec) \n";
+ next;
+ }
+ print_script_output($self);
+ if (! $self->{script_output}){print_stats($self)};
+
+
+}
+
+sub print_script_output {
+ my $self = shift;
+
+ my %final_properties;
+ my $master = $self->{streams}{$self->{masterstream}};
+ my $max = 0;
+
+ foreach my $movie_property(keys(%{$master->{Unit_analysis_hash}})) {
+ print "found key $movie_property\n" if $self->{debug};
+ foreach my $subkey (keys(%{$master->{Unit_analysis_hash}{$movie_property}})) {
+ print "found subkey $subkey with value $master->{Unit_analysis_hash}{$movie_property}{$subkey}\n" if $self->{debug};
+ if ((!$final_properties{$movie_property}) || $max < $master->{Unit_analysis_hash}{$movie_property}{$subkey}) {
+ $final_properties{$movie_property} = $subkey;
+ $max = $master->{Unit_analysis_hash}{$movie_property}{$subkey};
+ }
+
+ }
+ }
+ $max = 0;
+ if ($self->{streams}{bd}) {
+ my $ac3_audio= $self->{streams}{bd};
+ foreach my $ac3_property(keys(%{$ac3_audio->{Unit_analysis_hash}})) {
+ print "found key $ac3_property\n" if $self->{debug};
+ foreach my $subkey (keys(%{$ac3_audio->{Unit_analysis_hash}{$ac3_property}})) {
+ print "found subkey $subkey with value $ac3_audio->{Unit_analysis_hash}{$ac3_property}{$subkey}\n" if $self->{debug};
+ if ((!$final_properties{$ac3_property}) || $max < $ac3_audio->{Unit_analysis_hash}{$ac3_property}{$subkey}) {
+ $final_properties{$ac3_property} = $subkey;
+ $max = $ac3_audio->{Unit_analysis_hash}{$ac3_property}{$subkey};
+ }
+ }
+ }
+ }
+
+
+ foreach (keys(%final_properties)) {
+ print "Property $_ is $final_properties{$_}\n" if $self->{debug};
+ $self->{streams}{$self->{masterstream}}->{$_} = $final_properties{$_};
+ }
+ if (! $self->{script_output}) {
+ return
+ }
+
+
+ print "*" x 45 ."\n";
+ foreach (keys(%{$self->{streams}})) {
+ print "$self->{streams}{$_}{streamtype} for stream $_\n" if $self->{debug};
+ if ($self->{ignore_streams}{$_}) {
+ print "Ignoring stream $_\n" if $self->{debug};
+ next;
+ }
+
+ if (($self->{streams}{$_}{streamtype} =~ /audio/) || ($self->{streams}{$_}{streamtype} =~ /Layer/)) {
+
+ my $seconds = $self->{streams}{$_}->{frames_written} * $self->{streams}{$_}->{frame_ticks} / 90000; #/
+ print "$_" . "_Audio_stream=yes\n";
+ print "$_" . "_Audio_type=$self->{streams}{$_}{streamtype}\n";
+ print "$_" . "_Sample_frequency=$self->{streams}{$_}->{freq}\n"; # only for Audio
+ print "$_" . "_Bitrate=$self->{streams}{$_}->{bitrate}\n"; # only for Audio
+ print "$_" . "_Mode=$self->{streams}{$_}->{mode}\n"; # only for Audio
+ print "$_" . "_Copyright=$self->{streams}{$_}->{copyright}\n" if $self->{streams}{$_}->{copyright}; # only for Audio
+ print "$_" . "_Bytes_per_frame=$self->{streams}{$_}->{frame_bytes}\n"; # only for Audio
+ print "$_" . "_Ticks_per_frame=$self->{streams}{$_}->{frame_ticks}\n"; # one tick is 1 / 90000 of a second
+ print "$_" . "_Total_frames=$self->{streams}{$_}->{frames_written}\n";
+ print "$_" . "_Total_time=$seconds\n\n";
+
+ }
+ if ($self->{streams}{$_}{streamtype} eq "video") {
+
+ my $seconds = $self->{streams}{$_}->{frames_written} * $self->{streams}{$_}->{frame_ticks} / 90000; #/
+ print "$_" . "_Video_stream=yes\n";
+ print "$_" . "_Aspect_ratio=$self->{streams}{$_}->{aspect_ratio}\n";
+ print "$_" . "_Horizontal_size=$self->{streams}{$_}->{horizontal_size}\n";
+ print "$_" . "_Vertical_size=$self->{streams}{$_}->{vertical_size}\n";
+ print "$_" . "_Frames_per_Second=$self->{streams}{$_}->{fps}\n";
+ print "$_" . "_Bitrate=$self->{streams}{$_}{bitrate_value}\n";
+ print "$_" . "_Ticks_per_frame=$self->{streams}{$_}->{frame_ticks}\n";
+ print "$_" . "_Total_frames=$self->{streams}{$_}->{frames_written}\n";
+ print "$_" . "_Total_time=$seconds\n\n";
+
+ }
+ }
+}
+
+sub init_PES_stream {
+ my $self = shift;
+ my $limit = shift;
+ my $offset = 0; # stores the position we are at in the current buffer
+ my $plength = 0;
+ $self->{IFH} = undef; # Stores the filehandle for the current file
+ $self->{EOF} = 0; # All files processed ?
+ $self->{total_input} = 0;
+ my %pes_header;
+ $pes_header{packet_length} = 0;
+ my $head_tmpl = 'H6 H2 n1 B8 B8 C1 B40';
+
+
+
+ my $packetcounter = 0;
+
+ print "Initialising and analysing the streams....\n";
+ my @save_file_list = @{$self->{files}};
+
+ my $buffer = ${ readNextChunk($self) }; # First we read the first chunk of data
+
+ while ((($self->{EOF} eq 0 || $offset + $pes_header{packet_length} + 1) < (length($buffer))) && ($packetcounter < 2000)) {
+ ( $pes_header{startcode}, $pes_header{stream_id},
+ $pes_header{PES_packet_length}, $pes_header{eflags1},
+ $pes_header{eflags2}, $pes_header{header_length},
+ $pes_header{PTS_raw_bits})
+ = unpack ( $head_tmpl, substr($buffer, $offset, 12) );
+
+ $pes_header{payload_start} = (9 + $pes_header{header_length});
+ # there are at leat six bytes of header at the beginning of a PES packet
+ if ( $pes_header{startcode} ne "000001") {
+ print "No 0x000001 found at current packet, searching for next Packet start\n";
+ $offset = find_next_pes_packet($self, $buffer, $offset);
+ if ($offset == -1) {$kill_me = "all"}
+ next;
+ }
+ # We check whether a TimeStamp is present:
+ $pes_header{PTS_DTS_flags} = substr($pes_header{eflags2}, 0, 2);
+ if (($pes_header{PTS_DTS_flags} eq "10") || ($pes_header{PTS_DTS_flags} eq "11")) {
+ $pes_header{PTS_value_bits} = substr($pes_header{PTS_raw_bits},4,3) . substr($pes_header{PTS_raw_bits},8,15) . substr($pes_header{PTS_raw_bits},24,15);
+ # decode the timestamp
+ $pes_header{PTS_decimal} = unpack("N", (pack ("B32", substr($pes_header{PTS_value_bits},1))));
+ $pes_header{PTS_decimal} += 4294967296 if (substr($pes_header{PTS_value_bits},0,1) == 1);
+ }
+ else {
+ $pes_header{PTS_decimal} = 0;
+ }
+ if ((($offset + $pes_header{PES_packet_length} + 150) > (length($buffer))) && ($self->{EOF} == 0)) {
+ my $helpbuffer = substr($buffer, $offset);
+ $buffer = $helpbuffer . ${ readNextChunk($self) };
+ $offset = 0;
+ }
+
+ my $packet = substr($buffer, $offset, ($pes_header{PES_packet_length} + 6));
+ $offset += $pes_header{PES_packet_length} + 6;
+ $packetcounter++;
+
+ #print "analysed the first $packetcounter packets...\n" if $self->{debug};
+ if (!$self->{streams}{$pes_header{stream_id}}) {
+ if ($self->{ignore_streams}{$pes_header{stream_id}}){
+ next;
+ }
+
+ my $decimal_code = hex($pes_header{stream_id});
+ if ((223 < $decimal_code ) && (240 > $decimal_code)){
+ if ($self->{audio_only}) {
+ $self->{ingnore_streams}{$pes_header{stream_id}} = 1;
+ next;
+ }
+
+ if ($self->{masterstream}){
+ print "Video stream already defined, but found stream $pes_header{stream_id} in addition. Ingnoring stream $pes_header{stream_id}\n";
+ $self->{ingnore_streams}{$pes_header{stream_id}} = 1;
+ next;
+ }
+ else {
+ $self->{streams}{$pes_header{stream_id}} = MPEGSTREAM->new
+ (
+ streamcode => $pes_header{stream_id},
+ outputpath => $self->{outputpath},
+ masterstream => $pes_header{stream_id},
+ debug => $self->{debug},
+ dump_payload => $self->{dump_payload},
+ show_drift => $self->{show_drift},
+ dump_buffer => $self->{dump_buffer},
+ );
+ $self->{masterstream} = $pes_header{stream_id};
+ print "\nCreated new MPEG stream object for stream $pes_header{stream_id}, master video stream\n";
+ }
+ }
+ else {
+ $self->{streams}{$pes_header{stream_id}} = MPEGSTREAM->new
+ (
+ streamcode => $pes_header{stream_id},
+ masterstream => $self->{masterstream},
+ outputpath => $self->{outputpath},
+ debug => $self->{debug},
+ dump_payload => $self->{dump_payload},
+ audio_only => $self->{audio_only},
+ show_drift => $self->{show_drift},
+ dump_buffer => $self->{dump_buffer},
+ );
+ print "\nCreated new MPEG stream object for stream $pes_header{stream_id} \n";
+ }
+
+ }
+
+ if (!($self->{streams}{$pes_header{stream_id}}{check_frames}))
+ {
+ print "sending frame number $packetcounter for analysis of stream $pes_header{stream_id}\n" if $self->{debug};
+ init_stream($self->{streams}{$pes_header{stream_id}}, substr($packet, $pes_header{payload_start}), \%pes_header);
+ }
+ }
+
+
+ @{$self->{files}} = @save_file_list;
+ print "analysed the first $packetcounter packets...\n";
+ my $IFH = $self->{IFH};
+ close $IFH;
+
+ if (!($self->{masterstream}))
+ {
+ die "No video stream could be found within the first 2000 packets, exiting\n" if (! $self->{ignore_streams});
+ }
+
+ foreach (keys(%{$self->{streams}}))
+ {
+ $self->{streams}{$_}{masterstream} = $self->{masterstream};
+
+ if ((!($self->{streams}{$_}{check_frames})) && (!($self->{ignore_streams}{$_})))
+ {
+ print "The contents of stream $_ could not be identified, the stream will be skipped!\n";
+ $self->{ignore_streams}{$_} = 1;
+ }
+ }
+
+ print_stats($self) if $self->{debug};
+ return;
+}
+
+
+
+sub get_aspect_ratio {
+ my $self = shift;
+ return $self->{streams}{$self->{masterstream}}->{aspect_ratio};
+}
+
+sub readNextChunk {
+ my $self = shift;
+ my $IFH = $self->{IFH};
+
+
+ if (!($self->{IFH})) {
+ my $firstfile = shift @{$self->{files}};
+ print "trying to open $firstfile\n" if $self->{debug};
+ open $IFH, $firstfile || die "$! happend while opening $firstfile\n";
+ binmode $IFH;
+ $self->{IFH} = $IFH;
+ }
+
+ my $rbuffer;
+ my $rbytes = sysread $IFH, $rbuffer, 10000000, 0;
+
+ if ($rbytes != 10000000 ) {
+ if ((scalar(@{$self->{files}}))== 0) {
+ print "\nall Input files processed\n";
+ print "EOF reached\n";
+ $self->{EOF} = 1;
+ }
+ else {
+ my $nextf = shift @{$self->{files}};
+ print "$nextf is the next file\n";
+ close $IFH;
+ open $IFH, $nextf || die "$! happend while opening $nextf\n";
+ binmode $IFH;
+ $self->{IFH} = $IFH;
+ my $helpbuffer;
+ my $helprbytes = sysread $self->{IFH}, $helpbuffer, (10000000 - length($rbuffer)), 0;
+ $rbuffer .= $helpbuffer;
+ $rbytes += $helprbytes;
+ }
+ }
+ $self->{total_input} += $rbytes;
+ my $status = sprintf ("%4d", ($self->{total_input} / 1000000));#/
+ $bytes_read = $self->{total_input};
+ print "\r$status Mbytes of " . int($total_size/1000000) . " read"; #if $self->{debug}; f
+ return \$rbuffer;
+}
+
+
+
+sub get_silent_frame {
+ my $self = shift;
+ my %info = % { shift @_ };
+ my $frame;
+ my $function;
+
+ if ($info{streamtype} eq "MPEG1_Layer_2") {
+ $function = "get_$info{mode}_" . "$info{bitrate}";
+ }
+ elsif ($info{streamtype} eq "AC3_Audio") {
+ $info{mode} =~ s/\//_/;
+ $function = "get_ac3_$info{mode}_" . "$info{bitrate}";
+ }
+ else {
+ print "Can not understand Format $info{streamtype}, no silent frame available\n" if $self->{debug}; return -1;
+ }
+
+ my $uu_frame = eval $function ;
+ if (! $uu_frame) {print "No silent frame available for $function\n" if $self->{debug}; return -1}
+ foreach (split "\n", $uu_frame) {
+ last if /^end/;
+ next if /[a-z]/;
+ next unless int((((ord() - 32) & 077) + 2) / 3) == int(length() / 4);
+ $frame .= unpack "u", $_;
+ }
+
+ return $frame;
+}
+
+
+
+sub get_mono_32000 {
+my $frame = <<'End_FRAME';
+M__T4P!%)I&JJOOOOOOOOFM?/EL?/FM:^?+8^?-:U\^6Q\^:UKY\MCY\UK7SY
+M;'SYK6OGRV/GS6M?/EL?/FM:^?+8^?-:U\^6Q\^:UKY\MCY\UK7SY;'SYK6O
+&GRV/GS0`
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_48000 {
+my $frame = <<'End_FRAME';
+M__TDP#-R-NJJOOOOOOOOEL6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6
+MQ;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L
+M6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'=W=UL6Q;%L6Q;'
+)=W=UL6Q;````
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_56000 {
+
+my $frame = <<'End_FRAME';
+M__TTP!(C,R(B$D``````JJJJOOOOOOOOOOOOOFMMMMMMMM\^?/FM:UMMMMMM
+MMOGSY\UK6MMMMMMMM\^?/FM:UMMMMMMMOGSY\UK6MMMMMMMM\^?/FM:UMMMM
+MMMMOGSY\UK6MMMMMMMM\^?/FM:UMMMMMMMOGSY\UK6MMMMMMMM\^?/FM:UMM
+AMMMMMOGSY\UK6MMMMMMMM\^?/FM:UMMMMMMMOGSY\UK0
+end
+End_FRAME
+
+return $frame;
+}
+
+sub get_mono_64000 {
+my $frame = <<'End_FRAME';
+M__U$P"(D1$,B)$``````JJJJOOOOOOOOOOOOOFVVVVMBV+8MC;?/GSYK;;;;
+M6Q;%L6QMOGSY\UMMMMK8MBV+8VWSY\^:VVVVUL6Q;%L;;Y\^?-;;;;:V+8MB
+MV-M\^?/FMMMMM;%L6Q;&V^?/GS6VVVVMBV+8MC;?/GSYK;;;;6Q;%L6QMOGS
+MY\UMMMMK8MBV+8VWSY\^:VVVVUL6Q;%L;;Y\^?-;;;;:V+8MBV-M\^?/FMMM
+,MM;%L6Q;&V^?/GS0
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_mono_80000 {
+my $frame = <<'End_FRAME';
+M__U4P"(U541#-HD`````JJJJJ^^^^^^^^^^^^^^^;;;=W=W=W=W6Q;%L;;;;
+M;YK6VVW=W=W=W=UL6Q;&VVVV^:UMMMW=W=W=W=;%L6QMMMMOFM;;;=W=W=W=
+MW6Q;%L;;;;;YK6VVW=W=W=W=UL6Q;&VVVV^:UMMMW=W=W=W=;%L6QMMMMOFM
+M;;;=W=W=W=W6Q;%L;;;;;YK6VVW=W=W=W=UL6Q;&VVVV^:UMMMW=W=W=W=;%
+ML6QMMMMOFM;;;=W=W=W=W6Q;%L;;;;;YK6VVW=W=W=W=UL6Q;&VVVV^:UMMM
+/W=W=W=W=;%L6QMMMMOFM
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_96000 {
+my $frame = <<'End_FRAME';
+M__UDP#,V9E5$2-$@````JJJJJOOOOOOOOOOOOOOOOG=W=W=WO>][WO>[N[NM
+MBV+8MC;?-:[N[N[N][WO>][W=W=UL6Q;%L;;YK7=W=W=WO>][WO>[N[NMBV+
+M8MC;?-:[N[N[N][WO>][W=W=UL6Q;%L;;YK7=W=W=WO>][WO>[N[NMBV+8MC
+M;?-:[N[N[N][WO>][W=W=UL6Q;%L;;YK7=W=W=WO>][WO>[N[NMBV+8MC;?-
+M:[N[N[N][WO>][W=W=UL6Q;%L;;YK7=W=W=WO>][WO>[N[NMBV+8MC;?-:[N
+M[N[N][WO>][W=W=UL6Q;%L;;YK7=W=W=WO>][WO>[N[NMBV+8MC;?-:[N[N[
+2N][WO>][W=W=UL6Q;%L;;YK0
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_112000 {
+my $frame = <<'End_FRAME';
+M__UTP#-&=F956R(D````JJJJJK[[[[[[[[[[[[[[[[YW=W=[WO>]]]][WO>]
+M[WN[N[N[N[K8MCYK7=W=WO>][WWWWO>][WO>[N[N[N[NMBV/FM=W=W>][WO?
+M??>][WO>][N[N[N[NZV+8^:UW=W=[WO>]]]][WO>][WN[N[N[N[K8MCYK7=W
+M=WO>][WWWWO>][WO>[N[N[N[NMBV/FM=W=W>][WO???>][WO>][N[N[N[NZV
+M+8^:UW=W=[WO>]]]][WO>][WN[N[N[N[K8MCYK7=W=WO>][WWWWO>][WO>[N
+M[N[N[NMBV/FM=W=W>][WO???>][WO>][N[N[N[NZV+8^:UW=W=[WO>]]]][W
+MO>][WN[N[N[N[K8MCYK7=W=WO>][WWWWO>][WO>[N[N[N[NMBV/FM=W=W>][
+5WO???>][WO>][N[N[N[NZV+8^:T`
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_128000 {
+my $frame = <<'End_FRAME';
+M__V$P$17=V9F:V-D````JJJJJK[[[[[[[[[[[[[[[[Y[WO>]]]]]]]]]]]]]
+M[WO>][WO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVV
+MU[WO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO
+M>][WO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[W
+MO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][
+MWO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[WO>]
+M]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][WO>
+M][WN[N[K8VVVU[WO>]]]]]]]]]]]]][WO>][WO>][WN[N[K8VVVU[WO>]]]]
+8]]]]]]]]][WO>][WO>][WN[N[K8VVVT`
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_160000 {
+my $frame = <<'End_FRAME';
+M__V4P%5HF(=W?;60@```JJJJJJ^^^^^^^^^^^^^^^^^^???????OW[]^_?W]
+M_?OW[]^_????????????>][WO>][NZV+8U]]]]]]^_?OW[]_?W]^_?OW[]]]
+M]]]]]]]]]][WO>][WN[K8MC7WWWWWW[]^_?OW]_?W[]^_?OWWWWWWWWWWWWW
+MO>][WO>[NMBV-???????OW[]^_?W]_?OW[]^_????????????>][WO>][NZV
+M+8U]]]]]]^_?OW[]_?W]^_?OW[]]]]]]]]]]]]][WO>][WN[K8MC7WWWWWW[
+M]^_?OW]_?W[]^_?OWWWWWWWWWWWWWO>][WO>[NMBV-???????OW[]^_?W]_?
+MOW[]^_????????????>][WO>][NZV+8U]]]]]]^_?OW[]_?W]^_?OW[]]]]]
+M]]]]]]]][WO>][WN[K8MC7WWWWWW[]^_?OW]_?W[]^_?OWWWWWWWWWWWWWO>
+M][WO>[NMBV-???????OW[]^_?W]_?OW[]^_????????????>][WO>][NZV+8
+MU]]]]]]^_?OW[]_?W]^_?OW[]]]]]]]]]]]]][WO>][WN[K8MC7WWWWWW[]^
+>_?OW]_?W[]^_?OWWWWWWWWWWWWWO>][WO>[NMBV-
+end
+End_FRAME
+return $frame;
+}
+
+sub get_mono_192000 {
+my $frame = <<'End_FRAME';
+M__VDP%9IF8B(?[:Q$```JJJJJJOOOOOOOOOOOOOOOOOOOGWWW[]^_?OW]_?W
+M]_?W]_?W[]^_?OW[]^_?OWWWW__?_]__WO>][WO>[NMCYK[[[]^_?OW[^_O[
+M^_O[^_O[]^_?OW[]^_?OW[[[[__O_^__[WO>][WO=W6Q\U]]]^_?OW[]_?W]
+M_?W]_?W]^_?OW[]^_?OW[]]]]__W__?_][WO>][WN[K8^:^^^_?OW[]^_O[^
+M_O[^_O[^_?OW[]^_?OW[]^^^^__[__O_^][WO>][W=UL?-????OW[]^_?W]_
+M?W]_?W]_?OW[]^_?OW[]^_????_]__W__>][WO>][NZV/FOOOOW[]^_?O[^_
+MO[^_O[^_OW[]^_?OW[]^_?OOOO_^__[__O>][WO>]W=;'S7WWW[]^_?OW]_?
+MW]_?W]_?W[]^_?OW[]^_?OWWWW__?_]__WO>][WO>[NMCYK[[[]^_?OW[^_O
+M[^_O[^_O[]^_?OW[]^_?OW[[[[__O_^__[WO>][WO=W6Q\U]]]^_?OW[]_?W
+M]_?W]_?W]^_?OW[]^_?OW[]]]]__W__?_][WO>][WN[K8^:^^^_?OW[]^_O[
+M^_O[^_O[^_?OW[]^_?OW[]^^^^__[__O_^][WO>][W=UL?-????OW[]^_?W]
+M_?W]_?W]_?OW[]^_?OW[]^_????_]__W__>][WO>][NZV/FOOOOW[]^_?O[^
+D_O[^_O[^_OW[]^_?OW[]^_?OOOO_^__[__O>][WO>]W=;'S0
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_stereo_48000 {
+my $frame = <<'End_FRAME';
+M__TD```1)22)))JJJJK[[[[[[[[[[[[[YK6M?/GSYK6M:UK6M?/GSYK6M:UK
+M6M?/GSYK6M:UK6M?/GSYK6M:UK6M?/GSYK6M:UK6M?/GSYK6M:UK6M?/GSYK
+M6M:UK6M?/GSYK6M:UK6M?/GSYK6M:UK6M?/GSYK6M:UK6M?/GSYK6M:UK6M?
+)/GSYK6M:T```
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_56000{
+my $frame = <<'End_FRAME';
+M__TT`!$11222))JJJJJOOOOOOOOOOOOOOOOFM:U\U\^?/GSYK6M:UK7S7SY\
+M^?/FM:UK6M?-?/GSY\^:UK6M:U\U\^?/GSYK6M:UK7S7SY\^?/FM:UK6M?-?
+M/GSY\^:UK6M:U\U\^?/GSYK6M:UK7S7SY\^?/FM:UK6M?-?/GSY\^:UK6M:U
+A\U\^?/GSYK6M:UK7S7SY\^?/FM:UK6M?-?/GSY\^:UK0
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_64000 {
+my $frame = <<'End_FRAME';
+M__U$`!$12;;21)JJJJJOOOOOOOOOOOOOOOOFM:U\^6Q;%L6Q\^?-:UK6M?/E
+ML6Q;%L?/GS6M:UK7SY;%L6Q;'SY\UK6M:U\^6Q;%L6Q\^?-:UK6M?/EL6Q;%
+ML?/GS6M:UK7SY;%L6Q;'SY\UK6M:U\^6Q;%L6Q\^?-:UK6M?/EL6Q;%L?/GS
+M6M:UK7SY;%L6Q;'SY\UK6M:U\^6Q;%L6Q\^?-:UK6M?/EL6Q;%L?/GS6M:UK
+,7SY;%L6Q;'SY\UK0
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_80000{
+my $frame = <<'End_FRAME';
+M__U4`"(B;;;;;2JJJJJOOOOOOOOOOOOOOOOGSY\^6Q;%L6Q;%L6Q;%L6Q\^?
+M/GSY;%L6Q;%L6Q;%L6Q;'SY\^?/EL6Q;%L6Q;%L6Q;%L?/GSY\^6Q;%L6Q;%
+ML6Q;%L6Q\^?/GSY;%L6Q;%L6Q;%L6Q;'SY\^?/EL6Q;%L6Q;%L6Q;%L?/GSY
+M\^6Q;%L6Q;%L6Q;%L6Q\^?/GSY;%L6Q;%L6Q;%L6Q;'SY\^?/EL6Q;%L6Q;%
+ML6Q;%L?/GSY\^6Q;%L6Q;%L6Q;%L6Q\^?/GSY;%L6Q;%L6Q;%L6Q;'SY\^?/
+/EL6Q;%L6Q;%L6Q;%L?/@
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_96000{
+my $frame = <<'End_FRAME';
+M__UD`#,SCDDD;;JJJJJOOOOOOOOOOOOOOOOEL6Q;%L=W6QW=W=W=W=W=W=;%
+ML6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6
+MQ;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;
+M%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L
+M6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q
+M;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%L=W6QW=W=W=W=W=W=;%L6Q;%L6Q;%
+2L=W6QW=W=W=W=W=W=;%L6Q;`
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_112000{
+my $frame = <<'End_FRAME';
+M__UT`!$B(C-#,S(B(B(1))````````````"JJJJJJJJOOOOOOOOOOOOOOOOO
+MOOOOOOOOOFM;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;;;;;;;:V-MMMMMO
+MGSY\^?/FM:UK6M;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;;;;;;;:V-MMM
+MMMOGSY\^?/FM:UK6M;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;;;;;;;:V-
+MMMMMMOGSY\^?/FM:UK6M;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;;;;;;;
+M:V-MMMMMOGSY\^?/FM:UK6M;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;;;;
+M;;;:V-MMMMMOGSY\^?/FM:UK6M;;;;;;;;;6QMMMMMM\^?/GSY\UK6M:UK;;
+5;;;;;;:V-MMMMMOGSY\^?/FM:UK0
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_128000{
+my $frame = <<'End_FRAME';
+M__V$`"(B(D1$1#,R(B(B)))```````````"JJJJJJJJJ^^^^^^^^^^^^^^^^
+M^^^^^^^^^^^^;;;;;;;;;6Q;%L6Q;%L;;;;;Y\^?/GSYK6M:UMMMMMMMMM;%
+ML6Q;%L6QMMMMOGSY\^?/FM:UK6VVVVVVVVUL6Q;%L6Q;&VVVV^?/GSY\^:UK
+M6M;;;;;;;;;6Q;%L6Q;%L;;;;;Y\^?/GSYK6M:UMMMMMMMMM;%L6Q;%L6QMM
+MMMOGSY\^?/FM:UK6VVVVVVVVUL6Q;%L6Q;&VVVV^?/GSY\^:UK6M;;;;;;;;
+M;6Q;%L6Q;%L;;;;;Y\^?/GSYK6M:UMMMMMMMMM;%L6Q;%L6QMMMMOGSY\^?/
+MFM:UK6VVVVVVVVUL6Q;%L6Q;&VVVV^?/GSY\^:UK6M;;;;;;;;;6Q;%L6Q;%
+ML;;;;;Y\^?/GSYK6M:UMMMMMMMMM;%L6Q;%L6QMMMMOGSY\^?/FM:UK6VVVV
+8VVVVUL6Q;%L6Q;&VVVV^?/GSY\^:UK6M
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_stereo_160000{
+my $frame = <<'End_FRAME';
+M__V4`"(R,U55541$0S,S;2))``````````"JJJJJJJJJK[[[[[[[[[[[[[[[
+M[[[[[[[[[[[[[[YMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM
+M:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[N[N
+M[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[N[N[N[N[N[N[N[K8
+MMBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;
+M;;;;?/FM:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMM
+MW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[N[N[N[N[
+MN[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8
+MMC;;;;;;;;;;?/FM:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?
+M/FM:UMMMW=MN[N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:UMMMW=MN[
+>N[N[N[N[N[N[N[K8MBV+8MC;;;;;;;;;;?/FM:T`
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_192000{
+my $frame = <<'End_FRAME';
+M__VD`#,S,V9F9E551$1$DC21)`````````"JJJJJJJJJJOOOOOOOOOOOOOOO
+MOOOOOOOOOOOOOOOOOG=W=W=W=W=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6
+MQ;%L6Q;&V^?/FM:[N[N[N[N[N[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8
+MMBV+8VWSY\UK7=W=W=W=W=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6Q;%L6
+MQ;&V^?/FM:[N[N[N[N[N[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8MBV+8
+MVWSY\UK7=W=W=W=W=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6Q;%L6Q;&V^
+M?/FM:[N[N[N[N[N[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8MBV+8VWSY\
+MUK7=W=W=W=W=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6Q;%L6Q;&V^?/FM:
+M[N[N[N[N[N[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8MBV+8VWSY\UK7=W
+M=W=W=W=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6Q;%L6Q;&V^?/FM:[N[N[
+MN[N[N[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8MBV+8VWSY\UK7=W=W=W=
+MW=W=WO>][WO>][WO>][W=W=W=W=UL6Q;%L6Q;%L6Q;&V^?/FM:[N[N[N[N[N
+D[N][WO>][WO>][WO>[N[N[N[NMBV+8MBV+8MBV+8VWSY\UK0
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_stereo_224000{
+my $frame = <<'End_FRAME';
+M__VT`#,S1'=W9F9F5555MD;2))````````"JJJJJJJJJJJ^^^^^^^^^^^^^^
+M^^^^^^^^^^^^^^^^^^^^=W=W=W=W>][WO????????????>][WO>][WO>][WO
+M=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO????????????>][WO>]
+M[WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO???????????
+M?>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO???
+M?????????>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W
+M>][WO????????????>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M
+M=W=W=W=W>][WO????????????>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&
+MVVWSYK6M=W=W=W=W>][WO????????????>][WO>][WO>][WO=W=W=W=W=W=W
+M=W=W6Q;&VVWSYK6M=W=W=W=W>][WO????????????>][WO>][WO>][WO=W=W
+M=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO????????????>][WO>][WO>
+M][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO????????????>][
+MWO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][WO???????
+M?????>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M=W=W=W=W>][W
+JO????????????>][WO>][WO>][WO=W=W=W=W=W=W=W=W6Q;&VVWSYK6M
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_256000{
+my $frame = <<'End_FRAME';
+M__W$`$1$57=W=V9F9F9FVMD;:)````````"JJJJJJJJJJJ^^^^^^^^^^^^^^
+M^^^^^^^^^^^^^^^^^^^^>][WO>][WO????????????????????????>][WO>
+M][WO>][WO>][WO>][WO>][WO=W=W6Q;&VVVV^:U[WO>][WO>]]]]]]]]]]]]
+M]]]]]]]]]]]]][WO>][WO>][WO>][WO>][WO>][WO>]W=W=;%L;;;;;YK7O>
+M][WO>][WWWWWWWWWWWWWWWWWWWWWWWWWO>][WO>][WO>][WO>][WO>][WO>]
+M[W=W=UL6QMMMMOFM>][WO>][WO????????????????????????>][WO>][WO
+M>][WO>][WO>][WO>][WO=W=W6Q;&VVVV^:U[WO>][WO>]]]]]]]]]]]]]]]]
+M]]]]]]]]][WO>][WO>][WO>][WO>][WO>][WO>]W=W=;%L;;;;;YK7O>][WO
+M>][WWWWWWWWWWWWWWWWWWWWWWWWWO>][WO>][WO>][WO>][WO>][WO>][W=W
+M=UL6QMMMMOFM>][WO>][WO????????????????????????>][WO>][WO>][W
+MO>][WO>][WO>][WO=W=W6Q;&VVVV^:U[WO>][WO>]]]]]]]]]]]]]]]]]]]]
+M]]]]][WO>][WO>][WO>][WO>][WO>][WO>]W=W=;%L;;;;;YK7O>][WO>][W
+MWWWWWWWWWWWWWWWWWWWWWWWWO>][WO>][WO>][WO>][WO>][WO>][W=W=UL6
+MQMMMMOFM>][WO>][WO????????????????????????>][WO>][WO>][WO>][
+MWO>][WO>][WO=W=W6Q;&VVVV^:U[WO>][WO>]]]]]]]]]]]]]]]]]]]]]]]]
+M][WO>][WO>][WO>][WO>][WO>][WO>]W=W=;%L;;;;;YK7O>][WO>][WWWWW
+MWWWWWWWWWWWWWWWWWWWWO>][WO>][WO>][WO>][WO>][WO>][W=W=UL6QMMM
+#MOFM
+end
+End_FRAME
+return $frame;
+}
+
+sub get_stereo_320000{
+my $frame = <<'End_FRAME';
+M__W4`%559IB9B(AW=W=WVVVMDC)```````"JJJJJJJJJJJK[[[[[[[[[[[[[
+M[[[[[[[[[[[[[[[[[[[[[[Y]]]]]]]]]]]]^_?OW[]_?W]^_?O[^_O[^_OW[
+M]^_?OW[]^_?OOOOOOOOOOOOOOOOOOOOOOOOO>][WO>][WO>][WN[N[K8MBV-
+MMK7WWWWWWWWWWWW[]^_?OW]_?W[]^_O[^_O[^_?OW[]^_?OW[]^^^^^^^^^^
+M^^^^^^^^^^^^^^^][WO>][WO>][WO>[N[NMBV+8VVM?????????????OW[]^
+M_?W]_?OW[^_O[^_O[]^_?OW[]^_?OW[[[[[[[[[[[[[[[[[[[[[[[[[WO>][
+MWO>][WO>][N[NZV+8MC;:U]]]]]]]]]]]]^_?OW[]_?W]^_?O[^_O[^_OW[]
+M^_?OW[]^_?OOOOOOOOOOOOOOOOOOOOOOOOO>][WO>][WO>][WN[N[K8MBV-M
+MK7WWWWWWWWWWWW[]^_?OW]_?W[]^_O[^_O[^_?OW[]^_?OW[]^^^^^^^^^^^
+M^^^^^^^^^^^^^^][WO>][WO>][WO>[N[NMBV+8VVM?????????????OW[]^_
+M?W]_?OW[^_O[^_O[]^_?OW[]^_?OW[[[[[[[[[[[[[[[[[[[[[[[[[WO>][W
+MO>][WO>][N[NZV+8MC;:U]]]]]]]]]]]]^_?OW[]_?W]^_?O[^_O[^_OW[]^
+M_?OW[]^_?OOOOOOOOOOOOOOOOOOOOOOOOO>][WO>][WO>][WN[N[K8MBV-MK
+M7WWWWWWWWWWWW[]^_?OW]_?W[]^_O[^_O[^_?OW[]^_?OW[]^^^^^^^^^^^^
+M^^^^^^^^^^^^^][WO>][WO>][WO>[N[NMBV+8VVM?????????????OW[]^_?
+MW]_?OW[^_O[^_O[]^_?OW[]^_?OW[[[[[[[[[[[[[[[[[[[[[[[[[WO>][WO
+M>][WO>][N[NZV+8MC;:U]]]]]]]]]]]]^_?OW[]_?W]^_?O[^_O[^_OW[]^_
+M?OW[]^_?OOOOOOOOOOOOOOOOOOOOOOOOO>][WO>][WO>][WN[N[K8MBV-MK7
+MWWWWWWWWWWWW[]^_?OW]_?W[]^_O[^_O[^_?OW[]^_?OW[]^^^^^^^^^^^^^
+M^^^^^^^^^^^^][WO>][WO>][WO>[N[NMBV+8VVM?????????????OW[]^_?W
+M]_?OW[^_O[^_O[]^_?OW[]^_?OW[[[[[[[[[[[[[[[[[[[[[[[[[WO>][WO>
+/][WO>][N[NZV+8MC;:T`
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_stereo_384000 {
+my $frame = <<'End_FRAME';
+M__WD`%5F9IF9F8B(AW=W_^VML;2```````"JJJJJJJJJJJK[[[[[[[[[[[[[
+M[[[[[[[[[[[[[[[[[[[[[[Y]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]_?W]_
+M?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__O_^__
+M[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]_?W]
+M_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__O_^_
+M_[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]_?W
+M]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__O_^
+M__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]_?
+MW]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__O_
+M^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]_
+M?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__O
+M_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W]
+M_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[__
+MO_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?W
+M]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[_
+M_O_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_?
+MW]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__[
+M__O_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]_
+M?W]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^__
+M[__O_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W]
+M_?W]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^_
+M_[__O_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?W
+M]_?W]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_^
+M__[__O_^__[__O>][WO>]W=W=W=;&VVWSY]]]]]]^_?OW[]^_?OW[]_?W]_?
+MW]_?W]_?W]_?W]_?W]^_?OW[]^_?OW[]^_?OOOOOOOOOOOOOOOO_^__[__O_
+;^__[__O_^__[__O>][WO>]W=W=W=;&VVWSX`
+end
+End_FRAME
+return $frame;
+}
+
+
+sub get_ac3_2_0_448000 {
+my $frame = <<'End_FRAME';
+M"W>KMQY`0W_X2P:@N&'_.KY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^
+M?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^??_.KY\^?/GSY\^?/G
+MSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^
+M?/GSY\^?>4^D($``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````````\>/
+M'CQX\>/'CQX`````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````'CQX\>/
+M'CQX\>/#````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````'CQX\>/'
+MCQX\>/``````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````\>/'CQX\>/'
+MCQX8````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````\>/'CQX\>/'C
+MQX``````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````'CQX\>/'CQX\>/#`
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````'CQX\>/'CQX\>/```
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````\>/'CQX\>/'CQX8`0>`7
+M9',I_X0C2.]?[C,4O>``````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M'CQX\>/'CQX\>/``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````````````````\>/
+M'CQX\>/'CQX8`0X`1GEVD"Z"X6#I04H";@OIC#8Z]Q8X)BNY;28^@```````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````!X\>/'CQX\>/'CP```````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+E```````````````````````````````/'CQX\>/'CQX\>``C>@``
+end
+End_FRAME
+return $frame;
+}
+
+sub get_ac3_3_2_448000 {
+my $frame = <<'End_FRAME';
+M"W>9&!Y`X=_^$L`^_UE_P\/X>'\/#^'A_#P55X^?/GSY\^?/GSY\^?/GSY\^
+M??\ZOGSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?
+M/GW_SJ^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GS
+MY\^??_.KY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^
+M?/GSY]_\ZOGSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/G
+MSY\^?/GW_SJ^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\^?/GSY\
+M^?/GSY\^??_.N4^&)$B1(D2)``````&^;;;;;;;;>/'CN[N[N[N[N[N[N[N[
+MN[QX\;;;?/FM:UK6M:UK6M:UK6M:UK0````&VVVVVVVV\>/'=W=W=W=W=W=W
+M=W=W=WCQXVVV^?-:UK6M:UK6M:UK6M:UK6@````-MMMMMMMMX\>.[N[N[N[N
+M[N[N[N[N[O'CQMMM\^:UK6M:UK6M:UK6M:UK6M`````;YMMMMMMMMX\>.[N[
+MN[N[N[N[N[N[N[O'CQMMM\^:UK6M:UK6M:UK6M:UK6M`````;;;;;;;;;QX\
+M=W=W=W=W=W=W=W=W=W>/'C;;;Y\UK6M:UK6M:UK6M:UK6M:`````V#X`````
+M```!OFVVVVVVVWCQX[N[N[N[N[N[N[N[N[N\>/&VVWSYK6M:UK6M:UK6M:UK
+M6M:T````!MMMMMMMMO'CQW=W=W=W=W=W=W=W=W=X\>-MMOGS6M:UK6M:UK6M
+M:UK6M:UH````#;;;;;;;;>/'CN[N[N[N[N[N[N[N[N[QX\;;;?/FM:UK6M:U
+MK6M:UK6M:UK0````&^;;;;;;;;>/'CN[N[N[N[N[N[N[N[N[QX\;;;?/FM:U
+MK6M:UK6M:UK6M:UK0````&VVVVVVVV\>/'=W=W=W=W=W=W=W=W=WCQXVVV^?
+M-:UK6M:UK6M:UK6M:UK6@````-@^`````````;YMMMMMMMMX\>.[N[N[N[N[
+MN[N[N[N[O'CQMMM\^:UK6M:UK6M:UK6M:UK6M`````;;;;;;;;;QX\=W=W=W
+M=W=W=W=W=W=W>/'C;;;Y\UK6M:UK6M:UK6M:UK6M:`````VVVVVVVVWCQX[N
+M[N[N[N[N[N[N[N[N\>/&VVWSYK6M:UK6M:UK6M:UK6M:T````!OFVVVVVVVW
+MCQX[N[N[N[N[N[N[N[N[N\>/&VVWSYK6M:UK6M:UK6M:UK6M:T````!MMMMM
+MMMMO'CQW=W=W=W=W=W=W=W=W=X\>-MMOGS6M:UK6M:UK6M:UK6M:UH````#8
+M/@````````&^;;;;;;;;>/'CN[N[N[N[N[N[N[N[N[QX\;;;?/FM:UK6M:UK
+M6M:UK6M:UK0````&VVVVVVVV\>/'=W=W=W=W=W=W=W=W=WCQXVVV^?-:UK6M
+M:UK6M:UK6M:UK6@````-MMMMMMMMX\>.[N[N[N[N[N[N[N[N[O'CQMMM\^:U
+MK6M:UK6M:UK6M:UK6M`````;YMMMMMMMMX\>.[N[N[N[N[N[N[N[N[O'CQMM
+MM\^:UK6M:UK6M:UK6M:UK6M`````;;;;;;;;;QX\=W=W=W=W=W=W=W=W=W>/
+M'C;;;Y\UK6M:UK6M:UK6M:UK6M:`````V#X````````!OFVVVVVVVWCQX[N[
+MN[N[N[N[N[N[N[N\>/&VVWSYK6M:UK6M:UK6M:UK6M:T````!MMMMMMMMO'C
+MQW=W=W=W=W=W=W=W=W=X\>-MMOGS6M:UK6M:UK6M:UK6M:UH````#;;;;;;;
+M;>/'CN[N[N[N[N[N[N[N[N[QX\;;;?/FM:UK6M:UK6M:UK6M:UK0````&^;;
+M;;;;;;>/'CN[N[N[N[N[N[N[N[N[QX\;;;?/FM:UK6M:UK6M:UK6M:UK0```
+M`&VVVVVVVV\>/'=W=W=W=W=W=W=W=W=WCQXVVV^?-:UK6M:UK6M:UK6M:UK6
+M@````-@^```!`@!=D<R`````WS;;;;;;;;QX\=W=W=W=W=W=W=W=W=W>/'C;
+M;;Y\UK6M:UK6M:UK6M:UK6M:`````VVVVVVVVWCQX[N[N[N[N[N[N[N[N[N\
+M>/&VVWSYK6M:UK6M:UK6M:UK6M:T````!MMMMMMMMO'CQW=W=W=W=W=W=W=W
+M=W=X\>-MMOGS6M:UK6M:UK6M:UK6M:UH````#?-MMMMMMMO'CQW=W=W=W=W=
+MW=W=W=W=X\>-MMOGS6M:UK6M:UK6M:UK6M:UH````#;;;;;;;;>/'CN[N[N[
+EN[N[N[N[N[N[QX\;;;?/FM:UK6M:UK6M:UK6M:UK0````&PK$```
+end
+End_FRAME
+return $frame;
+}
+
+}