/[amv]/amv.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /amv.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 21 by dpavlin, Sat Jul 21 15:17:48 2007 UTC revision 29 by dpavlin, Sun Aug 19 19:23:03 2007 UTC
# Line 9  Line 9 
9  # http://en.wikipedia.org/wiki/RIFF_(File_format)  # http://en.wikipedia.org/wiki/RIFF_(File_format)
10  # http://www.obrador.com/essentialjpeg/HeaderInfo.htm  # http://www.obrador.com/essentialjpeg/HeaderInfo.htm
11  # http://lists.helixcommunity.org/pipermail/datatype-dev/2005-January/001886.html  # http://lists.helixcommunity.org/pipermail/datatype-dev/2005-January/001886.html
12    # http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
13    # http://wiki.multimedia.cx/index.php?title=IMA_ADPCM
14    
15  use strict;  use strict;
16    
# Line 17  use Carp qw/confess/; Line 19  use Carp qw/confess/;
19  use File::Path;  use File::Path;
20  use Getopt::Long;  use Getopt::Long;
21    
22  my $dump = 0;  my $dump_amv = 0;
23    my $dump_video = 0;
24    my $dump_jpeg = 0;
25    my $dump_audio = 0;
26  my $debug = 0;  my $debug = 0;
27    my $verbose = 0;
28  my $dump_dir = '/tmp/dump/';  my $dump_dir = '/tmp/dump/';
29  my $dump_avi = "dump.avi";  my $dump_avi = "dump.avi";
30  my $no_jpeg_header = 0;  my $no_jpeg_header = 0;
31  my $jpeg_q = 100;  my $jpeg_q = 100;
32    my $jpegtran;
33    
34  GetOptions(  GetOptions(
35          "dump!"                 => \$dump,          "dump-amv!"             => \$dump_amv,
36            "dump-video!"   => \$dump_video,
37            "dump-jpeg!"    => \$dump_jpeg,
38            "dump-audio!"   => \$dump_audio,
39          "debug!"                => \$debug,          "debug!"                => \$debug,
40          "dump-dir=s"    => \$dump_dir,          "dump-dir=s"    => \$dump_dir,
41          "no-jpeg-headers!" => \$no_jpeg_header,          "no-jpeg-headers!" => \$no_jpeg_header,
42            "jpegtran=s"    => \$jpegtran,
43            "verbose!"              => \$verbose,
44  );  );
45    
46  my $path = shift @ARGV || die "usage: $0 movie.amv\n";  my $path = shift @ARGV || die "usage: $0 movie.amv\n";
47    
48    # by default, flip frames
49    $jpegtran = '-flip vertical' unless defined($jpegtran);
50    
51  rmtree $dump_dir if -e $dump_dir;  rmtree $dump_dir if -e $dump_dir;
52  mkpath $dump_dir || die "can't create $dump_dir: $!";  mkpath $dump_dir || die "can't create $dump_dir: $!";
53    
54    $| = 1;
55    
56  open(my $fh, '<', $path) || die "can't open $path: $!";  open(my $fh, '<', $path) || die "can't open $path: $!";
57    
58  # offset in file  # offset in file
# Line 46  my $o = 0; Line 62  my $o = 0;
62  my $d;  my $d;
63    
64  sub hex_dump {  sub hex_dump {
         return unless $dump;  
   
65          my ( $bytes, $offset ) = @_;          my ( $bytes, $offset ) = @_;
66          return unless $bytes;          return unless $bytes;
67    
# Line 89  sub x { Line 103  sub x {
103          my $r_len = length($bytes);          my $r_len = length($bytes);
104          confess "read $r_len bytes, expected $len" if $len != $r_len;          confess "read $r_len bytes, expected $len" if $len != $r_len;
105    
106          hex_dump( $bytes );          if ( $dump_amv ) {
107                    print "## raw $len bytes\n";
108                    hex_dump( $bytes );
109            }
110    
111          if ( $bytes eq 'AMV_END_' ) {          if ( $bytes eq 'AMV_END_' ) {
112                  print "> end of file marker AMV_END_\n" if $dump;                  print "> end of file marker AMV_END_\n" if $dump_video;
113                  $d->{eof}++;                  $d->{eof}++;
114                  return;                  return;
115          }          }
# Line 114  sub next_part { Line 131  sub next_part {
131          if ( $expected_len ) {          if ( $expected_len ) {
132                  confess "expected $expected_len bytes for $part got $len" if $len != $expected_len;                  confess "expected $expected_len bytes for $part got $len" if $len != $expected_len;
133          }          }
134          printf "<< %s - %d 0x%x bytes\n", $part, $len, $len;          printf "## next_part %s - %d 0x%x bytes\n", $part, $len, $len if $debug;
135          x($len) if $skip;          x($len) if $skip;
136          return $len;          return $len;
137  }  }
# Line 132  sub quality { Line 149  sub quality {
149                  $out .= chr($t);                  $out .= chr($t);
150          }          }
151    
152          if ( $dump ) {          if ( $dump_video ) {
153                  print "## quantization table original\n";                  print "## quantization table original\n";
154                  hex_dump( $in );                  hex_dump( $in );
155                  print "## quantization table for $jpeg_q %\n";                  print "## quantization table for $jpeg_q %\n";
# Line 142  sub quality { Line 159  sub quality {
159          return $out;          return $out;
160  }  }
161    
162    sub mp3_frame {
163            my $frame = join('',
164                    # Frame sync (all bits set)
165                    1 x 11 .
166                    # MPEG Audio version ID
167                    # 00 - MPEG Version 2.5 (unofficial)
168                    # 01 - reserved
169                    # 10 - MPEG Version 2 (ISO/IEC 13818-3)
170                    # 11 - MPEG Version 1 (ISO/IEC 11172-3)
171                    1,0,
172                    # Layer description
173                    # 00 - reserved
174                    # 01 - Layer III
175                    # 10 - Layer II
176                    # 11 - Layer I
177                    0,1,
178                    # Protection bit
179                    # 0 - Protected by CRC (16bit crc follows header)
180                    # 1 - Not protected
181                    0,
182                    # Bitrate index
183                    0,0,0,0,
184                    # Sampling rate frequency index (22050)
185                    0,0,
186                    # Padding bit
187                    # 0 - frame is not padded
188                    # 1 - frame is padded with one extra slot
189                    0,
190                    # Private bit
191                    0,
192                    # Channel Mode
193                    # 00 - Stereo
194                    # 01 - Joint stereo (Stereo)
195                    # 10 - Dual channel (2 mono channels)
196                    # 11 - Single channel (Mono)
197                    1,1,
198                    # Mode extension (Only if Joint stereo)
199                    0,0,
200                    # Copyright
201                    0,
202                    # Original
203                    0,
204                    # Emphasis
205                    # 00 - none
206                    # 01 - 50/15 ms
207                    # 10 - reserved
208                    # 11 - CCIT J.17
209                    0,0,
210            );
211    
212            die "frame must have 32 bits, not ", length($frame), " for $frame" if length($frame) != 32;
213    
214            my $bits = pack("b32", $frame);
215    
216            die "packed bits must be 4 bytes, not $bits" if length($bits) != 4;
217    
218            my $t = $frame;
219            $t =~ s/(.{8})/$1 /g;
220            warn "## mp3 frame frame = $t\n";
221    
222            return $bits;
223    }
224    
225  my @subframes;  my @subframes;
226  my $frame_nr = 1;  my $frame_nr = 1;
227    
# Line 157  sub mkjpg { Line 237  sub mkjpg {
237    
238          if ( $#subframes < ( $join_subframes - 1 ) ) {          if ( $#subframes < ( $join_subframes - 1 ) ) {
239                  push @subframes, $data;                  push @subframes, $data;
240                  print "## saved $frame_nr/", $#subframes + 1, " subframe of ", length($data), " bytes\n";                  print "## saved $frame_nr/", $#subframes + 1, " subframe of ", length($data), " bytes\n" if $debug;
241                  return;                  return;
242          }          }
243    
         my $path = sprintf("$dump_dir/%04d.jpg", $frame_nr++ );  
   
         open(my $fh, '>', $path) || die "can't create $path: $!";  
   
