1 <?php |
1 <?php |
|
2 |
2 ///////////////////////////////////////////////////////////////// |
3 ///////////////////////////////////////////////////////////////// |
3 /// getID3() by James Heinrich <info@getid3.org> // |
4 /// getID3() by James Heinrich <info@getid3.org> // |
4 // available at http://getid3.sourceforge.net // |
5 // available at https://github.com/JamesHeinrich/getID3 // |
5 // or http://www.getid3.org // |
6 // or https://www.getid3.org // |
6 // also https://github.com/JamesHeinrich/getID3 // |
7 // or http://getid3.sourceforge.net // |
7 ///////////////////////////////////////////////////////////////// |
8 // see readme.txt for more details // |
8 // See readme.txt for more details // |
|
9 ///////////////////////////////////////////////////////////////// |
9 ///////////////////////////////////////////////////////////////// |
10 // // |
10 // // |
11 // module.audio.ac3.php // |
11 // module.audio.ac3.php // |
12 // module for analyzing AC-3 (aka Dolby Digital) audio files // |
12 // module for analyzing AC-3 (aka Dolby Digital) audio files // |
13 // dependencies: NONE // |
13 // dependencies: NONE // |
14 // /// |
14 // /// |
15 ///////////////////////////////////////////////////////////////// |
15 ///////////////////////////////////////////////////////////////// |
16 |
16 |
|
17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers |
|
18 exit; |
|
19 } |
17 |
20 |
18 class getid3_ac3 extends getid3_handler |
21 class getid3_ac3 extends getid3_handler |
19 { |
22 { |
20 private $AC3header = array(); |
23 /** |
21 private $BSIoffset = 0; |
24 * @var array |
22 |
25 */ |
23 const syncword = 0x0B77; |
26 private $AC3header = array(); |
24 |
27 |
|
28 /** |
|
29 * @var int |
|
30 */ |
|
31 private $BSIoffset = 0; |
|
32 |
|
33 const syncword = 0x0B77; |
|
34 |
|
35 /** |
|
36 * @return bool |
|
37 */ |
25 public function Analyze() { |
38 public function Analyze() { |
26 $info = &$this->getid3->info; |
39 $info = &$this->getid3->info; |
27 |
40 |
28 ///AH |
41 ///AH |
29 $info['ac3']['raw']['bsi'] = array(); |
42 $info['ac3']['raw']['bsi'] = array(); |
185 |
198 |
186 |
199 |
187 } elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3 |
200 } elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3 |
188 |
201 |
189 |
202 |
190 $this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.'); |
203 $this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.'); |
191 $info['audio']['dataformat'] = 'eac3'; |
204 $info['audio']['dataformat'] = 'eac3'; |
192 |
205 |
193 $thisfile_ac3_raw_bsi['strmtyp'] = $this->readHeaderBSI(2); |
206 $thisfile_ac3_raw_bsi['strmtyp'] = $this->readHeaderBSI(2); |
194 $thisfile_ac3_raw_bsi['substreamid'] = $this->readHeaderBSI(3); |
207 $thisfile_ac3_raw_bsi['substreamid'] = $this->readHeaderBSI(3); |
195 $thisfile_ac3_raw_bsi['frmsiz'] = $this->readHeaderBSI(11); |
208 $thisfile_ac3_raw_bsi['frmsiz'] = $this->readHeaderBSI(11); |
429 } |
442 } |
430 if (isset($thisfile_ac3_raw_bsi['frmsizecod'])) { |
443 if (isset($thisfile_ac3_raw_bsi['frmsizecod'])) { |
431 $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']); |
444 $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']); |
432 $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']); |
445 $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']); |
433 } elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) { |
446 } elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) { |
434 // this isn't right, but it's (usually) close, roughly 5% less than it should be. |
447 // this isn't right, but it's (usually) close, roughly 5% less than it should be. |
435 // but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know! |
448 // but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know! |
436 $thisfile_ac3['bitrate'] = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048. |
449 $thisfile_ac3['bitrate'] = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048. |
437 // kludge-fix to make it approximately the expected value, still not "right": |
450 // kludge-fix to make it approximately the expected value, still not "right": |
438 $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000; |
451 $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000; |
439 } |
452 } |
440 $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; |
453 $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; |
441 |
454 |
442 $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); |
455 if (isset($thisfile_ac3_raw_bsi['bsmod']) && isset($thisfile_ac3_raw_bsi['acmod'])) { |
|
456 $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); |
|
457 } |
443 $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); |
458 $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); |
444 foreach($ac3_coding_mode as $key => $value) { |
459 foreach($ac3_coding_mode as $key => $value) { |
445 $thisfile_ac3[$key] = $value; |
460 $thisfile_ac3[$key] = $value; |
446 } |
461 } |
447 switch ($thisfile_ac3_raw_bsi['acmod']) { |
462 switch ($thisfile_ac3_raw_bsi['acmod']) { |
468 $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; |
483 $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; |
469 |
484 |
470 return true; |
485 return true; |
471 } |
486 } |
472 |
487 |
|
488 /** |
|
489 * @param int $length |
|
490 * |
|
491 * @return int |
|
492 */ |
473 private function readHeaderBSI($length) { |
493 private function readHeaderBSI($length) { |
474 $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); |
494 $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); |
475 $this->BSIoffset += $length; |
495 $this->BSIoffset += $length; |
476 |
496 |
477 return bindec($data); |
497 return bindec($data); |
478 } |
498 } |
479 |
499 |
|
500 /** |
|
501 * @param int $fscod |
|
502 * |
|
503 * @return int|string|false |
|
504 */ |
480 public static function sampleRateCodeLookup($fscod) { |
505 public static function sampleRateCodeLookup($fscod) { |
481 static $sampleRateCodeLookup = array( |
506 static $sampleRateCodeLookup = array( |
482 0 => 48000, |
507 0 => 48000, |
483 1 => 44100, |
508 1 => 44100, |
484 2 => 32000, |
509 2 => 32000, |
485 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. |
510 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. |
486 ); |
511 ); |
487 return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); |
512 return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); |
488 } |
513 } |
489 |
514 |
|
515 /** |
|
516 * @param int $fscod2 |
|
517 * |
|
518 * @return int|string|false |
|
519 */ |
490 public static function sampleRateCodeLookup2($fscod2) { |
520 public static function sampleRateCodeLookup2($fscod2) { |
491 static $sampleRateCodeLookup2 = array( |
521 static $sampleRateCodeLookup2 = array( |
492 0 => 24000, |
522 0 => 24000, |
493 1 => 22050, |
523 1 => 22050, |
494 2 => 16000, |
524 2 => 16000, |
495 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. |
525 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. |
496 ); |
526 ); |
497 return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false); |
527 return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false); |
498 } |
528 } |
499 |
529 |
|
530 /** |
|
531 * @param int $bsmod |
|
532 * @param int $acmod |
|
533 * |
|
534 * @return string|false |
|
535 */ |
500 public static function serviceTypeLookup($bsmod, $acmod) { |
536 public static function serviceTypeLookup($bsmod, $acmod) { |
501 static $serviceTypeLookup = array(); |
537 static $serviceTypeLookup = array(); |
502 if (empty($serviceTypeLookup)) { |
538 if (empty($serviceTypeLookup)) { |
503 for ($i = 0; $i <= 7; $i++) { |
539 for ($i = 0; $i <= 7; $i++) { |
504 $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; |
540 $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; |
516 } |
552 } |
517 } |
553 } |
518 return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); |
554 return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); |
519 } |
555 } |
520 |
556 |
|
557 /** |
|
558 * @param int $acmod |
|
559 * |
|
560 * @return array|false |
|
561 */ |
521 public static function audioCodingModeLookup($acmod) { |
562 public static function audioCodingModeLookup($acmod) { |
522 // array(channel configuration, # channels (not incl LFE), channel order) |
563 // array(channel configuration, # channels (not incl LFE), channel order) |
523 static $audioCodingModeLookup = array ( |
564 static $audioCodingModeLookup = array ( |
524 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), |
565 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), |
525 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), |
566 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), |
531 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), |
572 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), |
532 ); |
573 ); |
533 return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); |
574 return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); |
534 } |
575 } |
535 |
576 |
|
577 /** |
|
578 * @param int $cmixlev |
|
579 * |
|
580 * @return int|float|string|false |
|
581 */ |
536 public static function centerMixLevelLookup($cmixlev) { |
582 public static function centerMixLevelLookup($cmixlev) { |
537 static $centerMixLevelLookup; |
583 static $centerMixLevelLookup; |
538 if (empty($centerMixLevelLookup)) { |
584 if (empty($centerMixLevelLookup)) { |
539 $centerMixLevelLookup = array( |
585 $centerMixLevelLookup = array( |
540 0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB) |
586 0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB) |
544 ); |
590 ); |
545 } |
591 } |
546 return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); |
592 return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); |
547 } |
593 } |
548 |
594 |
|
595 /** |
|
596 * @param int $surmixlev |
|
597 * |
|
598 * @return int|float|string|false |
|
599 */ |
549 public static function surroundMixLevelLookup($surmixlev) { |
600 public static function surroundMixLevelLookup($surmixlev) { |
550 static $surroundMixLevelLookup; |
601 static $surroundMixLevelLookup; |
551 if (empty($surroundMixLevelLookup)) { |
602 if (empty($surroundMixLevelLookup)) { |
552 $surroundMixLevelLookup = array( |
603 $surroundMixLevelLookup = array( |
553 0 => pow(2, -3.0 / 6), |
604 0 => pow(2, -3.0 / 6), |
557 ); |
608 ); |
558 } |
609 } |
559 return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); |
610 return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); |
560 } |
611 } |
561 |
612 |
|
613 /** |
|
614 * @param int $dsurmod |
|
615 * |
|
616 * @return string|false |
|
617 */ |
562 public static function dolbySurroundModeLookup($dsurmod) { |
618 public static function dolbySurroundModeLookup($dsurmod) { |
563 static $dolbySurroundModeLookup = array( |
619 static $dolbySurroundModeLookup = array( |
564 0 => 'not indicated', |
620 0 => 'not indicated', |
565 1 => 'Not Dolby Surround encoded', |
621 1 => 'Not Dolby Surround encoded', |
566 2 => 'Dolby Surround encoded', |
622 2 => 'Dolby Surround encoded', |
567 3 => 'reserved' |
623 3 => 'reserved' |
568 ); |
624 ); |
569 return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); |
625 return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); |
570 } |
626 } |
571 |
627 |
|
628 /** |
|
629 * @param int $acmod |
|
630 * @param bool $lfeon |
|
631 * |
|
632 * @return array |
|
633 */ |
572 public static function channelsEnabledLookup($acmod, $lfeon) { |
634 public static function channelsEnabledLookup($acmod, $lfeon) { |
573 $lookup = array( |
635 $lookup = array( |
574 'ch1'=>(bool) ($acmod == 0), |
636 'ch1'=>($acmod == 0), |
575 'ch2'=>(bool) ($acmod == 0), |
637 'ch2'=>($acmod == 0), |
576 'left'=>(bool) ($acmod > 1), |
638 'left'=>($acmod > 1), |
577 'right'=>(bool) ($acmod > 1), |
639 'right'=>($acmod > 1), |
578 'center'=>(bool) ($acmod & 0x01), |
640 'center'=>(bool) ($acmod & 0x01), |
579 'surround_mono'=>false, |
641 'surround_mono'=>false, |
580 'surround_left'=>false, |
642 'surround_left'=>false, |
581 'surround_right'=>false, |
643 'surround_right'=>false, |
582 'lfe'=>$lfeon); |
644 'lfe'=>$lfeon); |
592 break; |
654 break; |
593 } |
655 } |
594 return $lookup; |
656 return $lookup; |
595 } |
657 } |
596 |
658 |
|
659 /** |
|
660 * @param int $compre |
|
661 * |
|
662 * @return float|int |
|
663 */ |
597 public static function heavyCompression($compre) { |
664 public static function heavyCompression($compre) { |
598 // The first four bits indicate gain changes in 6.02dB increments which can be |
665 // The first four bits indicate gain changes in 6.02dB increments which can be |
599 // implemented with an arithmetic shift operation. The following four bits |
666 // implemented with an arithmetic shift operation. The following four bits |
600 // indicate linear gain changes, and require a 5-bit multiply. |
667 // indicate linear gain changes, and require a 5-bit multiply. |
601 // We will represent the two 4-bit fields of compr as follows: |
668 // We will represent the two 4-bit fields of compr as follows: |
621 // -6 -30.10 dB |
688 // -6 -30.10 dB |
622 // -7 -36.12 dB |
689 // -7 -36.12 dB |
623 // -8 -42.14 dB |
690 // -8 -42.14 dB |
624 |
691 |
625 $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); |
692 $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); |
626 if ($fourbit{0} == '1') { |
693 if ($fourbit[0] == '1') { |
627 $log_gain = -8 + bindec(substr($fourbit, 1)); |
694 $log_gain = -8 + bindec(substr($fourbit, 1)); |
628 } else { |
695 } else { |
629 $log_gain = bindec(substr($fourbit, 1)); |
696 $log_gain = bindec(substr($fourbit, 1)); |
630 } |
697 } |
631 $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); |
698 $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); |
642 // -42.14 - 6.02 = -48.16 dB. |
709 // -42.14 - 6.02 = -48.16 dB. |
643 |
710 |
644 return $log_gain - $lin_gain; |
711 return $log_gain - $lin_gain; |
645 } |
712 } |
646 |
713 |
|
714 /** |
|
715 * @param int $roomtyp |
|
716 * |
|
717 * @return string|false |
|
718 */ |
647 public static function roomTypeLookup($roomtyp) { |
719 public static function roomTypeLookup($roomtyp) { |
648 static $roomTypeLookup = array( |
720 static $roomTypeLookup = array( |
649 0 => 'not indicated', |
721 0 => 'not indicated', |
650 1 => 'large room, X curve monitor', |
722 1 => 'large room, X curve monitor', |
651 2 => 'small room, flat monitor', |
723 2 => 'small room, flat monitor', |
652 3 => 'reserved' |
724 3 => 'reserved' |
653 ); |
725 ); |
654 return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); |
726 return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); |
655 } |
727 } |
656 |
728 |
|
729 /** |
|
730 * @param int $frmsizecod |
|
731 * @param int $fscod |
|
732 * |
|
733 * @return int|false |
|
734 */ |
657 public static function frameSizeLookup($frmsizecod, $fscod) { |
735 public static function frameSizeLookup($frmsizecod, $fscod) { |
658 // LSB is whether padding is used or not |
736 // LSB is whether padding is used or not |
659 $padding = (bool) ($frmsizecod & 0x01); |
737 $padding = (bool) ($frmsizecod & 0x01); |
660 $framesizeid = ($frmsizecod & 0x3E) >> 1; |
738 $framesizeid = ($frmsizecod & 0x3E) >> 1; |
661 |
739 |
681 16 => array(2048, 2228, 3072), // 512 kbps |
759 16 => array(2048, 2228, 3072), // 512 kbps |
682 17 => array(2304, 2506, 3456), // 576 kbps |
760 17 => array(2304, 2506, 3456), // 576 kbps |
683 18 => array(2560, 2786, 3840) // 640 kbps |
761 18 => array(2560, 2786, 3840) // 640 kbps |
684 ); |
762 ); |
685 } |
763 } |
|
764 $paddingBytes = 0; |
686 if (($fscod == 1) && $padding) { |
765 if (($fscod == 1) && $padding) { |
687 // frame lengths are padded by 1 word (16 bits) at 44100 |
766 // frame lengths are padded by 1 word (16 bits) at 44100 |
688 $frameSizeLookup[$frmsizecod] += 2; |
767 // (fscode==1) means 44100Hz (see sampleRateCodeLookup) |
689 } |
768 $paddingBytes = 2; |
690 return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false); |
769 } |
691 } |
770 return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] + $paddingBytes : false); |
692 |
771 } |
|
772 |
|
773 /** |
|
774 * @param int $frmsizecod |
|
775 * |
|
776 * @return int|false |
|
777 */ |
693 public static function bitrateLookup($frmsizecod) { |
778 public static function bitrateLookup($frmsizecod) { |
694 // LSB is whether padding is used or not |
779 // LSB is whether padding is used or not |
695 $padding = (bool) ($frmsizecod & 0x01); |
780 $padding = (bool) ($frmsizecod & 0x01); |
696 $framesizeid = ($frmsizecod & 0x3E) >> 1; |
781 $framesizeid = ($frmsizecod & 0x3E) >> 1; |
697 |
782 |