File Coverage

blib/lib/Linux/DVB/DVBT/TS.pm
Criterion Covered Total %
statement 18 84 21.4
branch 0 52 0.0
condition 0 10 0.0
subroutine 6 14 42.8
pod 7 7 100.0
total 31 167 18.5


line stmt bran cond sub pod time code
1             package Linux::DVB::DVBT::TS ;
2              
3             =head1 NAME
4              
5             Linux::DVB::DVBT::TS - Transport Stream utilities
6              
7             =head1 SYNOPSIS
8              
9             use Linux::DVB::DVBT::TS ;
10            
11             my $settings_href = {'debug' => $debug} ;
12            
13             # get file information
14             my %info = info($filename, $settings_href) ;
15            
16             # Splitting file...
17             ts_split($filename, $ofilename, \@cuts, $settings_href) ;
18              
19             # Cutting file...
20             ts_cut($filename, $ofilename, \@cuts, $settings_href) ;
21            
22             # repair a file...
23             my %stats = repair($filename, $ofilename, \&error_display) ;
24            
25             sub error_display
26             {
27             my ($info_href) = @_ ;
28             print "ERROR: PID $info_href->{'pidinfo'}{'pid'} $info_href->{'error'}{'str'} [$info_href->{'pidinfo'}{'pktnum'}]\n" ;
29             }
30            
31            
32             # Parse the file, calling subroutines on each frame...
33             parse($filename, {
34             'mpeg2_rgb_callback' = \&colour_callback
35             'user_data' => {
36             'outname' => "$outdir/$base%03d.ppm",
37             },
38             }) ;
39            
40             sub colour_callback
41             {
42             my ($tsreader, $info_href, $width, $height, $data, $user_data_href) = @_ ;
43            
44             ## save image
45             write_ppm($user_data_href->{'outname'}, $info_href->{'framenum'},
46             $width, $height,
47             $data,
48             ) ;
49             }
50            
51            
52              
53             =head1 DESCRIPTION
54              
55             Module provides a set of useful transport stream utility routines. As well as an underlying
56             transport stream parsing framework, this module also incorporates MPEG2 video decoding and AAC
57             audio decoding.
58              
59             =head2 Callbacks
60              
61             The transport stream parsing framework works through the video file, calling user provided
62             callback functions at the appropriate points. If you don't specify any callbacks, then the
63             framework will run through the video file and do nothing!
64              
65             Many of the callbacks have the following common arguments passed to them, and are described
66             here rather than in the callback description:
67              
68             =over 4
69              
70             =item $tsreader_ref
71              
72             The $tsreader_ref is a pointer to the TS framework parser that is calling the callback.
73             Some other routines accept this value as a parameters (see L).
74             Do not modify this value!
75              
76             =item $user_data
77              
78             Optionally, you can pass a reference to your own user data into the settings
79             when calling the framework (see L). This reference is passed back
80             in the $user_data argument
81              
82             =back
83              
84             The list of supported callbacks and the arguments they are called with are as follows:
85              
86              
87             =head3 PID callback
88              
89             pid_callback($tsreader_ref, $pid, $user_data)
90              
91             The pid of the current stream is passed as an integer in $pid. You must return
92             a TRUE value to tell the framework to continue processing with this pid; otherwise
93             return a FALSE value to indicate that the framework should move on to the next pid.
94              
95             You can use this to skip processing any unwanted pids (mainly to speed up operation).
96              
97             =head3 Error callback
98              
99             error_callback($tsreader_ref, $info_href, $user_data)
100              
101             The information HASH ref contains:
102              
103             =over 4
104              
105             B = HASH ref containing:
106              
107             =over 4
108              
109             B = current pid
110              
111             B = TRUE if error flag is set in this TS packet
112              
113             B = TRUE is this packet is the start of a PES packet
114              
115             B = afc field code
116              
117             B = count of errors (so far) for this pid
118              
119             B = TS packet count (from start of video, starting at 0)
120              
121             =back
122              
123             B = HASH ref containing:
124              
125             =over 4
126              
127             B = error code
128              
129             B = error string
130              
131             =back
132              
133             =back
134              
135             Called either when there is an error indication in the transport stream, or for
136             other errors.
137              
138             =head3 TS callback
139              
140             ts_callback($tsreader_ref, $info_href, $packet, $user_data)
141              
142             The information HASH ref contains:
143              
144             =over 4
145              
146             B = (see L)
147              
148             =back
149              
150             This is called with the complete transport stream packet.
151              
152              
153             =head3 Payload callback
154              
155             payload_callback($tsreader_ref, $info_href, $payload, $user_data)
156              
157             The information HASH ref contains:
158              
159             =over 4
160              
161             B = (see L)
162              
163             =back
164              
165             This is called with the payload data of the transport stream packet (i.e. with
166             the headers stripped off).
167              
168              
169             =head3 PES callback
170              
171             pes_callback($tsreader_ref, $info_href, $pes_packet, $user_data)
172              
173             The information HASH ref contains:
174              
175             =over 4
176              
177             B = (see L)
178              
179             =back
180              
181             =over 4
182            
183             B = HASH ref containing:
184              
185             =over 4
186              
187             B = number of errors in PES packets
188              
189             B = number of errors in PSI packets
190              
191             B = number of TS packet errors
192              
193             B = String set to:
194              
195             =over 4
196              
197             "PES" for a PES packet
198              
199             "PSI" for an SI packet
200              
201             =back
202              
203             B = presentation timestamp as a HASH ref (see below for details)
204              
205             B = display timestamp in same format as B
206              
207             B = first pts in video (in same format as B)
208              
209             B = first dts in video (in same format as B)
210              
211             B = current last pts in video (in same format as B)
212              
213             B = current last dts in video (in same format as B)
214              
215             B = pts relative to start (in same format as B)
216              
217             B = dts relative to start (in same format as B)
218              
219             =back
220              
221             =back
222              
223             The timestamp format (for pts and dts entries) is a HASH containing:
224              
225             =over 4
226              
227             B = pts integer seconds
228              
229             B = remainder in microseconds
230              
231             B = string of the 33-bit timestamp integer
232              
233             =back
234              
235             So the time in seconds and fractional seconds can be displayed using:
236              
237             printf "%d.%06d", $pts->{'secs'}, $pts->{'usecs'} ;
238            
239             (Note: The 33-bit pts value is (roughly) = 'secs'*90000 + 'usecs'*90)
240              
241              
242             Called with the complete PES/PSI packet.
243              
244              
245             =head3 PES data callback
246              
247             pes_data_callback($tsreader_ref, $info_href, $pes_data, $user_data)
248              
249             The information HASH ref contains:
250              
251             =over 4
252              
253             B = (see L)
254              
255             B = (see L)
256              
257             =back
258              
259             Called with just the PES/PSI data (i.e. with headers removed).
260              
261              
262             =head3 MPEG2 callback
263              
264             mpeg2_callback($tsreader_ref, $info_href, $width, $height, $image, $user_data)
265              
266             The information HASH ref contains:
267              
268             =over 4
269              
270             B = (see L)
271              
272             B = (see L)
273              
274             B = Frame number (starting at 0)
275              
276             B = TS packet number of the last GOP (see MPEG2 docs for details on a GOP!)
277              
278             =back
279              
280             This callback is called with a greyscale image, 1 per video frame. The image data ($image) is
281             $width pixels wides and $height pixels tall, each pixel being a single 8-bit byte value.
282              
283             NOTE: If you use the L with the video pid, you can write the data directly
284             into a file and this data will be the raw MPEG2 video.
285              
286              
287             =head3 MPEG2 RGB callback
288              
289             mpeg2_rgb_callback($tsreader_ref, $info_href, $width, $height, $image, $user_data)
290              
291             The information HASH ref is as L
292              
293             This callback is called with a colour image. Here the pixels are represented by 3 consecutive
294             bytes: a byte each for red, green, and blue.
295              
296              
297             =head3 Audio callback
298              
299             audio_callback($tsreader_ref, $info_href, $audio_data, $user_data)
300              
301             The information HASH ref contains:
302              
303             =over 4
304              
305             B = (see L)
306              
307             B = (see L)
308              
309             B = Number of samples pre second (usually 48000)
310              
311             B = number of audio channels (usually 2)
312              
313             B = Total number of samples in an audio frame (usually 1152)
314              
315             B = the number of audio samples in the data
316              
317             B = Count of audio frames (starting with 0)
318              
319             B = number of samples per frame for a single channel
320              
321             =back
322              
323             Called for every audio frame's worth of data. The audio data is stored as 16-bit values, 1 for each channel.
324              
325             NOTE: If you use the L with the audio pid, you can write the data directly
326             into a file and this data will be the raw AAC audio for the video.
327              
328             =head3 Progress callback
329              
330             progress_callback($tsreader_ref, $state_str, $progress, $total, $user_data)
331              
332             This is a general progress update callback that is called at regular intervals during the parsing of the video
333             file. The $progress and $total values are scaled versions of the current packet count and total number of packets
334             respectively.
335            
336             The $state_str string is one of:
337              
338             =over 4
339              
340             "START" = callback will be called once with this string at the start of execution
341              
342             "END" = callback will be called once with this string at the end of execution
343              
344             "RUNNING" = normal execution - framework is parsing the video file
345              
346             "STOPPED" = set if the user has told the framework to abort (see L)
347              
348             =back
349              
350             =head2 Settings
351              
352             The parsing framework accepts a variety of settings. These are passed as values in a HASH ref. The settings
353             HASH ref consists of:
354              
355             =over 4
356              
357             B = set this to get debug information (higher setting gives more output) [default=0]
358              
359             B = number of TS packets to process before stopping [default=0 which means the whole file]
360              
361             B = start processing the file this many packets from the origin [default=0]
362              
363             B = used with B. May be 0=FILE START, 1=FILE CENTER, 2=FILE END [default=0]
364              
365             B = set to whatever data you like. This is passed on to all callbacks.
366              
367             B = see L
368              
369             B = see L
370              
371             B = see L
372              
373             B = see L
374              
375             B = see L
376              
377             B = see L
378              
379             B = see L
380              
381             B = see L
382              
383             B = see L
384              
385             B = see L
386              
387             =back
388              
389             Most of the entries may be omitted, but it is expected that at least one callback function be set.
390              
391              
392              
393             =head2 Example
394              
395             The following example shows the use of the callback in order to save the AAC audio stream to a file:
396              
397             Linux::DVB::DVBT::TS::parse("file.ts", {
398             # you can put whatever you like in this...
399             'user_data' => {
400             'outname' => "test.aac",
401             },
402             # the callback routine:
403             'audio_callback' => \&audio_callback,
404             } ;
405            
406             sub audio_callback
407             {
408             my ($tsreader_ref, $info_href, $audio_data, $user_data_href) = @_ ;
409            
410             open my $fh, ">>$user_data_href->{'outname'}" or die "Unable to write AAC file" ;
411             print $fh $audio_data ;
412             close $fh ;
413             }
414              
415              
416              
417              
418             =cut
419              
420              
421             #============================================================================================
422             # USES
423             #============================================================================================
424 3     3   117915 use strict ;
  3         9  
  3         107  
425 3     3   5565 use Env ;
  3         19704  
  3         20  
426 3     3   1991 use Carp ;
  3         6  
  3         201  
427 3     3   27 use File::Basename ;
  3         8  
  3         339  
428 3     3   17 use File::Path ;
  3         11  
  3         192  
429              
430 3     3   1420 use Data::Dumper ;
  3         13597  
  3         4050  
431              
432             #============================================================================================
433             # EXPORTER
434             #============================================================================================
435             require Exporter;
436             our @ISA = qw(Exporter);
437              
438             our @EXPORT = qw/
439             error_str
440             info
441             parse
442             parse_stop
443             repair
444             ts_cut
445             ts_split
446             / ;
447              
448              
449             #============================================================================================
450             # GLOBALS
451             #============================================================================================
452             our $VERSION = '0.08' ;
453             our $DEBUG = 0 ;
454              
455             #============================================================================================
456             # XS
457             #============================================================================================
458             require XSLoader;
459              
460             if (!$ENV{'TS_NO_XS'})
461             {
462             XSLoader::load('Linux::DVB::DVBT::TS', $VERSION);
463             }
464             else
465             {
466             print STDERR "WARNING: Running Linux::DVB::DVBT::TS without XS\n" ;
467             }
468              
469             #============================================================================================
470              
471             #============================================================================================
472              
473             =head2 Functions
474              
475             =over 4
476              
477             =cut
478              
479              
480             #-----------------------------------------------------------------------------
481              
482             =item B
483              
484             Repair a transport stream file by removing error packets. Returns a hash
485             of repair stats containing a HASH entry per PID. Each entry is of the form:
486              
487             =over 4
488              
489             B => error count for this pid
490              
491             B
=> HASH where the keys are the error reason string, and the values
492             are the error count for that reason.
493              
494             =back
495              
496             If any runtime error occurs (e.g. unable to read file), then an error string is added
497             to the HASH with the field 'error'.
498              
499             $error_display is an optional callback routine (see L)
500              
501             At the moment "repair" is probably an overstatement. What this currently does is just dump
502             any packets that contain any errors (transport stream or PES). All of the players/transcoders
503             I've tried so far seem fine with this approach. It also prevents ffmpeg from grabbing all available
504             memory then crashing!
505              
506              
507             =cut
508              
509             sub repair
510             {
511 0     0 1   my ($src, $dest, $error_display) = @_ ;
512 0           my %stats ;
513 0           my %settings = (
514             'debug' => $DEBUG,
515             'error_callback' => \&_repair_error_callback,
516             'user_data' => {
517             'error_display' => $error_display,
518             'stats' => \%stats,
519             },
520             ) ;
521 0 0         croak "Unable to read \"$src\"" unless -f $src ;
522 0 0         croak "Zero-length file \"$src\"" unless -s $src ;
523 0 0         croak "Must specify a destination filename" unless $dest ;
524            
525             ## Ensure dest dir is present
526 0           my $dir = dirname($dest) ;
527 0 0         if (! -d $dir)
528             {
529 0 0         mkpath([$dir], 0, 0755) or croak "Unable to create destination directory $dir : $!" ;
530             }
531            
532             ## repair
533 0           Linux::DVB::DVBT::TS::dvb_ts_repair($src, $dest, \%settings) ;
534            
535 0 0         if (Linux::DVB::DVBT::TS::dvb_ts_error())
536             {
537 0           $stats{'error'} = Linux::DVB::DVBT::TS::dvb_ts_error_str() ;
538 0           $stats{'errorcode'} = Linux::DVB::DVBT::TS::dvb_ts_error() ;
539             }
540            
541 0           return %stats ;
542             }
543              
544             #-----------------------------------------------------------------------------
545              
546             =item B
547              
548             Parse a TS file. Uses the settings HASH ref ($settings_href) to configure the callbacks etc.
549             (see L for further details).
550              
551             =cut
552              
553             sub parse
554             {
555 0     0 1   my ($src, $settings_href) = @_ ;
556              
557 0 0         croak "Unable to read \"$src\"" unless -f $src ;
558 0 0         croak "Zero-length file \"$src\"" unless -s $src ;
559              
560 0   0       $settings_href ||= {} ;
561 0           Linux::DVB::DVBT::TS::dvb_ts_parse($src, $settings_href) ;
562             }
563              
564             #-----------------------------------------------------------------------------
565              
566             =item B
567              
568             Abort the parsing of a TS file now (rather than completing to the end of the file).
569              
570             =cut
571              
572             sub parse_stop
573             {
574 0     0 1   my ($tsreader_ref) = @_ ;
575              
576 0 0         croak "Invalid tsreader reference" unless $tsreader_ref ;
577 0           Linux::DVB::DVBT::TS::dvb_ts_parse_stop($tsreader_ref) ;
578             }
579              
580             #-----------------------------------------------------------------------------
581              
582             =item B
583              
584             Get information about a TS file. Returns a HASH containing information about the transport
585             stream file:
586              
587             =over 4
588              
589             B = total number of TS packets in the file
590              
591             B = first timestamp in file (see L for timestamp format)
592              
593             B = last timestamp in file (see L for timestamp format)
594              
595             B = HASH ref containing video duration information in timestamp format and also:
596              
597             =over 4
598              
599             B = integer hours
600              
601             B = integer minutes
602              
603             B = integer seconds
604              
605             =back
606              
607             B = HASH ref containing an entry per pid found in the file. Each pid contains:
608              
609             =over 4
610              
611             B = (see L)
612              
613             B = (see L)
614              
615             =back
616              
617             =back
618              
619             If there is an error of any kind, the returned HASH conatins a single entry:
620              
621             =over 4
622              
623             B = error string describing the error cause
624              
625             =back
626              
627             =cut
628              
629             sub info
630             {
631 0     0 1   my ($src, $settings_href) = @_ ;
632              
633 0           my $info_href = {} ;
634            
635 0 0         unless (-f $src)
636             {
637 0           $info_href->{'error'} = "Unable to read \"$src\"" ;
638             }
639             else
640             {
641 0   0       $settings_href ||= {} ;
642 0           $info_href = Linux::DVB::DVBT::TS::dvb_ts_info($src, $settings_href) ;
643             }
644              
645 0           return %$info_href ;
646             }
647              
648             #-----------------------------------------------------------------------------
649              
650             =item B
651              
652             Cut a transport stream file, removing the reqions described in $cuts_aref, saving
653             the results in the new file $dest.
654              
655             The ARRAY ref $cuts_aref consists of an array of HASH refs, each HASH ref defining
656             the start and end of a region to be cut:
657              
658             =over 4
659              
660             B = TS packet number of start of region
661              
662             B = TS packet number of end of region
663              
664             =back
665              
666             (Note that these ar transport stream packet numbers NOT mpeg2 frame counts. You will need
667             to scan the file to produce a lookup table if you want to specify cuts in frames (or video
668             time)).
669              
670             See L for a description of $settings_href.
671              
672              
673             =cut
674              
675             sub ts_cut
676             {
677 0     0 1   my ($src, $dest, $cuts_aref, $settings_href) = @_ ;
678            
679 0 0         croak "Unable to read \"$src\"" unless -f $src ;
680 0 0         croak "Zero-length file \"$src\"" unless -s $src ;
681 0 0         croak "Must specify a destination filename" unless $dest ;
682            
683             ## Ensure dest dir is present
684 0           my $dir = dirname($dest) ;
685 0 0         if (! -d $dir)
686             {
687 0 0         mkpath([$dir], 0, 0755) or croak "Unable to create destination directory $dir : $!" ;
688             }
689            
690             ## check cuts
691 0 0         croak "Must specify a cuts list array ref" unless ref($cuts_aref) eq 'ARRAY' ;
692            
693             ## run command
694 0   0       $settings_href ||= {} ;
695 0           my $rc = Linux::DVB::DVBT::TS::dvb_ts_cut($src, $dest, $cuts_aref, $settings_href) ;
696 0 0         if ($rc)
697             {
698 0           croak "Error while running ts_cut() : " . Linux::DVB::DVBT::TS::dvb_ts_error_str() ;
699             }
700            
701             }
702              
703             #-----------------------------------------------------------------------------
704              
705             =item B
706              
707             Split a transport stream file into multiple files, starting a new file at the
708             boundaries described in the ARRAY ref $cuts_aref (see L).
709              
710             In this case, a new file is created at the start of the boundary and at the end of the boundary.
711             This means that the original file is simply the concatenation of all of the individual files.
712              
713             The output files are created using the specified destination name (without any exetension), and appending
714             a 4-digit count:
715              
716             sprintf("%s-%04d.ts", $dest, $filenum)
717              
718             Where $filenum is the file counter (starting at 1).
719              
720             For example, with a cut list of:
721              
722             start=100, end=200
723             start=500, end=600
724              
725             and assuming a file of 1000 ts packets, running this function will result in 5 output files (where $dest="file"):
726              
727             file-0001.ts created from packets 0 to 99
728             file-0002.ts created from packets 100 to 199
729             file-0003.ts created from packets 200 to 499
730             file-0004.ts created from packets 500 to 599
731             file-0005.ts created from packets 600 to 999
732              
733             See L for a description of $settings_href.
734              
735              
736             =cut
737              
738             sub ts_split
739             {
740 0     0 1   my ($src, $dest, $cuts_aref, $settings_href) = @_ ;
741            
742 0 0         croak "Unable to read \"$src\"" unless -f $src ;
743 0 0         croak "Zero-length file \"$src\"" unless -s $src ;
744 0 0         croak "Must specify a destination filename" unless $dest ;
745            
746             ## Ensure dest dir is present
747 0           my $dir = dirname($dest) ;
748 0 0         if (! -d $dir)
749             {
750 0 0         mkpath([$dir], 0, 0755) or croak "Unable to create destination directory $dir : $!" ;
751             }
752            
753             ## check cuts
754 0 0         croak "Must specify a cuts list array ref" unless ref($cuts_aref) eq 'ARRAY' ;
755 0 0         croak "Must specify a non-empty cuts list array ref" unless @$cuts_aref ;
756            
757             ## run command
758 0   0       $settings_href ||= {} ;
759 0           my $rc = Linux::DVB::DVBT::TS::dvb_ts_split($src, $dest, $cuts_aref, $settings_href) ;
760 0 0         if ($rc)
761             {
762 0           croak "Error while running ts_split() : " . Linux::DVB::DVBT::TS::dvb_ts_error_str() ;
763             }
764             }
765              
766             #-----------------------------------------------------------------------------
767              
768             =item B
769              
770             In the event of an error, calling this routine will return the appropriate error
771             string that (hopefully) makes more sense than an error code integer.
772              
773             =cut
774              
775             sub error_str
776             {
777 0     0 1   return Linux::DVB::DVBT::TS::dvb_ts_error_str() ;
778             }
779              
780              
781              
782             #============================================================================================
783             # PRIVATE
784             #============================================================================================
785              
786             #-----------------------------------------------------------------------------
787             sub _repair_error_callback
788             {
789 0     0     my ($tsreader, $info_href, $user_href) = @_ ;
790              
791             ## callback user-provided
792 0           my $error_display = $user_href->{'error_display'} ;
793 0 0         if ($error_display)
794             {
795 0           &$error_display($info_href) ;
796             }
797            
798             ## save stats
799 0           my $pid = $info_href->{'pidinfo'}{'pid'} ;
800 0           my ($code, $str) = @{$info_href->{'error'}}{qw/code str/} ;
  0            
801            
802 0           my $stats_href = $user_href->{'stats'} ;
803 0   0       $stats_href->{$pid} ||= {
804             'errors' => 0,
805             'details' => {},
806             } ;
807 0           $stats_href->{$pid}{'errors'}++ ;
808 0           $stats_href->{$pid}{'details'}{$str}++ ;
809             }
810              
811              
812             # ============================================================================================
813             # END OF PACKAGE
814              
815              
816             1;
817              
818             __END__