File Coverage

blib/lib/Video/Info/MPEG/System.pm
Criterion Covered Total %
statement 143 181 79.0
branch 34 70 48.5
condition 6 15 40.0
subroutine 17 17 100.0
pod 0 12 0.0
total 200 295 67.8


line stmt bran cond sub pod time code
1             ##------------------------------------------------------------------------
2             ## Package: Video::Info::MPEG::System
3             ## Author: Benjamin R. Ginter
4             ## Notice: Copyright (c) 2002 Benjamin R. Ginter
5             ## Purpose: Parse system streams
6             ## Comments: None
7             ## CVS: $Id: System.pm,v 1.3 2002/11/12 07:19:34 allenday Exp $
8             ##------------------------------------------------------------------------
9              
10             package Video::Info::MPEG::System;
11 5     5   28 use strict;
  5         11  
  5         205  
12 5     5   28 use Video::Info::MPEG;
  5         11  
  5         114  
13 5     5   26 use Video::Info::MPEG::Constants;
  5         9  
  5         1141  
14              
15 5     5   37 use constant DEBUG => 0;
  5         12  
  5         298  
16 5     5   29 use base qw(Video::Info::MPEG);
  5         10  
  5         13770  
17              
18             ##------------------------------------------------------------------------
19             ## Preloaded methods go here.
20             ##------------------------------------------------------------------------
21             1;
22              
23             ##------------------------------------------------------------------------
24             ## new()
25             ##
26             ## override superclass constructor
27             ##------------------------------------------------------------------------
28             sub init {
29 6     6 0 280 my $self = shift;
30 6         19 my %param = @_;
31 6         1195 $self->init_attributes(@_);
32 6         486 $self->handle($self->filename);
33 6         209 $self->version(0);
34 6         210 $self->offset(0);
35 6         200 $self->last_offset(0);
36 6         186 $self->{audio} = Video::Info::MPEG::Audio->new(-file => $self->filename);
37 6         372 $self->{video} = Video::Info::MPEG::Video->new(-context => 'system',
38             -file => $self->filename
39             );
40             }
41              
42 27     27 0 388 sub audio { return shift->{audio} }
43 24     24 0 561 sub video { return shift->{video} }
44              
45             ##------------------------------------------------------------------------
46             ## parse()
47             ##
48             ## Parse a system packet.
49             ##
50             ## Strategy:
51             ## - Find the first PACK sequence start code
52             ## - Search for additional packs ( process_packs() )
53             ## -
54             ##------------------------------------------------------------------------
55             sub parse {
56 3     3 0 7 my ($self,$offset) = @_;
57              
58 3         11 my $fh = $self->handle;
59 3 50       28 $offset = 0 if !defined $offset;
60              
61 3         6 my ( $pack_start, $pack_len, $pack_head, $packet_size, $packet_type );
62              
63 3         5 print "Video::Info::MPEG::System::parse( $offset )\n" if DEBUG;
64              
65             ##--------------------------------------------------------------------
66             ## Verify we're dealing with a system stream by trying to fetch the
67             ## first sequence start code (ssc). Save the offset if we succeed.
68             ##--------------------------------------------------------------------
69 3 50       16 $self->is_system( $offset ) or return 0;
70             # $offset = $self->_last_offset if defined $self->_last_offset;
71 3         5 $offset = 12;
72              
73             ##--------------------------------------------------------------------
74             ## Find the remaining packs and process them, returning if we find any
75             ## audio or video tracks. We handle padding packets here too.
76             ##--------------------------------------------------------------------
77 3 50       14 $self->process_packs( $offset ) or return 0;
78              
79             # print "OFFSET: $offset $self->{last_offset}\n";
80 3         9 $offset = $self->_last_offset - 13;
81              
82             ## okay, this is a miracle but we have what we wanted here
83             ## video!
84 3 50       12 if ( !$self->video->parse( $offset ) ) {
85 0         0 print "parse_system: call to parse_video() failed\n" if DEBUG;
86 0         0 return 0;
87             }
88             ## now get the pack and the packet header just before the video sequence
89 3         9 my $main_offset = $offset;
90 3         7 print "Finding audio\n" if DEBUG;
91 3 50       113 if ( $self->next_start_code( AUDIO_PKT, $offset + $self->header_size ) ) {
92 3         7 print "Found it at ", $self->_last_offset, "\n" if DEBUG;
93 3         24 my $audio_offset = $self->skip_packet_header( $self->{last_offset} );
94 3         6 print "AUDIO OFFSET: $audio_offset $self->{last_offset} \n" if DEBUG;
95            
96 3 50       16 if ( !$self->audio->parse( $audio_offset ) ) {
97 3         77 while ( $audio_offset < $self->filesize - 10 ) {
98             ## mm, audio packet doesn't begin with FFF
99             # print "OFFSET: $audio_offset\n" if DEBUG;
100 12 100       94 if ( $self->audio->parse( $audio_offset ) ) {
101 3         8 last;
102             }
103            
104 9         234 $audio_offset++; ## is this ok?
105             }
106             }
107             # print "Parsed audio OK!\n";
108              
109             }
110              
111             ## seek the file duration by fetching the last PACK
112             ## and reading its timestamp
113 3 50       85 if ( $self->next_start_code( PACK_PKT, $self->filesize - 2500 ) ) {
114             # print "Found final PACK at $self->{last_offset}\n";
115             }
116 3         20 my $byte = $self->get_byte( $self->{last_offset} + 4 );
117            
118             ## see if it's a standard MPEG1
119 3 50       15 if ( $byte & 0xF0 == 0x20 ) {
120 0         0 $self->duration( $self->read_ts( 1, $self->{last_offset} + 4 ) );
121             }
122             ## no?
123             else {
124             ## Is it MPEG2?
125 3 50       20 if ( $byte & 0xC0 == 0x40 ) {
126 0         0 print "TS: ", $self->read_ts( 2, $self->{last_offset} + 4 ), "\n" if DEBUG;
127             }
128             ## try mpeg1 anyway
129             else {
130 3         33 $self->duration( $self->read_ts( 1, $self->{last_offset} + 4) );
131             }
132             }
133            
134 3         41 return 1;
135             }
136              
137             ##------------------------------------------------------------------------
138             ## process_packs()
139             ##
140             ## Step through the bitstream and process each type of pack encountered,
141             ## stopping if we find any audio or video tracks.
142             ##------------------------------------------------------------------------
143             sub process_packs {
144 3     3 0 15 my ( $self, $offset ) = @_;
145 3         21 my $fh = $self->handle;
146              
147 3         20 print "\n", '-' x 74, "\nSearching for start code packets\n", '-' x 74, "\n" if DEBUG;
148              
149 3         68 while ( $offset <= $self->filesize ) {
150             ## print '-' x 20, '[ LOOP ]', '-' x 20, "\n" if DEBUG;
151             ## print "OFFSET: $offset\n" if DEBUG;
152            
153             ## Find next start code
154 9         78 my $code = $self->next_start_code( undef, $offset );
155              
156 9         29 $offset = $self->_last_offset;
157 9         12 printf( "Found marker '%s' (0x%02x) at %d\n",
158             $STREAM_ID->{$code}, ## Note the uppercase. This is defined in Constants.pm
159             $code,
160             $offset ) if DEBUG;
161              
162            
163             ##----------------------------------------------------------------
164             ## We found what we're looking for (VIDEO or AUDIO)
165             ##----------------------------------------------------------------
166 9 100 66     37 last if $code == VIDEO_PKT || $code == AUDIO_PKT;
167              
168             ##----------------------------------------------------------------
169             ## if this is a PADDING packet for byte alignment
170             ##----------------------------------------------------------------
171 6 50       35 if ( $code == PADDING_PKT ) {
    100          
    50          
172             # print "\t\tFound Padding Packet at $offset\n";
173 0         0 $offset += $self->grab( 2, $offset + 4 );
174             # print "Skipped to $offset\n";
175 0         0 next;
176             }
177              
178             ##----------------------------------------------------------------
179             ## if this is a PACK
180             ##----------------------------------------------------------------
181             elsif ( $code == PACK_PKT ) {
182 3         12 $self->{muxrate} = $self->get_mux_rate( $offset + 4);
183 3         5 $offset += 12;
184 3         64 next;
185             }
186            
187             ##----------------------------------------------------------------
188             ## It has to be a system packet
189             ##----------------------------------------------------------------
190             elsif ( $code == SYS_PKT ) {
191 3         13 my $len = $self->parse_sys_pkt( $offset );
192            
193 3 50       9 if ( $len ) {
194 3         4 $offset = $len;
195 3         67 next;
196             }
197             }
198              
199             ##----------------------------------------------------------------
200             ## No more guessing
201             ##----------------------------------------------------------------
202             else {
203 0         0 printf( "1: Unhandled packet encountered '%s' ( 0x%02x ) at offset %d\n",
204             $STREAM_ID->{$code},
205             $code,
206             $offset ) if DEBUG;
207             # $offset += 4;
208             # next;
209             }
210              
211 0         0 $offset += 4;
212             }
213              
214 3         11 return 1;
215             }
216              
217             ##------------------------------------------------------------------------
218             ## is_system()
219             ##
220             ## Verify this is a system stream.
221             ##------------------------------------------------------------------------
222             sub is_system {
223 3     3 0 7 my ( $self, $offset ) = @_;
224              
225 3         4 print "\n", '-' x 74, "\nLooking for System Start Packet\n", '-' x 74, "\n" if DEBUG;
226              
227              
228 3 50       28 if ( !$self->next_start_code( PACK_PKT, 0 ) ) {
229 0         0 print "Couldn't find packet start code\n" if DEBUG;
230 0         0 return 0;
231             }
232              
233 3         8 print "Warning: junk at the beginning!\n" if DEBUG && $self->_last_offset;
234 3         10 return 1;
235             }
236              
237             ##------------------------------------------------------------------------
238             ## get_streams
239             ##
240             ## Parse a system packet and extract the number of streams.
241             ##------------------------------------------------------------------------
242             sub get_streams {
243 3     3 0 8 my ( $self, $offset ) = @_;
244            
245 3         4 print "\n", '-' x 74, "\nGetting Stream Counts\n", '-' x 74, "\n" if DEBUG;
246              
247 3         13 my $stream_count_token = $self->grab( 2, $offset + 4 ) - 6;
248              
249 3 50       14 return 0 if $stream_count_token % 3 != 0;
250              
251 3         17 for ( my $i = 0; $i < $stream_count_token / 3; $i++ ) {
252 6         21 my $code = $self->get_byte( $offset + 12 + $i * 3 );
253            
254 6 100 33     27 if ( ( $code & 0xf0 ) == AUDIO_PKT ) {
    50          
255             # print "Audio Stream\n" if DEBUG;
256 3         11 $self->{astreams}++;
257             }
258             elsif ( ( $code & 0xf0 ) == VIDEO_PKT || ( $code & 0xf0 ) == 0xD0 ) {
259             # print "Video Stream\n" if DEBUG;
260 3         11 $self->{vstreams}++;
261             }
262             }
263              
264 3         69 $self->astreams( $self->{astreams} );
265 3         74 $self->vstreams( $self->{vstreams} );
266             # print "\t", $self->astreams, " audio\n";
267             # print "\t", $self->vstreams, " video\n";
268              
269 3 50       67 return 1 if $self->vstreams;
270              
271 0         0 return 0;
272             }
273              
274             ##------------------------------------------------------------------------
275             ## get_version()
276             ##
277             ## Sets the MPEG version.
278             ##------------------------------------------------------------------------
279             sub get_version {
280 3     3 0 8 my ( $self, $offset ) = @_;
281              
282 3         3 print "\n", '-' x 74, "\nGetting Version\n", '-' x 74, "\n" if DEBUG;
283              
284             ##--------------------------------------------------------------------
285             ## Check for variable length PACK in mpeg2
286             ##--------------------------------------------------------------------
287 3         6 $offset = 0;
288 3         7 $self->{pack_len} = 0;
289 3         10 my $pack_head = $self->get_byte( $offset + 4 );
290            
291 3 50       12 if ( ( $pack_head & 0xF0 ) == 0x20 ) {
292 3         92 $self->vcodec('MPEG1');
293 3         21 print "MPEG1\n" if DEBUG;
294 3         7 $self->{pack_len} = 12;
295             }
296             else {
297 0 0       0 if ( ( $pack_head & 0xC0 ) == 0x40 ) {
298             ## new mpeg2 pack : 14 bytes + stuffing
299 0         0 $self->vcodec('MPEG2');
300 0         0 print "MPEG2\n" if DEBUG;
301 0         0 $self->{pack_len} = 14 + $self->get_byte( $offset + 13 ) & 0x07;
302             }
303             else {
304             ## whazzup?!
305 0         0 printf "Weird pack encountered! 0x%02x\n", $pack_head if DEBUG;
306 0         0 $self->{pack_len} = 12;
307 0         0 return 0;
308             }
309             }
310            
311 3         20 return 1;
312             }
313              
314             ##------------------------------------------------------------------------
315             ## parse_sys_pkt()
316             ##
317             ## Parse a system packet
318             ##------------------------------------------------------------------------
319             sub parse_sys_pkt {
320 3     3 0 5 my ( $self, $offset ) = @_;
321 3         9 my $fh = $self->handle;
322              
323 3         23 print "\n", '-' x 74, "\nParsing System Packet\n", '-' x 74, "\n" if DEBUG;
324              
325             ## Get the MPEG version then the number of audio and video streams.
326 3 50       13 $self->get_version( $offset ) or die "Can't get MPEG version\n";
327 3 50       10 $self->get_streams( $offset ) or die "Strange number of packets!\n";
328              
329             # print "Getting packet size\n" if DEBUG;
330 3         39 my $packet_size = $self->grab( 2, $offset + 4 );
331              
332             # print "Getting packet type\n" if DEBUG;
333 3         13 my $packet_type = $self->get_byte( $offset + 12 );
334              
335 3         11 my $byte = $self->get_byte( $offset + 15 );
336             # printf "PACKET_TYPE: %02x\n", $packet_type;
337             # printf "BYTE: %02x\n", $byte;
338              
339 3         6 my $header_len = 0;
340              
341 3 50 33     29 if ( $byte == AUDIO_PKT || $byte == VIDEO_PKT ) {
342             # print "System packet with both audio and video\n" if DEBUG;
343 3         6 $packet_type = VIDEO_PKT; ## since video is mandatory
344            
345 3         8 $header_len = $self->{pack_len} + 6 + $packet_size;
346              
347             ## We could grab the entire video header here and pass it off
348             ## to MPEG::Info::Video to avoid the seek/read penalties
349             }
350            
351             ##--------------------------------------------------------------------
352             ## If we ever encounter a packet with multiple audio or video streams,
353             ## we can implement this.
354             ##--------------------------------------------------------------------
355 3 50 33     18 if ( $packet_type != AUDIO_PKT && $packet_type != VIDEO_PKT ) {
356 0         0 printf "Unknown system packet '%s', %x @ $offset\n", $STREAM_ID->{$packet_type},
357             $packet_type if DEBUG;
358 0         0 return 0;
359             }
360              
361 3         15 print "\n", '-' x 74, "\nEnd System Packet Parse\n", '-' x 74, "\n" if DEBUG;
362 3         7 return $header_len;
363             }
364              
365             ##------------------------------------------------------------------------
366             ## read_ts()
367             ##
368             ## Read an MPEG-1 or MPEG-2 timestamp
369             ##------------------------------------------------------------------------
370             sub read_ts {
371 3     3 0 7 my $self = shift;
372 3         6 my $type = shift;
373 3         6 my $offset = shift;
374              
375 3         15 my $ts = 0;
376              
377 3 50       12 if ( $type == 1 ) {
    0          
378 3         13 my $highbit = ( $self->get_byte( $offset ) >> 3 ) & 0x01;
379 3         14 my $low4bytes = ( ( $self->get_byte( $offset ) >> 1 ) & 0x30 ) << 30;
380 3         215 $low4bytes |= ( $self->get_byte( $offset + 1 ) << 22 );
381 3         13 $low4bytes |= ( ( $self->get_byte( $offset + 2 ) >> 1 ) << 15 );
382 3         13 $low4bytes |= ( $self->get_byte( $offset + 3 ) << 7 );
383 3         14 $low4bytes |= ( $self->get_byte( $offset + 4 ) >> 1 );
384              
385 3         8 $ts = $highbit * ( 1 << 16 );
386 3         6 $ts += $low4bytes;
387 3         8 $ts /= 90000;
388             }
389             elsif ( $type == 2 ) {
390 0         0 print "Define mpeg-2 timestamps\n" if DEBUG;
391             }
392 3         83 return $ts;
393              
394             }
395              
396             ##------------------------------------------------------------------------
397             ## skip_packet_header()
398             ##
399             ## Skip a packet header
400             ##------------------------------------------------------------------------
401             sub skip_packet_header {
402 3     3 0 6 my $self = shift;
403 3         80 my $offset = shift;
404              
405 3 50       172 if ( $self->version == 1 ) {
    50          
406             ## skip startcode and packet size
407 0         0 $offset += 6;
408              
409             ## remove stuffing bytes
410 0         0 my $byte = $self->get_byte( $offset );
411              
412 0         0 while ( $byte & 0x80 ) {
413 0         0 $byte = $self->get_byte( ++$offset );
414             }
415              
416             ## next two bytes are 01
417 0 0       0 if ( ( $byte & 0xC0 ) == 0x40 ) {
418 0         0 $offset += 2;
419             }
420            
421 0         0 $byte = $self->get_byte( $offset );
422              
423 0 0       0 if ( ( $byte & 0xF0 ) == 0x20 ) {
    0          
424 0         0 $offset += 5;
425             }
426             elsif ( ( $byte & 0xF0 ) == 0x30 ) {
427 0         0 $offset += 10;
428             }
429             else {
430 0         0 $offset++;
431             }
432              
433             # print "1. Returning offset of $offset\n" if DEBUG;
434            
435 0         0 return $offset;
436             }
437             elsif ( $self->version == 2 ) {
438             ## this is a PES, easyer
439             ## offset + 9 is the header length (-9)
440              
441             # print "2. Returning offset of ", $offset + 9 + ( $self->get_byte + 8 ), "\n" if DEBUG;
442 0         0 return $offset + 9 + ( $self->get_byte + 8 );
443             }
444             else {
445             # print "3. Returning offset of ", $offset + 10, "\n" if DEBUG;
446 3         135 return $offset + 10;
447             }
448             }
449              
450             ##------------------------------------------------------------------------
451             ## get_mux_rate()
452             ##
453             ## Calculate the mux rate
454             ##------------------------------------------------------------------------
455             sub get_mux_rate {
456 3     3 0 6 my $self = shift;
457 3   33     10 my $offset = shift || $self->{offset};
458              
459 3         17 print "\n", '-' x 74, "\nGetting Muxrate @ $offset\n", '-' x 74, "\n" if DEBUG;
460              
461 3         5 my $muxrate = 0;
462              
463 3         9 my $byte = $self->get_byte( $offset );
464              
465 3 50       17 if ( ( $byte & 0xC0 ) == 0x40 ) {
466 0         0 $muxrate = $self->get_byte( $offset + 6 ) << 14;
467 0         0 $muxrate |= $self->get_byte( $offset + 7 ) << 6;
468 0         0 $muxrate |= $self->get_byte( $offset + 8 ) >> 2;
469             }
470             else {
471             ## maybe mpeg1
472 3 50       46 if ( ( $byte & 0xf0 ) != 0x20 ) {
473 0         0 print "Weird pack header while parsing muxrate (offset ", $offset, ")\n" if DEBUG;
474             # die;
475             }
476              
477 3         12 $muxrate = ( $self->get_byte( $offset + 5 ) & 0x7f ) << 15;
478 3         12 $muxrate |= $self->get_byte( $offset + 6 ) << 7;
479 3         10 $muxrate |= $self->get_byte( $offset + 7 ) >> 1;
480             }
481            
482 3         6 $muxrate *= 50;
483 3         6 return $muxrate;
484             }
485              
486              
487              
488             __END__