244          my $w = $d->{amvh}->{width} || die "no width?";          my $w = $d->{amvh}->{width} || die "no width?";
245          my $h = $d->{amvh}->{height} || confess "no height?";          my $h = $d->{amvh}->{height} || confess "no height?";
246    
# Line 287  sub mkjpg { Line 363  sub mkjpg {
363                  0,                      # Ah, Ai (not used)                  0,                      # Ah, Ai (not used)
364          );          );
365    
366          if ( $dump ) {          if ( $dump_jpeg ) {
367                  print "## created JPEG header...\n";                  print "## created JPEG header...\n";
368                  hex_dump( $header, 0 );                  hex_dump( $header, 0 );
369          }          }
# Line 295  sub mkjpg { Line 371  sub mkjpg {
371          my $frame = join('', @subframes ) . $data;          my $frame = join('', @subframes ) . $data;
372          @subframes = ();          @subframes = ();
373    
374            my $path = sprintf("$dump_dir/%04d.jpg", $frame_nr );
375    
376            my $fh;
377            if ( $jpegtran ) {
378                    open($fh, '|-', "jpegtran $jpegtran > $path") || die "can't create $path: $!";
379            } else {
380                    open($fh, '>', $path) || die "can't create $path: $!";
381            }
382    
383          if ( ! $no_jpeg_header ) {          if ( ! $no_jpeg_header ) {
384                  print $fh $header, $frame, "\xFF\xD9" || die "can't write jpeg $path: $!";                  print $fh $header . $frame . "\xFF\xD9" || die "can't write jpeg $path: $!";
385          } else {          } else {
386                  print $fh $frame || die "can't write raw jpeg $path: $!";                  print $fh $frame || die "can't write raw jpeg $path: $!";
387          }          }
388          close $fh || die "can't close $path: $!";          close $fh || die "can't close $path: $!";
389          print ">> created $frame_nr ", $no_jpeg_header ? 'raw' : '', " jpeg $path ", -s $path, " bytes\n";          print ">> created $frame_nr ", $no_jpeg_header ? 'raw' : '', " jpeg $path ", -s $path, " bytes\n" if $verbose;
390    }
391    
392    #
393    # IMA ADPCM decoder
394    #
395    
396    my @index_adjust = ( -1, -1, -1, -1, 2, 4, 6, 8 );
397    
398    my @step_size = (
399            7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
400            19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
401            50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
402            130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
403            337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
404            876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
405            2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
406            5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
407            15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
408    );
409    
410    my $pred_val = 0;
411    my $step_idx = 0;
412    
413    # This code is "borrowed" from the ALSA library
414    # http://www.alsa-project.org
415    
416    sub adpcm_decode_sample {
417            my $code = shift;
418    
419            my $pred_diff;  # Predicted difference to next sample
420            my $step;               # holds previous step_size value
421    
422            # Separate sign and magnitude
423            my $sign = $code & 0x8;
424            $code &= 0x7;
425    
426            # Computes pred_diff = (code + 0.5) * step / 4,
427            # but see comment in adpcm_coder.
428    
429            $step = $step_size[$step_idx] || die "no step_size[$step_idx]";
430    
431            # Compute difference and new predicted value
432            $pred_diff = $step >> 3;
433            my $i = 0x4;
434            while( $i ) {
435                    if ($code & $i) {
436                            $pred_diff += $step;
437                    }
438                    $i >>= 1;
439                    $step >>= 1;
440            }
441            $pred_val += $sign ? -$pred_diff : $pred_diff;
442    
443            # Clamp output value
444            if ($pred_val > 32767) {
445                    $pred_val = 32767;
446            } elsif ($pred_val < -32768) {
447                    $pred_val = -32768;
448            }
449    
450            # Find new step_size index value
451            $step_idx += $index_adjust[$code];
452    
453            if ($step_idx < 0) {
454                    $step_idx = 0;
455            } elsif ($step_idx > 88) {
456                    $step_idx = 88;
457            }
458            return $pred_val;
459    }
460    
461    my $au_path = "$dump_dir/sound.au";
462    open(my $au_fh, '>', $au_path) || die "can't open $au_path: $!";
463    print $au_fh pack 'a4N5', (
464            # magic
465            '.snd',
466            # data offset
467            24,
468            # data size
469            -1,
470            # encoding - 16-bit linear PCM
471            3,
472            # sample rate
473            22050,
474            #channels
475            1,
476    );
477    
478    sub audio_frame {
479            my $data = shift || die "no data?";
480    
481            my ( $origin, $index, $bytes ) = unpack 'ssL', substr($data,0,8);
482    
483            $pred_val = $origin;
484            $step_idx = $index;
485    
486            foreach my $b ( map { ord($_) } split(//, substr($data,8)) ) {
487                    print $au_fh pack 'n', adpcm_decode_sample( $b >> 4 );          
488                    print $au_fh pack 'n', adpcm_decode_sample( $b & 15 );          
489            }
490    }
491    
492    
493    sub x_audio_frame {
494            my $data = shift || die "no data?";
495    
496            my $apath = sprintf("$dump_dir/%04d.wav", $frame_nr );
497            open(my $audio_fh, '>', $apath) || die "can't open audio file $apath: $!";
498    
499            print $audio_fh pack 'a4Va4a4VvvVVv4', (
500                    # header 'RIFF', size
501                    'RIFF',-1,
502                    # type: 'WAVE'
503                    'WAVE',
504                    'fmt ',0x14,
505                    # format: DVI (IMA) ADPCM Wave Type
506                    0x11,
507                    # channels
508                    1,
509                    # samples/sec
510                    22050,
511                    # avg. bytes/sec (for esimation)
512                    11567,
513                    # block align (size of block)
514                    0x800,
515                    # bits per sample (mono data)
516                    4,
517                    # cbSize (ADPCM with 7 soefficient pairs)
518                    2,
519                    # nSamplesPerBlock
520                    # (((nBlockAlign - (7 * nChannels)) * 8) / (wBitsPerSample * nChannels)) + 2
521                    0x03f9,
522            );
523    
524            print $audio_fh pack 'a4VVa4V', (
525                    # time length of the data in samples
526                    'fact',4,
527                    220500,
528                    #
529                    'data',-1,
530            );
531    
532            my $riff_header_len = tell($audio_fh);
533    
534            print $audio_fh $data;
535    
536            my $size = tell($audio_fh);
537            warn "## wav file $apath size: $size\n";
538    
539            seek( $audio_fh, 4, 0 );
540            print $audio_fh pack("V", $size - 8);
541            seek( $audio_fh, $riff_header_len - 4, 0 );
542            print $audio_fh pack("V", $size - $riff_header_len);
543    
544            close($audio_fh) || die "can't close audio file $apath: $!";
545  }  }
546    
547    #
548    # read AMV file
549    #
550    
551  my ( $riff, $amv ) = x(12, 'Z4x4Z4');  my ( $riff, $amv ) = x(12, 'Z4x4Z4');
552  die "$path not RIFF but $riff" if $riff ne 'RIFF';  die "$path not RIFF but $riff" if $riff ne 'RIFF';
553  die "$path not AMV but $amv" if $amv ne 'AMV ';  die "$path not AMV but $amv" if $amv ne 'AMV ';
554    
555    my $fps = 16;
556    my $duration;
557    
558  while ( ! defined($d->{eof}) ) {  while ( ! defined($d->{eof}) ) {
559          my ( $list, $name ) = x(12,'A4x4A4');          my ( $list, $name ) = x(12,'A4x4A4');
560          die "not LIST but $list" if $list ne 'LIST';          die "not LIST but $list" if $list ne 'LIST';
561          print "< $list * $name\n";          print "< $list * $name\n" if $verbose;
562    
563          if ( $name eq 'hdrl' ) {          if ( $name eq 'hdrl' ) {
564    
# Line 325  while ( ! defined($d->{eof}) ) { Line 572  while ( ! defined($d->{eof}) ) {
572                          $h->{$n} = $v;                          $h->{$n} = $v;
573                  } x($len, 'Vx28VVVx8CCv');                  } x($len, 'Vx28VVVx8CCv');
574    
575                  printf "## %s %d*%d %s fps (%d ms/frame) %02d:%02d:%02d\n",                  $duration = sprintf('%02d:%02d:%02d', $h->{hh}, $h->{mm}, $h->{ss} );
576    
577                    printf "## %s %d*%d %s fps (%d ms/frame) %s\n",
578                          $path,                          $path,
579                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},
580                          $h->{hh}, $h->{mm}, $h->{ss};                          $duration;
581    
582                  $d->{amvh} = $h;                  $d->{amvh} = $h;
583                    $fps = $h->{fps};
584    
585          } elsif ( $name eq 'strl' ) {          } elsif ( $name eq 'strl' ) {
586    
# Line 344  while ( ! defined($d->{eof}) ) { Line 594  while ( ! defined($d->{eof}) ) {
594                                    
595                          my $len = next_part( '00dc' );                          my $len = next_part( '00dc' );
596                          last unless $len;                          last unless $len;
597                          printf "<< %s 00dc - frame %d jpeg %d 0x%x bytes\n", $name, $frame, $len, $len;                          printf "<< %s 00dc - part %d jpeg %d 0x%x bytes\n", $name, $frame, $len, $len if $verbose;
598                          mkjpg( x($len) );                          mkjpg( x($len) );
599    
600                          $len = next_part( '01wb', 0, 1 );                          $len = next_part( '01wb' );
601                          printf "<< %s 01wb - frame %d audio %d 0x%x bytes\n", $name, $frame, $len, $len;                          printf "<< %s 01wb - part %d audio %d 0x%x bytes\n", $name, $frame, $len, $len if $verbose;
602    
603                            my $audio_frame = x( $len );
604    
605                            if ( $dump_audio ) {
606                                    printf "#### dumping audio frame %d 0x%x bytes\n", length($audio_frame), length($audio_frame);
607                                    hex_dump( $audio_frame );
608                            }
609    
610    #                       print $audio_fh mp3_frame;
611                            audio_frame( $audio_frame );
612    
613                            $frame_nr++;
614    
615                            if ( $frame_nr % $fps == 0 ) {
616                                    print "\n" if ( ( $frame_nr / $fps ) % 60 == 0 );
617                                    print ".";
618                            }
619                  };                  };
620    
621          } else {          } else {
# Line 356  while ( ! defined($d->{eof}) ) { Line 623  while ( ! defined($d->{eof}) ) {
623          }          }
624  }  }
625    
626  my $cmd = "ffmpeg -i $dump_dir/%04d.jpg -r 16 -f $dump_avi";  my $cmd = "ffmpeg -r $fps -i $dump_dir/%04d.jpg -i $au_path -y $dump_avi";
627  system($cmd) == 0 || die "can't convert frames to avi using $cmd: $!";  system($cmd) == 0 || die "can't convert frames to avi using $cmd: $!";
628    
629    print ">>>> created $frame_nr frames $dump_avi ", -s $dump_avi, "\n";

Legend:
Removed from v.21  
changed lines
  Added in v.29

  ViewVC Help
Powered by ViewVC 1.1.26