File Coverage

blib/lib/Audio/WMA.pm
Criterion Covered Total %
statement 273 387 70.5
branch 73 128 57.0
condition 28 59 47.4
subroutine 27 32 84.3
pod 7 7 100.0
total 408 613 66.5


line stmt bran cond sub pod time code
1             package Audio::WMA;
2              
3 3     3   81962 use strict;
  3         9  
  3         139  
4 3     3   17 use vars qw($VERSION);
  3         5  
  3         18602  
5              
6             # WMA stores tags in UTF-16LE by default.
7             my $utf8 = 0;
8              
9             # Minimum requirements
10             if ($] > 5.007) {
11             require Encode;
12             }
13              
14             $VERSION = '1.3';
15              
16             my %guidMapping = _knownGUIDs();
17             my %reversedGUIDs = reverse %guidMapping;
18             my %objectParsers = _knownParsers();
19              
20             my $DEBUG = 0;
21             my $WORD = 2;
22             my $DWORD = 4;
23             my $QWORD = 8;
24             my $GUID = 16;
25              
26             sub new {
27 3     3 1 36 my $class = shift;
28 3         7 my $file = shift;
29 3         6 my $size = shift;
30              
31 3         7 my $self = {};
32              
33 3         7 bless $self, $class;
34              
35 3 50       12 if (ref $file) {
36 0         0 binmode $file;
37 0         0 $self->{'fileHandle'} = $file;
38              
39 0 0       0 if ($size) {
40 0         0 $self->{'size'} = $size;
41             }
42              
43             } else {
44              
45 3 50       128 open(FILE, $file) or do {
46 0         0 warn "[$file] does not exist or cannot be read: $!";
47 0         0 return undef;
48             };
49              
50 3         11 binmode FILE;
51              
52 3         21 $self->{'filename'} = $file;
53 3         10 $self->{'fileHandle'} = \*FILE;
54 3         43 $self->{'size'} = -s $file;
55             }
56              
57 3         7 $self->{'offset'} = 0;
58              
59 3         16 $self->_parseWMAHeader();
60              
61 3         11 delete $self->{'headerData'};
62              
63 3 50       13 unless (ref $file) {
64 3         73 close $self->{'fileHandle'};
65 3         6 close FILE;
66             }
67            
68 3         9 delete $self->{'fileHandle'};
69              
70 3         12 return $self;
71             }
72              
73             sub parseObject {
74 0     0 1 0 my $class = shift;
75 0         0 my $data = shift;
76            
77             # Read the GUID for this object
78 0         0 my $hex = qr/[0-9A-F]/i;
79 0         0 my $gr = qr/($hex{8})($hex{4})($hex{4})($hex{4})($hex{12})/;
80            
81 0         0 my $guid;
82 0         0 map { $guid .= $_ } unpack( 'H*', substr $data, 0, 16 );
  0         0  
83 0         0 $guid = uc( join '-', ( $guid =~ /$gr/ ) );
84 0         0 my $name = $reversedGUIDs{$guid};
85            
86             # Set up a new WMA object for parsing
87 0         0 my $self = {
88             headerData => $data,
89             offset => 16,
90             };
91              
92 0         0 bless $self, $class;
93            
94             # Read the size
95 0         0 my $objectSize = _parse64BitString($self->_readAndIncrementOffset(8));
96 0 0       0 return -1 if !defined $objectSize;
97            
98 0         0 my $parser = $objectParsers{$name};
99            
100 0 0       0 if (ref $parser) {
101 0 0       0 $DEBUG && warn "Parsing $name (size: $objectSize)\n";
102 0         0 $parser->( $self );
103             } else {
104 0 0       0 $DEBUG && warn "No parser found for $name (size: $objectSize)\n";
105             }
106            
107 0         0 return $self;
108             }
109              
110             sub setConvertTagsToUTF8 {
111 0     0 1 0 my $class = shift;
112 0         0 my $val = shift;
113              
114 0 0 0     0 $utf8 = $val if (($val == 0) || ($val == 1));
115              
116 0         0 return $utf8;
117             }
118              
119             sub setDebug {
120 0     0 1 0 my $self = shift;
121              
122 0   0     0 $DEBUG = shift || 0;
123             }
124              
125             sub info {
126 7     7 1 3279 my ($self, $key) = @_;
127              
128 7 100       40 return $self->{'INFO'} unless $key;
129 4         33 return $self->{'INFO'}{lc $key};
130             }
131              
132             sub tags {
133 6     6 1 1143 my ($self, $key) = @_;
134              
135 6 100       23 return $self->{'TAGS'} unless $key;
136 3         23 return $self->{'TAGS'}{uc $key};
137             }
138              
139             sub stream {
140 0     0 1 0 my ($self, $index) = @_;
141              
142 0 0       0 return undef unless $self->{'STREAM'};
143 0 0       0 return $self->{'STREAM'} unless defined($index);
144 0         0 return $self->{'STREAM'}->[$index];
145             }
146              
147             sub _readAndIncrementOffset {
148 314     314   403 my ($self, $size) = @_;
149              
150 314         613 my $value = substr($self->{'headerData'}, $self->{'offset'}, $size);
151              
152 314         392 $self->{'offset'} += $size;
153              
154 314         847 return $value;
155             }
156              
157             sub _readAndIncrementInlineOffset {
158 142     142   163 my ($self, $size) = @_;
159              
160 142         277 my $value = substr($self->{'inlineData'}, $self->{'inlineOffset'}, $size);
161              
162 142         152 $self->{'inlineOffset'} += $size;
163              
164 142         337 return $value;
165             }
166              
167             sub _UTF16ToUTF8 {
168 44     44   56 my $data = shift;
169              
170 44 50 33     154 if ($utf8 && $] > 5.007) {
    50          
171              
172             # This also turns on the utf8 flag - perldoc Encode
173 0   0     0 $data = eval { Encode::decode('UTF-16LE', $data) } || $data;
174              
175             } elsif ($] > 5.007) {
176              
177             # otherwise try and turn it into ISO-8859-1 if we have Encode
178 44   66     53 $data = eval { Encode::encode('latin1', $data) } || $data;
179             }
180              
181 44         3266 return _denull($data);
182             }
183              
184             sub _denull {
185 85     85   116 my $string = shift;
186 85 50       481 $string =~ s/\0//g if defined $string;
187 85         192 return $string;
188             }
189              
190             sub _parseWMAHeader {
191 3     3   5 my $self = shift;
192              
193 3         9 my $fh = $self->{'fileHandle'};
194              
195 3 50       116 read($fh, my $headerObjectData, 30) or return -1;
196              
197 3         10 my $objectId = substr($headerObjectData, 0, $GUID);
198 3         14 my $objectSize = unpack('V', substr($headerObjectData, 16, $QWORD) );
199 3         11 my $headerObjects = unpack('V', substr($headerObjectData, 24, $DWORD));
200 3         10 my $reserved1 = vec(substr($headerObjectData, 28, 1), 0, $DWORD);
201 3         10 my $reserved2 = vec(substr($headerObjectData, 29, 1), 0, $DWORD);
202              
203 3 50       12 if ($DEBUG) {
204 0         0 printf("ObjectId: [%s]\n", _byteStringToGUID($objectId));
205 0         0 print "\tobjectSize: [$objectSize]\n";
206 0         0 print "\theaderObjects [$headerObjects]\n";
207 0         0 print "\treserved1 [$reserved1]\n";
208 0         0 print "\treserved2 [$reserved2]\n\n";
209             }
210              
211             # some sanity checks
212 3 50 33     29 return -1 if ($self->{'size'} && $objectSize > $self->{'size'});
213              
214             # Must begin with ASF_Header_Object GUID
215 3 50       13 return -1 unless _byteStringToGUID($objectId) eq $guidMapping{ASF_Header_Object};
216              
217 3         46 read($fh, $self->{'headerData'}, ($objectSize - 30));
218              
219 3         16 for (my $headerCounter = 0; $headerCounter < $headerObjects; $headerCounter++) {
220              
221 25         61 my $nextObjectGUID = $self->_readAndIncrementOffset($GUID);
222 25         49 my $nextObjectGUIDText = _byteStringToGUID($nextObjectGUID);
223 25         60 my $nextObjectSize = _parse64BitString($self->_readAndIncrementOffset($QWORD));
224              
225 25         70 my $nextObjectGUIDName = $reversedGUIDs{$nextObjectGUIDText};
226              
227             # FIX: calculate the next offset up-front to allow for
228             # object handlers that don't read the full object.
229 25         46 my $nextObjectOffset = $self->{'offset'} + ($nextObjectSize - (16 + 8));
230              
231 25 50       52 if ($DEBUG) {
232 0         0 print "nextObjectGUID: [" . $nextObjectGUIDText . "]\n";
233 0 0       0 print "nextObjectName: [" . (defined($nextObjectGUIDName) ? $nextObjectGUIDName : "") . "]\n";
234 0         0 print "nextObjectSize: [" . $nextObjectSize . "]\n";
235 0         0 print "nextObjectOffset: [" . $nextObjectOffset . "]\n";
236 0         0 print "\n";
237             }
238              
239             # FIX: don't error out on unknown objects (they are properly
240             # skipped below), report a debug message if we get an
241             # inconsistent object size. some sanity checks
242 25 50 33     123 if ((!defined $nextObjectSize) || ($nextObjectSize > $self->{'size'})) {
243              
244 0 0       0 print "Inconsistent object size: $nextObjectSize\n" if $DEBUG;
245 0         0 return -1;
246             }
247              
248             # FIX: fall-through to the bottom which sets the
249             # offset for the next object.
250 25 50       81 if (defined($nextObjectGUIDName)) {
251              
252             # start the different header types parsing
253 25 100 100     147 if ($nextObjectGUIDName eq 'ASF_File_Properties_Object') {
    100          
    100          
    100          
254            
255 3         18 $self->_parseASFFilePropertiesObject();
256             }
257            
258             elsif ($nextObjectGUIDName eq 'ASF_Content_Description_Object') {
259            
260 3         14 $self->_parseASFContentDescriptionObject();
261             }
262              
263             elsif ($nextObjectGUIDName eq 'ASF_Content_Encryption_Object' ||
264             $nextObjectGUIDName eq 'ASF_Extended_Content_Encryption_Object') {
265              
266 2         6 $self->_parseASFContentEncryptionObject();
267             }
268              
269             elsif ($nextObjectGUIDName eq 'ASF_Extended_Content_Description_Object') {
270            
271 2         10 $self->_parseASFExtendedContentDescriptionObject();
272             }
273              
274 25 100       98 if ($nextObjectGUIDName eq 'ASF_Stream_Properties_Object') {
    100          
    100          
275              
276 4         14 $self->_parseASFStreamPropertiesObject(0);
277 4         14 next;
278             }
279            
280             elsif ($nextObjectGUIDName eq 'ASF_Stream_Bitrate_Properties_Object') {
281              
282 2         8 $self->_parseASFStreamBitratePropertiesObject();
283             }
284              
285             elsif ($nextObjectGUIDName eq 'ASF_Header_Extension_Object') {
286              
287 3         15 $self->_parseASFHeaderExtensionObject();
288             }
289             }
290              
291             # FIX: set the next offset based on what we calculated
292             # up-front, rather then relying on our object handlers.
293 21         66 $self->{'offset'} = $nextObjectOffset;
294             }
295              
296             # Now work on the subtypes.
297 3         6 for my $stream (@{$self->{'STREAM'}}) {
  3         8  
298            
299             # insert stream bitrate
300 4         15 $stream->{'bitrate'} = $self->{'BITRATES'}->{ $stream->{'streamNumber'} };
301              
302 4 100       20 if ($reversedGUIDs{ $stream->{'stream_type_guid'} } eq 'ASF_Audio_Media') {
303              
304 3         14 my $audio = $self->_parseASFAudioMediaObject($stream);
305              
306 3         25 while (my ($key, $value) = each %$audio) {
307              
308 12         63 $self->{'INFO'}->{$key} = $value;
309             }
310             }
311             }
312              
313             # pull these out and normalize them.
314 3         12 my @arrayOk = qw(ALBUMARTIST GENRE COMPOSER AUTHOR);
315              
316 3         6 for my $ext (@{$self->{'EXT'}}) {
  3         8  
317              
318 5         9 while (my ($k,$v) = each %{$ext->{'content'}}) {
  42         143  
319              
320             # this gets both WM/Title and isVBR
321 37 100 100     194 next unless $v->{'name'} =~ s#^(?:WM/|is|replay)##i || $v->{'name'} =~ /^Author/;
322              
323 23         42 my $name = uc($v->{'name'});
324 23   100     66 my $value = $v->{'value'} || 0;
325              
326             # Append onto an existing item, as an array ref
327 23 100 100     71 if (exists $self->{'TAGS'}->{$name} && grep { /^$name$/ } @arrayOk) {
  8         65  
328              
329 1 50       5 if (ref($self->{'TAGS'}->{$name}) eq 'ARRAY') {
330              
331 0         0 push @{$self->{'TAGS'}->{$name}}, $value;
  0         0  
332              
333             } else {
334              
335 1         4 my $oldValue = delete $self->{'TAGS'}->{$name};
336              
337 1         2 @{$self->{'TAGS'}->{$name}} = ($oldValue, $value);
  1         5  
338             }
339              
340             } else {
341              
342 22         76 $self->{'TAGS'}->{$name} = $value;
343             }
344             }
345             }
346              
347 3         148 delete $self->{'EXT'};
348             }
349              
350             # We can't do anything about DRM'd files.
351             sub _parseASFContentEncryptionObject {
352 2     2   3 my $self = shift;
353              
354 2         6 $self->{'INFO'}->{'drm'} = 1;
355             }
356              
357             sub _parseASFFilePropertiesObject {
358 3     3   7 my $self = shift;
359              
360 3         7 my %info = ();
361              
362 3         20 $info{'fileid_guid'} = _byteStringToGUID($self->_readAndIncrementOffset($GUID));
363              
364 3         27 $info{'filesize'} = _parse64BitString($self->_readAndIncrementOffset($QWORD));
365              
366 3         12 $info{'creation_date'} = unpack('V', $self->_readAndIncrementOffset($QWORD));
367 3         31 $info{'creation_date_unix'} = _fileTimeToUnixTime($info{'creation_date'});
368              
369 3         9 $info{'data_packets'} = unpack('V', $self->_readAndIncrementOffset($QWORD));
370              
371 3         10 $info{'play_duration'} = _parse64BitString($self->_readAndIncrementOffset($QWORD));
372 3         12 $info{'send_duration'} = _parse64BitString($self->_readAndIncrementOffset($QWORD));
373 3         11 $info{'preroll'} = unpack('V', $self->_readAndIncrementOffset($QWORD));
374 3         15 $info{'playtime_seconds'} = ($info{'play_duration'} / 10000000)-($info{'preroll'} / 1000);
375              
376 3         7 $info{'flags_raw'} = unpack('V', $self->_readAndIncrementOffset(4));
377              
378 3 50       25 $info{'flags'}->{'broadcast'} = ($info{'flags_raw'} & 0x0001) ? 1 : 0;
379 3 100       14 $info{'flags'}->{'seekable'} = ($info{'flags_raw'} & 0x0002) ? 1 : 0;
380              
381 3         9 $info{'min_packet_size'} = unpack('V', $self->_readAndIncrementOffset($DWORD));
382 3         10 $info{'max_packet_size'} = unpack('V', $self->_readAndIncrementOffset($DWORD));
383 3         10 $info{'max_bitrate'} = unpack('V', $self->_readAndIncrementOffset($DWORD));
384              
385 3 50       24 $info{'bitrate'} = ($info{'playtime_seconds'}) ? ($info{'filesize'} * 8) / $info{'playtime_seconds'} : undef;
386 3         11 $self->{'INFO'} = \%info;
387             }
388              
389             sub _parseASFContentDescriptionObject {
390 3     3   7 my $self = shift;
391              
392 3         7 my %desc = ();
393 3         13 my @keys = qw(TITLE AUTHOR COPYRIGHT DESCRIPTION RATING);
394              
395             # populate the lengths of each key
396 3         8 for my $key (@keys) {
397 15         35 $desc{"_${key}length"} = unpack('v', $self->_readAndIncrementOffset($WORD));
398             }
399              
400             # now pull the data based on length
401 3         9 for my $key (@keys) {
402              
403 15         30 my $lengthKey = "_${key}length";
404 15         42 $desc{$key} = _UTF16ToUTF8($self->_readAndIncrementOffset($desc{$lengthKey}));
405              
406 15         44 delete $desc{$lengthKey};
407             }
408              
409 3         14 $self->{'TAGS'} = \%desc;
410             }
411              
412             sub _parseASFExtendedContentDescriptionObject {
413 2     2   3 my $self = shift;
414              
415 2         6 my %ext = ();
416              
417 2         5 my $content_count = unpack('v', $self->_readAndIncrementOffset($WORD));
418              
419 2         10 for (my $id = 0; $id < $content_count; $id++) {
420              
421 29         63 my $name_length = unpack('v', $self->_readAndIncrementOffset($WORD));
422 29         168 my $name = _denull( $self->_readAndIncrementOffset($name_length) );
423 29         82 my $data_type = unpack('v', $self->_readAndIncrementOffset($WORD));
424 29         68 my $data_length = unpack('v', $self->_readAndIncrementOffset($WORD));
425 29         61 my $value = $self->_bytesToValue($data_type, $self->_readAndIncrementOffset($data_length));
426              
427 29 50 33     73 if ( $DEBUG && uc($name) ne 'WM/PICTURE' ) {
428 0         0 print "Ext Cont Desc: $id";
429 0         0 print "\tname = $name\n";
430 0         0 print "\tvalue = $value\n";
431 0         0 print "\ttype = $data_type\n";
432 0         0 print "\tlength = $data_length\n";
433 0         0 print "\n";
434             }
435              
436             # Parse out the WM/Picture structure into something we can use.
437             #
438             # typedef struct _WMPicture {
439             # LPWSTR pwszMIMEType;
440             # BYTE bPictureType;
441             # LPWSTR pwszDescription;
442             # DWORD dwDataLen;
443             # BYTE* pbData;
444             # };
445              
446 29 100       69 if (uc($name) eq 'WM/PICTURE') {
447              
448 1         3 my $image_type_id = unpack('v', substr($value, 0, 1));
449 1         3 my $image_size = unpack('v', substr($value, 1, $DWORD));
450 1         2 my $image_mime = '';
451 1         3 my $image_desc = '';
452 1         1 my $image_data = '';
453 1         2 my $offset = 5;
454 1         1 my $byte_pair = '';
455              
456 1         2 do {
457 11         13 $byte_pair = substr($value, $offset, 2);
458 11         11 $offset += 2;
459 11         20 $image_mime .= $byte_pair;
460              
461             } while ($byte_pair ne "\x00\x00");
462              
463 1         3 do {
464 10         43 $byte_pair = substr($value, $offset, 2);
465 10         9 $offset += 2;
466 10         20 $image_desc .= $byte_pair;
467              
468             } while ($byte_pair ne "\x00\x00");
469              
470 1         3 $image_mime = _UTF16ToUTF8($image_mime);
471 1         2 $image_desc = _UTF16ToUTF8($image_desc);
472 1         5 $image_data = substr($value, $offset, $image_size);
473              
474 1         3 $value = {
475             'TYPE' => $image_mime,
476             'DATA' => $image_data,
477             };
478              
479 1 50       5 if ($DEBUG) {
480 0         0 print "Ext Cont Desc: $id";
481 0         0 print "\tname = $name\n";
482 0         0 print "\timage_type_id = $image_type_id\n";
483 0         0 print "\timage_size = $image_size\n";
484 0         0 print "\timage_mime = $image_mime\n";
485 0         0 print "\timage_desc = $image_desc\n";
486 0         0 print "\n";
487             }
488             }
489              
490 29         178 $ext{'content'}->{$id} = {
491             'name' => $name,
492             'value' => $value,
493             };
494             }
495              
496 2         4 push @{$self->{'EXT'}}, \%ext;
  2         10  
497             }
498              
499             sub _parseASFStreamPropertiesObject {
500 4     4   8 my $self = shift;
501 4         41 my $inline = shift;
502              
503 4         9 my %stream = ();
504 4         7 my $streamNumber;
505              
506             # Stream Properties Object: (mandatory, one per media stream)
507             # Field Name Field Type Size (bits)
508             # Object ID GUID 128 GUID for stream properties object - ASF_Stream_Properties_Object
509             # Object Size QWORD 64 size of stream properties object, including 78 bytes of
510             # Stream Properties Object header
511             # Stream Type GUID 128 ASF_Audio_Media, ASF_Video_Media or ASF_Command_Media
512             # Error Correction Type GUID 128 ASF_Audio_Spread for audio-only streams,
513             # ASF_No_Error_Correction for other stream types
514             # Time Offset QWORD 64 100-nanosecond units. typically zero. added to all
515             # timestamps of samples in the stream
516             # Type-Specific Data Length DWORD 32 number of bytes for Type-Specific Data field
517             # Error Correction Data Length DWORD 32 number of bytes for Error Correction Data field
518             # Flags WORD 16
519             # * Stream Number bits 7 (0x007F) number of this stream. 1 <= valid <= 127
520             # * Reserved bits 8 (0x7F80) reserved - set to zero
521             # * Encrypted Content Flag bits 1 (0x8000) stream contents encrypted if set
522             # Reserved DWORD 32 reserved - set to zero
523             # Type-Specific Data BYTESTREAM variable type-specific format data, depending on value of Stream Type
524             # Error Correction Data BYTESTREAM variable error-correction-specific format data, depending on
525             # value of Error Correct Type
526             #
527             # There is one ASF_Stream_Properties_Object for each stream (audio, video) but the
528             # stream number isn't known until halfway through decoding the structure, hence it
529             # it is decoded to a temporary variable and then stuck in the appropriate index later
530 4 50       12 my $method = $inline ? '_readAndIncrementInlineOffset' : '_readAndIncrementOffset';
531              
532 4         13 $stream{'stream_type'} = $self->$method($GUID);
533 4         12 $stream{'stream_type_guid'} = _byteStringToGUID($stream{'stream_type'});
534 4         13 $stream{'error_correct_type'} = $self->$method($GUID);
535 4         26 $stream{'error_correct_guid'} = _byteStringToGUID($stream{'error_correct_type'});
536              
537 4         15 $stream{'time_offset'} = unpack('v', $self->$method($QWORD));
538 4         14 $stream{'type_data_length'} = unpack('V', $self->$method($DWORD));
539 4         14 $stream{'error_data_length'} = unpack('V', $self->$method($DWORD));
540 4         20 $stream{'flags_raw'} = unpack('v', $self->$method($WORD));
541 4         12 $stream{'streamNumber'} = $stream{'flags_raw'} & 0x007F;
542 4         14 $stream{'flags'}{'encrypted'} = ($stream{'flags_raw'} & 0x8000);
543              
544             # Skip the reserved DWORD
545 4         12 $self->$method($DWORD);
546              
547             # XXX: If a file has bogus data for either of these length values
548             # it will screw up parsing the rest of the header
549             # i.e. mms://c9l.earthcache.net/wlc-01.media.globix.net/COMP005996BCT1_wqxr_live_hi.wmv
550 4         11 $stream{'type_specific_data'} = $self->$method($stream{'type_data_length'});
551 4         12 $stream{'error_correct_data'} = $self->$method($stream{'error_data_length'});
552              
553 4         7 push @{$self->{'STREAM'}}, \%stream;
  4         14  
554             }
555              
556             sub _parseASFStreamBitratePropertiesObject {
557 2     2   4 my $self = shift;
558            
559 2         4 my $bitrates = {};
560            
561 2         6 my $count = unpack('v', $self->_readAndIncrementOffset($WORD));
562            
563             # Read each bitrate record
564 2         14 for ( 1..$count ) {
565 2         5 my $stream = unpack('v', $self->_readAndIncrementOffset($WORD)) & 0x007F;
566 2         11 my $bitrate = unpack('V', $self->_readAndIncrementOffset($DWORD));
567            
568 2         8 $bitrates->{$stream} = $bitrate;
569             }
570            
571 2         6 $self->{'BITRATES'} = $bitrates;
572             }
573              
574             sub _parseASFAudioMediaObject {
575 3     3   5 my $self = shift;
576 3         4 my $stream = shift;
577              
578             # Field Name Field Type Size (bits)
579             # Codec ID / Format Tag WORD 16 unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure
580             #
581             # Number of Channels WORD 16 number of channels of audio - defined as nChannels field of WAVEFORMATEX structure
582             #
583             # Samples Per Second DWORD 32 in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure
584             #
585             # Average number of Bytes/sec DWORD 32 bytes/sec of audio stream - defined as nAvgBytesPerSec field of WAVEFORMATEX structure
586             #
587             # Block Alignment WORD 16 block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure
588             #
589             # Bits per sample WORD 16 bits per sample of mono data. set to zero for variable bitrate codecs.
590             # defined as wBitsPerSample field of WAVEFORMATEX structure
591             #
592             # Codec Specific Data Size WORD 16 size in bytes of Codec Specific Data buffer -
593             # defined as cbSize field of WAVEFORMATEX structure
594             #
595             # Codec Specific Data BYTESTREAM variable array of codec-specific data bytes
596              
597 3         34 $stream->{'audio'} = $self->_parseWavFormat(substr($stream->{'type_specific_data'}, 0, $GUID));
598              
599 3         11 return $stream->{'audio'};
600             }
601              
602             sub _parseWavFormat {
603 3     3   6 my $self = shift;
604 3         11 my $data = shift;
605              
606 3         8 my $wFormatTag = unpack('v', substr($data, 0, 2));
607              
608 3         13 my %wav = (
609             'codec' => _RIFFwFormatTagLookup($wFormatTag),
610             'channels' => unpack('v', substr($data, 2, $WORD)),
611             'sample_rate' => unpack('V', substr($data, 4, $DWORD)),
612             # See bitrate in _parseASFFilePropertiesObject() for the correct calculation.
613             #'bitrate' => unpack('v', substr($data, 8, $DWORD)) * 8,
614             'bits_per_sample' => unpack('v', substr($data, 14, $WORD)),
615             );
616              
617 3 50 33     28 if ($wFormatTag == 0x0001 || $wFormatTag == 0x0163) {
618 0         0 $wav{'lossless'} = 1;
619             }
620              
621 3         12 return \%wav;
622             }
623              
624             sub _parseASFExtendedStreamPropertiesObject {
625 2     2   4 my $self = shift;
626 2         5 my $size = shift;
627              
628 2         5 my $offset = $self->{'inlineOffset'};
629              
630 2         7 my %ext = (
631             startTime => $self->_bytesToValue(4, $self->_readAndIncrementInlineOffset($QWORD)),
632             endTime => $self->_bytesToValue(4, $self->_readAndIncrementInlineOffset($QWORD)),
633             dataBitrate => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
634             bufferSize => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
635             bufferFullness => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
636             altDataBitrate => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
637             altBufferSize => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
638             altBufferFullness => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
639             maxObjectSize => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
640             flags => $self->_bytesToValue(3, $self->_readAndIncrementInlineOffset($DWORD)),
641             streamNumber => $self->_bytesToValue(5, $self->_readAndIncrementInlineOffset($WORD)),
642             streamLanguageID => $self->_bytesToValue(5, $self->_readAndIncrementInlineOffset($WORD)),
643             averageTimePerFrame => $self->_bytesToValue(4, $self->_readAndIncrementInlineOffset($QWORD)),
644             streamNameCount => $self->_bytesToValue(5, $self->_readAndIncrementInlineOffset($WORD)),
645             payloadExtensionCount => $self->_bytesToValue(5, $self->_readAndIncrementInlineOffset($WORD)),
646             );
647              
648 2         14 for (my $s = 0; $s < $ext{'streamNameCount'}; $s++) {
649            
650 0   0     0 my $language = unpack('v', $self->_readAndIncrementInlineOffset($WORD)) || last;
651 0         0 my $length = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
652              
653 0         0 $self->_readAndIncrementInlineOffset($length);
654 0         0 $self->{'inlineOffset'} += 4;
655             }
656              
657 2         11 for (my $p = 0; $p < $ext{'payloadExtensionCount'}; $p++) {
658            
659 0 0       0 $self->_readAndIncrementInlineOffset(18) || last;
660 0         0 my $length = unpack('V', $self->_readAndIncrementInlineOffset($DWORD));
661              
662 0         0 $self->_readAndIncrementInlineOffset($length);
663 0         0 $self->{'inlineOffset'} += 22;
664             }
665              
666 2 50       19 if (($self->{'inlineOffset'} - $offset) < $size) {
667              
668 0         0 my $nextObjectGUID = _byteStringToGUID($self->_readAndIncrementInlineOffset($GUID));
669 0   0     0 my $nextObjectName = $reversedGUIDs{$nextObjectGUID} || 'ASF_Unknown_Object';
670 0         0 my $nextObjectSize = unpack('v', $self->_readAndIncrementInlineOffset($QWORD));
671              
672 0 0       0 if ($DEBUG) {
673 0         0 print "extendedStreamPropertiesObject nextObjectGUID: [" . $nextObjectGUID . "]\n";
674 0         0 print "extendedStreamPropertiesObject nextObjectName: [" . $nextObjectName . "]\n";
675 0         0 print "extendedStreamPropertiesObject nextObjectSize: [" . $nextObjectSize . "]\n";
676 0         0 print "\n";
677             }
678              
679 0 0 0     0 if (defined $nextObjectName && $nextObjectName eq 'ASF_Stream_Properties_Object') {
680 0         0 $self->_parseASFStreamPropertiesObject(1);
681             }
682             }
683             }
684              
685             sub _parseASFHeaderExtensionObject {
686 3     3   6 my $self = shift;
687              
688 3         7 my %ext = ();
689              
690 3         18 $ext{'reserved_1'} = _byteStringToGUID($self->_readAndIncrementOffset($GUID));
691 3         10 $ext{'reserved_2'} = unpack('v', $self->_readAndIncrementOffset($WORD));
692              
693 3         12 $ext{'extension_data_size'} = unpack('V', $self->_readAndIncrementOffset($DWORD));
694 3         10 $ext{'extension_data'} = $self->_readAndIncrementOffset($ext{'extension_data_size'});
695              
696             # Set these so we can use a convience method.
697 3         15 $self->{'inlineData'} = $ext{'extension_data'};
698 3         8 $self->{'inlineOffset'} = 0;
699              
700 3 50       11 if ($DEBUG) {
701 0         0 print "Working on an ASF_Header_Extension_Object:\n\n";
702             }
703              
704 3         16 while ($self->{'inlineOffset'} < $ext{'extension_data_size'}) {
705              
706 12   50     27 my $nextObjectGUID = _byteStringToGUID($self->_readAndIncrementInlineOffset($GUID)) || last;
707 12   50     44 my $nextObjectName = $reversedGUIDs{$nextObjectGUID} || 'ASF_Unknown_Object';
708 12         26 my $nextObjectSize = unpack('v', $self->_readAndIncrementInlineOffset($QWORD));
709              
710             # some sanity checks
711 12 50 33     67 next if $nextObjectSize == 0 || $nextObjectSize > $ext{'extension_data_size'};
712 12 50       27 next unless defined $nextObjectName;
713              
714 12 50       19 if ($DEBUG) {
715 0         0 print "\textensionObject nextObjectGUID: [$nextObjectGUID]\n";
716 0         0 print "\textensionObject nextObjectName: [$nextObjectName]\n";
717 0         0 print "\textensionObject nextObjectSize: [$nextObjectSize]\n";
718 0         0 print "\n";
719             }
720              
721             # We only handle this object type for now.
722 12 100 100     60 if ($nextObjectName eq 'ASF_Metadata_Library_Object' ||
    100          
723             $nextObjectName eq 'ASF_Metadata_Object') {
724              
725 4         9 my $content_count = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
726              
727 4 50       11 if ($DEBUG) {
728 0         0 print "\tContent Count: [$content_count]\n";
729             }
730              
731             # Language List Index WORD 16
732             # Stream Number WORD 16
733             # Name Length WORD 16
734             # Data Type WORD 16
735             # Data Length DWORD 32
736             # Name WCHAR varies
737             # Data See below varies
738 4         12 for (my $id = 0; $id < $content_count; $id++) {
739              
740 12         26 my $language_list = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
741 12         35 my $stream_number = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
742 12         25 my $name_length = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
743 12         26 my $data_type = unpack('v', $self->_readAndIncrementInlineOffset($WORD));
744 12         23 my $data_length = unpack('V', $self->_readAndIncrementInlineOffset($DWORD));
745 12         23 my $name = _denull($self->_readAndIncrementInlineOffset($name_length));
746 12         37 my $value = $self->_bytesToValue($data_type, $self->_readAndIncrementInlineOffset($data_length));
747              
748 12 100       29 if ($name eq 'WM/Picture') {
749            
750 1         4 $value = {
751             'TYPE' => 'image/jpg',
752             'DATA' => $value,
753             };
754             }
755              
756 12         45 $ext{'content'}->{$id}->{'name'} = $name;
757 12         24 $ext{'content'}->{$id}->{'value'} = $value;
758              
759 12 50       48 if ($DEBUG) {
760 0         0 print "\t$nextObjectName: $id\n";
761 0         0 print "\t\tname = $name\n";
762 0         0 print "\t\tvalue = $value\n";
763 0         0 print "\t\ttype = $data_type\n";
764 0         0 print "\t\tlength = $data_length\n";
765 0         0 print "\n";
766             }
767             }
768              
769             } elsif ($nextObjectName eq 'ASF_Extended_Stream_Properties_Object') {
770              
771 2         15 $self->_parseASFExtendedStreamPropertiesObject($nextObjectSize - $GUID - $QWORD);
772              
773             } else {
774              
775             # Only increment the offset if we couldn't parse the object.
776 6         24 $self->{'inlineOffset'} += ($nextObjectSize - $GUID - $QWORD);
777             }
778             }
779              
780 3         9 delete $ext{'extension_data'};
781 3         10 delete $self->{'inlineData'};
782 3         5 delete $self->{'inlineOffset'};
783              
784 3         6 push @{$self->{'EXT'}}, \%ext;
  3         12  
785             }
786              
787             sub _bytesToValue {
788 71     71   108 my ($self, $data_type, $value) = @_;
789              
790             # 0x0000 Unicode string. The data consists of a sequence of Unicode characters.
791             #
792             # 0x0001 BYTE array. The type of the data is implementation-specific.
793             #
794             # 0x0002 BOOL. The data is 2 bytes long and should be interpreted as a
795             # 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values.
796             #
797             # 0x0003 DWORD. The data is 4 bytes long - 32-bit unsigned integer.
798             #
799             # 0x0004 QWORD. The data is 8 bytes long - 64-bit unsigned integer.
800             #
801             # 0x0005 WORD. The data is 2 bytes long - 16-bit unsigned integer.
802             #
803             # 0x0006 GUID. The data is 16 bytes long - 128-bit GUID.
804              
805 71 100 100     414 if ($data_type == 0) {
    100          
    100          
    100          
    100          
    50          
806              
807 27         52 $value = _UTF16ToUTF8($value);
808              
809             } elsif ($data_type == 1) {
810              
811             # Leave byte arrays as is.
812              
813             } elsif ($data_type == 2 || $data_type == 5) {
814              
815 12         19 $value = unpack('v', $value);
816              
817             } elsif ($data_type == 3) {
818              
819 20         26 $value = unpack('V', $value);
820              
821             } elsif ($data_type == 4) {
822              
823 7         14 $value = _parse64BitString($value);
824              
825             } elsif ($data_type == 6) {
826              
827 2         5 $value = _byteStringToGUID($value);
828             }
829              
830 71         211 return $value;
831             }
832              
833             sub _parse64BitString {
834 41     41   134 my ($low,$high) = unpack('VV', shift);
835              
836 41         88 return $high * 2 ** 32 + $low;
837             }
838              
839             sub _knownGUIDs {
840              
841 3     3   163 my %guidMapping = (
842              
843             'ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A',
844             'ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8',
845             'ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8',
846             'ASF_Script_Command_Object' => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6',
847             'ASF_No_Error_Correction' => '20FB5700-5B55-11CF-A8FD-00805F5C442B',
848             'ASF_Content_Branding_Object' => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E',
849             'ASF_Content_Encryption_Object' => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E',
850             'ASF_Digital_Signature_Object' => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E',
851             'ASF_Extended_Content_Encryption_Object' => '298AE614-2622-4C17-B935-DAE07EE9289C',
852             'ASF_Simple_Index_Object' => '33000890-E5B1-11CF-89F4-00A0C90349CB',
853             'ASF_Degradable_JPEG_Media' => '35907DE0-E415-11CF-A917-00805F5C442B',
854             'ASF_Payload_Extension_System_Timecode' => '399595EC-8667-4E2D-8FDB-98814CE76C1E',
855             'ASF_Binary_Media' => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343',
856             'ASF_Timecode_Index_Object' => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C',
857             'ASF_Metadata_Library_Object' => '44231C94-9498-49D1-A141-1D134E457054',
858             'ASF_Reserved_3' => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6',
859             'ASF_Reserved_4' => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB',
860             'ASF_Command_Media' => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6',
861             'ASF_Header_Extension_Object' => '5FBF03B5-A92E-11CF-8EE3-00C00C205365',
862             'ASF_Media_Object_Index_Parameters_Obj' => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7',
863             'ASF_Header_Object' => '75B22630-668E-11CF-A6D9-00AA0062CE6C',
864             'ASF_Content_Description_Object' => '75B22633-668E-11CF-A6D9-00AA0062CE6C',
865             'ASF_Error_Correction_Object' => '75B22635-668E-11CF-A6D9-00AA0062CE6C',
866             'ASF_Data_Object' => '75B22636-668E-11CF-A6D9-00AA0062CE6C',
867             'ASF_Web_Stream_Media_Subtype' => '776257D4-C627-41CB-8F81-7AC7FF1C40CC',
868             'ASF_Stream_Bitrate_Properties_Object' => '7BF875CE-468D-11D1-8D82-006097C9A2B2',
869             'ASF_Language_List_Object' => '7C4346A9-EFE0-4BFC-B229-393EDE415C85',
870             'ASF_Codec_List_Object' => '86D15240-311D-11D0-A3A4-00A0C90348F6',
871             'ASF_Reserved_2' => '86D15241-311D-11D0-A3A4-00A0C90348F6',
872             'ASF_File_Properties_Object' => '8CABDCA1-A947-11CF-8EE4-00C00C205365',
873             'ASF_File_Transfer_Media' => '91BD222C-F21C-497A-8B6D-5AA86BFC0185',
874             'ASF_Advanced_Mutual_Exclusion_Object' => 'A08649CF-4775-4670-8A16-6E35357566CD',
875             'ASF_Bandwidth_Sharing_Object' => 'A69609E6-517B-11D2-B6AF-00C04FD908E9',
876             'ASF_Reserved_1' => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365',
877             'ASF_Bandwidth_Sharing_Exclusive' => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9',
878             'ASF_Bandwidth_Sharing_Partial' => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9',
879             'ASF_JFIF_Media' => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B',
880             'ASF_Stream_Properties_Object' => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365',
881             'ASF_Video_Media' => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B',
882             'ASF_Audio_Spread' => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220',
883             'ASF_Metadata_Object' => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA',
884             'ASF_Payload_Ext_Syst_Sample_Duration' => 'C6BD9450-867F-4907-83A3-C77921B733AD',
885             'ASF_Group_Mutual_Exclusion_Object' => 'D1465A40-5A79-4338-B71B-E36B8FD6C249',
886             'ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850',
887             'ASF_Stream_Prioritization_Object' => 'D4FED15B-88D3-454F-81F0-ED5C45999E24',
888             'ASF_Payload_Ext_System_Content_Type' => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC',
889             'ASF_Index_Object' => 'D6E229D3-35DA-11D1-9034-00A0C90349BE',
890             'ASF_Bitrate_Mutual_Exclusion_Object' => 'D6E229DC-35DA-11D1-9034-00A0C90349BE',
891             'ASF_Index_Parameters_Object' => 'D6E229DF-35DA-11D1-9034-00A0C90349BE',
892             'ASF_Mutex_Language' => 'D6E22A00-35DA-11D1-9034-00A0C90349BE',
893             'ASF_Mutex_Bitrate' => 'D6E22A01-35DA-11D1-9034-00A0C90349BE',
894             'ASF_Mutex_Unknown' => 'D6E22A02-35DA-11D1-9034-00A0C90349BE',
895             'ASF_Web_Stream_Format' => 'DA1E6B13-8359-4050-B398-388E965BF00C',
896             'ASF_Payload_Ext_System_File_Name' => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B',
897             'ASF_Marker_Object' => 'F487CD01-A951-11CF-8EE6-00C00C205365',
898             'ASF_Timecode_Index_Parameters_Object' => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24',
899             'ASF_Audio_Media' => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B',
900             'ASF_Media_Object_Index_Object' => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C',
901             'ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE',
902             'ASF_Index_Placeholder_Object' => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2',
903             'ASF_Compatibility_Object' => '26F18B5D-4584-47EC-9F5F-0E651F0452C9',
904             );
905              
906 3         91 return %guidMapping;
907             }
908              
909             sub _knownParsers {
910            
911             return (
912 3     3   51 'ASF_File_Properties_Object' => \&_parseASFFilePropertiesObject,
913             'ASF_Content_Description_Object' => \&_parseASFContentDescriptionObject,
914             'ASF_Stream_Bitrate_Properties_Object' => \&_parseASFStreamBitratePropertiesObject,
915            
916             # We don't currently use most of these, so no point in spending time parsing them
917             #'ASF_Extended_Content_Description_Object' => \&_parseASFExtendedContentDescriptionObject,
918             #'ASF_Content_Encryption_Object' => \&_parseASFContentEncryptionObject,
919             #'ASF_Extended_Content_Encryption_Object' => \&_parseASFContentEncryptionObject,
920             #'ASF_Stream_Properties_Object' => \&_parseASFStreamPropertiesObject,
921             );
922             }
923              
924             sub _RIFFwFormatTagLookup {
925 3     3   7 my $wFormatTag = shift;
926              
927 3         703 my %formatTags = (
928             0x0000 => 'Microsoft Unknown Wave Format',
929             0x0001 => 'Pulse Code Modulation (PCM)',
930             0x0002 => 'Microsoft ADPCM',
931             0x0003 => 'IEEE Float',
932             0x0004 => 'Compaq Computer VSELP',
933             0x0005 => 'IBM CVSD',
934             0x0006 => 'Microsoft A-Law',
935             0x0007 => 'Microsoft mu-Law',
936             0x0008 => 'Microsoft DTS',
937             0x000A => 'Windows Media Audio 9 Voice',
938             0x000B => 'Microsoft Windows Media RT Voice Audio',
939             0x0010 => 'OKI ADPCM',
940             0x0011 => 'Intel DVI/IMA ADPCM',
941             0x0012 => 'Videologic MediaSpace ADPCM',
942             0x0013 => 'Sierra Semiconductor ADPCM',
943             0x0014 => 'Antex Electronics G.723 ADPCM',
944             0x0015 => 'DSP Solutions DigiSTD',
945             0x0016 => 'DSP Solutions DigiFIX',
946             0x0017 => 'Dialogic OKI ADPCM',
947             0x0018 => 'MediaVision ADPCM',
948             0x0019 => 'Hewlett-Packard CU',
949             0x0020 => 'Yamaha ADPCM',
950             0x0021 => 'Speech Compression Sonarc',
951             0x0022 => 'DSP Group TrueSpeech',
952             0x0023 => 'Echo Speech EchoSC1',
953             0x0024 => 'Audiofile AF36',
954             0x0025 => 'Audio Processing Technology APTX',
955             0x0026 => 'AudioFile AF10',
956             0x0027 => 'Prosody 1612',
957             0x0028 => 'LRC',
958             0x0030 => 'Dolby AC2',
959             0x0031 => 'Microsoft GSM 6.10',
960             0x0032 => 'MSNAudio',
961             0x0033 => 'Antex Electronics ADPCME',
962             0x0034 => 'Control Resources VQLPC',
963             0x0035 => 'DSP Solutions DigiREAL',
964             0x0036 => 'DSP Solutions DigiADPCM',
965             0x0037 => 'Control Resources CR10',
966             0x0038 => 'Natural MicroSystems VBXADPCM',
967             0x0039 => 'Crystal Semiconductor IMA ADPCM',
968             0x003A => 'EchoSC3',
969             0x003B => 'Rockwell ADPCM',
970             0x003C => 'Rockwell Digit LK',
971             0x003D => 'Xebec',
972             0x0040 => 'Antex Electronics G.721 ADPCM',
973             0x0041 => 'G.728 CELP',
974             0x0042 => 'MSG723',
975             0x0050 => 'MPEG Layer-2 or Layer-1',
976             0x0052 => 'RT24',
977             0x0053 => 'PAC',
978             0x0055 => 'MPEG Layer-3',
979             0x0059 => 'Lucent G.723',
980             0x0060 => 'Cirrus',
981             0x0061 => 'ESPCM',
982             0x0062 => 'Voxware',
983             0x0063 => 'Canopus Atrac',
984             0x0064 => 'G.726 ADPCM',
985             0x0065 => 'G.722 ADPCM',
986             0x0066 => 'DSAT',
987             0x0067 => 'DSAT Display',
988             0x0069 => 'Voxware Byte Aligned',
989             0x0070 => 'Voxware AC8',
990             0x0071 => 'Voxware AC10',
991             0x0072 => 'Voxware AC16',
992             0x0073 => 'Voxware AC20',
993             0x0074 => 'Voxware MetaVoice',
994             0x0075 => 'Voxware MetaSound',
995             0x0076 => 'Voxware RT29HW',
996             0x0077 => 'Voxware VR12',
997             0x0078 => 'Voxware VR18',
998             0x0079 => 'Voxware TQ40',
999             0x0080 => 'Softsound',
1000             0x0081 => 'Voxware TQ60',
1001             0x0082 => 'MSRT24',
1002             0x0083 => 'G.729A',
1003             0x0084 => 'MVI MV12',
1004             0x0085 => 'DF G.726',
1005             0x0086 => 'DF GSM610',
1006             0x0088 => 'ISIAudio',
1007             0x0089 => 'Onlive',
1008             0x0091 => 'SBC24',
1009             0x0092 => 'Dolby AC3 SPDIF',
1010             0x0093 => 'MediaSonic G.723',
1011             0x0094 => 'Aculab PLC Prosody 8kbps',
1012             0x0097 => 'ZyXEL ADPCM',
1013             0x0098 => 'Philips LPCBB',
1014             0x0099 => 'Packed',
1015             0x00FF => 'AAC',
1016             0x0100 => 'Rhetorex ADPCM',
1017             0x0101 => 'IBM mu-law',
1018             0x0102 => 'IBM A-law',
1019             0x0103 => 'IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM)',
1020             0x0111 => 'Vivo G.723',
1021             0x0112 => 'Vivo Siren',
1022             0x0123 => 'Digital G.723',
1023             0x0125 => 'Sanyo LD ADPCM',
1024             0x0130 => 'Sipro Lab Telecom ACELP NET',
1025             0x0131 => 'Sipro Lab Telecom ACELP 4800',
1026             0x0132 => 'Sipro Lab Telecom ACELP 8V3',
1027             0x0133 => 'Sipro Lab Telecom G.729',
1028             0x0134 => 'Sipro Lab Telecom G.729A',
1029             0x0135 => 'Sipro Lab Telecom Kelvin',
1030             0x0140 => 'Windows Media Video V8',
1031             0x0150 => 'Qualcomm PureVoice',
1032             0x0151 => 'Qualcomm HalfRate',
1033             0x0155 => 'Ring Zero Systems TUB GSM',
1034             0x0160 => 'Microsoft Audio 1',
1035             0x0161 => 'Windows Media Audio V7 / V8 / V9',
1036             0x0162 => 'Windows Media Audio Professional V9',
1037             0x0163 => 'Windows Media Audio Lossless V9',
1038             0x0200 => 'Creative Labs ADPCM',
1039             0x0202 => 'Creative Labs Fastspeech8',
1040             0x0203 => 'Creative Labs Fastspeech10',
1041             0x0210 => 'UHER Informatic GmbH ADPCM',
1042             0x0220 => 'Quarterdeck',
1043             0x0230 => 'I-link Worldwide VC',
1044             0x0240 => 'Aureal RAW Sport',
1045             0x0250 => 'Interactive Products HSX',
1046             0x0251 => 'Interactive Products RPELP',
1047             0x0260 => 'Consistent Software CS2',
1048             0x0270 => 'Sony SCX',
1049             0x0300 => 'Fujitsu FM Towns Snd',
1050             0x0400 => 'BTV Digital',
1051             0x0401 => 'Intel Music Coder',
1052             0x0450 => 'QDesign Music',
1053             0x0680 => 'VME VMPCM',
1054             0x0681 => 'AT&T Labs TPC',
1055             0x08AE => 'ClearJump LiteWave',
1056             0x1000 => 'Olivetti GSM',
1057             0x1001 => 'Olivetti ADPCM',
1058             0x1002 => 'Olivetti CELP',
1059             0x1003 => 'Olivetti SBC',
1060             0x1004 => 'Olivetti OPR',
1061             0x1100 => 'Lernout & Hauspie Codec (0x1100)',
1062             0x1101 => 'Lernout & Hauspie CELP Codec (0x1101)',
1063             0x1102 => 'Lernout & Hauspie SBC Codec (0x1102)',
1064             0x1103 => 'Lernout & Hauspie SBC Codec (0x1103)',
1065             0x1104 => 'Lernout & Hauspie SBC Codec (0x1104)',
1066             0x1400 => 'Norris',
1067             0x1401 => 'AT&T ISIAudio',
1068             0x1500 => 'Soundspace Music Compression',
1069             0x181C => 'VoxWare RT24 Speech',
1070             0x1FC4 => 'NCT Soft ALF2CD (www.nctsoft.com)',
1071             0x2000 => 'Dolby AC3',
1072             0x2001 => 'Dolby DTS',
1073             0x2002 => 'WAVE_FORMAT_14_4',
1074             0x2003 => 'WAVE_FORMAT_28_8',
1075             0x2004 => 'WAVE_FORMAT_COOK',
1076             0x2005 => 'WAVE_FORMAT_DNET',
1077             0x674F => 'Ogg Vorbis 1',
1078             0x6750 => 'Ogg Vorbis 2',
1079             0x6751 => 'Ogg Vorbis 3',
1080             0x676F => 'Ogg Vorbis 1+',
1081             0x6770 => 'Ogg Vorbis 2+',
1082             0x6771 => 'Ogg Vorbis 3+',
1083             0x7A21 => 'GSM-AMR (CBR, no SID)',
1084             0x7A22 => 'GSM-AMR (VBR, including SID)',
1085             0xFFFE => 'WAVE_FORMAT_EXTENSIBLE',
1086             0xFFFF => 'WAVE_FORMAT_DEVELOPMENT',
1087             );
1088              
1089 3         98 return $formatTags{$wFormatTag};
1090             }
1091              
1092             sub _guidToByteString {
1093 0     0   0 my $guidString = shift;
1094              
1095             # Microsoft defines these 16-byte (128-bit) GUIDs as:
1096             # first 4 bytes are in little-endian order
1097             # next 2 bytes are appended in little-endian order
1098             # next 2 bytes are appended in little-endian order
1099             # next 2 bytes are appended in big-endian order
1100             # next 6 bytes are appended in big-endian order
1101              
1102             # AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string:
1103             # $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
1104              
1105 0         0 my $hexByteCharString;
1106              
1107 0         0 $hexByteCharString = chr(hex(substr($guidString, 6, 2)));
1108 0         0 $hexByteCharString .= chr(hex(substr($guidString, 4, 2)));
1109 0         0 $hexByteCharString .= chr(hex(substr($guidString, 2, 2)));
1110 0         0 $hexByteCharString .= chr(hex(substr($guidString, 0, 2)));
1111              
1112 0         0 $hexByteCharString .= chr(hex(substr($guidString, 11, 2)));
1113 0         0 $hexByteCharString .= chr(hex(substr($guidString, 9, 2)));
1114              
1115 0         0 $hexByteCharString .= chr(hex(substr($guidString, 16, 2)));
1116 0         0 $hexByteCharString .= chr(hex(substr($guidString, 14, 2)));
1117              
1118 0         0 $hexByteCharString .= chr(hex(substr($guidString, 19, 2)));
1119 0         0 $hexByteCharString .= chr(hex(substr($guidString, 21, 2)));
1120              
1121 0         0 $hexByteCharString .= chr(hex(substr($guidString, 24, 2)));
1122 0         0 $hexByteCharString .= chr(hex(substr($guidString, 26, 2)));
1123 0         0 $hexByteCharString .= chr(hex(substr($guidString, 28, 2)));
1124 0         0 $hexByteCharString .= chr(hex(substr($guidString, 30, 2)));
1125 0         0 $hexByteCharString .= chr(hex(substr($guidString, 32, 2)));
1126 0         0 $hexByteCharString .= chr(hex(substr($guidString, 34, 2)));
1127              
1128 0         0 return $hexByteCharString;
1129             }
1130              
1131             sub _byteStringToGUID {
1132 56     56   282 my $buff = unpack('H*',pack('NnnNN',unpack('VvvNN',$_[0])));
1133 56         471 $buff =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
1134 56         193 return uc($buff);
1135             }
1136              
1137             sub _fileTimeToUnixTime {
1138 3     3   12 my $filetime = shift;
1139 3   50     19 my $round = shift || 1;
1140              
1141             # filetime is a 64-bit unsigned integer representing
1142             # the number of 100-nanosecond intervals since January 1, 1601
1143             # UNIX timestamp is number of seconds since January 1, 1970
1144             # 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
1145 3 50       10 if ($round) {
1146 3         16 return int(($filetime - 116444736000000000) / 10000000);
1147             }
1148              
1149 0           return ($filetime - 116444736000000000) / 10000000;
1150             }
1151              
1152             1;
1153              
1154             __END__