rlm@3: | rlm@3: // | Allan Hansen | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // | module.audio.voc.php | rlm@3: // | Module for analyzing Creative VOC Audio files. | rlm@3: // | dependencies: NONE | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // rlm@3: // $Id: module.audio.voc.php,v 1.3 2006/11/02 10:48:02 ah Exp $ rlm@3: rlm@3: rlm@3: rlm@3: class getid3_voc extends getid3_handler rlm@3: { rlm@3: rlm@3: public function Analyze() { rlm@3: rlm@3: $getid3 = $this->getid3; rlm@3: rlm@3: $original_av_data_offset = $getid3->info['avdataoffset']; rlm@3: rlm@3: fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET); rlm@3: $voc_header= fread($getid3->fp, 26); rlm@3: rlm@3: // Magic bytes: 'Creative Voice File' rlm@3: rlm@3: $info_audio = &$getid3->info['audio']; rlm@3: $getid3->info['voc'] = array (); rlm@3: $info_voc = &$getid3->info['voc']; rlm@3: rlm@3: $getid3->info['fileformat'] = 'voc'; rlm@3: $info_audio['dataformat'] = 'voc'; rlm@3: $info_audio['bitrate_mode'] = 'cbr'; rlm@3: $info_audio['lossless'] = true; rlm@3: $info_audio['channels'] = 1; // might be overriden below rlm@3: $info_audio['bits_per_sample'] = 8; // might be overriden below rlm@3: rlm@3: // byte # Description rlm@3: // ------ ------------------------------------------ rlm@3: // 00-12 'Creative Voice File' rlm@3: // 13 1A (eof to abort printing of file) rlm@3: // 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation) rlm@3: // 16-17 Version number (minor,major) (VOC-HDR puts 0A 01) rlm@3: // 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11) rlm@3: rlm@3: getid3_lib::ReadSequence('LittleEndian2Int', $info_voc['header'], $voc_header, 20, rlm@3: array ( rlm@3: 'datablock_offset' => 2, rlm@3: 'minor_version' => 1, rlm@3: 'major_version' => 1 rlm@3: ) rlm@3: ); rlm@3: rlm@3: do { rlm@3: $block_offset = ftell($getid3->fp); rlm@3: $block_data = fread($getid3->fp, 4); rlm@3: $block_type = ord($block_data{0}); rlm@3: $block_size = getid3_lib::LittleEndian2Int(substr($block_data, 1, 3)); rlm@3: $this_block = array (); rlm@3: rlm@3: @$info_voc['blocktypes'][$block_type]++; rlm@3: rlm@3: switch ($block_type) { rlm@3: rlm@3: case 0: // Terminator rlm@3: // do nothing, we'll break out of the loop down below rlm@3: break; rlm@3: rlm@3: case 1: // Sound data rlm@3: $block_data .= fread($getid3->fp, 2); rlm@3: if ($getid3->info['avdataoffset'] <= $original_av_data_offset) { rlm@3: $getid3->info['avdataoffset'] = ftell($getid3->fp); rlm@3: } rlm@3: fseek($getid3->fp, $block_size - 2, SEEK_CUR); rlm@3: rlm@3: getid3_lib::ReadSequence('LittleEndian2Int', $this_block, $block_data, 4, rlm@3: array ( rlm@3: 'sample_rate_id' => 1, rlm@3: 'compression_type' => 1 rlm@3: ) rlm@3: ); rlm@3: rlm@3: $this_block['compression_name'] = getid3_voc::VOCcompressionTypeLookup($this_block['compression_type']); rlm@3: if ($this_block['compression_type'] <= 3) { rlm@3: $info_voc['compressed_bits_per_sample'] = (int)(str_replace('-bit', '', $this_block['compression_name'])); rlm@3: } rlm@3: rlm@3: // Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available) rlm@3: if (empty($info_audio['sample_rate'])) { rlm@3: // SR byte = 256 - (1000000 / sample_rate) rlm@3: $info_audio['sample_rate'] = (int)floor((1000000 / (256 - $this_block['sample_rate_id'])) / $info_audio['channels']); rlm@3: } rlm@3: break; rlm@3: rlm@3: case 2: // Sound continue rlm@3: case 3: // Silence rlm@3: case 4: // Marker rlm@3: case 6: // Repeat rlm@3: case 7: // End repeat rlm@3: // nothing useful, just skip rlm@3: fseek($getid3->fp, $block_size, SEEK_CUR); rlm@3: break; rlm@3: rlm@3: case 8: // Extended rlm@3: $block_data .= fread($getid3->fp, 4); rlm@3: rlm@3: //00-01 Time Constant: rlm@3: // Mono: 65536 - (256000000 / sample_rate) rlm@3: // Stereo: 65536 - (256000000 / (sample_rate * 2)) rlm@3: getid3_lib::ReadSequence('LittleEndian2Int', $this_block, $block_data, 4, rlm@3: array ( rlm@3: 'time_constant' => 2, rlm@3: 'pack_method' => 1, rlm@3: 'stereo' => 1 rlm@3: ) rlm@3: ); rlm@3: $this_block['stereo'] = (bool)$this_block['stereo']; rlm@3: rlm@3: $info_audio['channels'] = ($this_block['stereo'] ? 2 : 1); rlm@3: $info_audio['sample_rate'] = (int)floor((256000000 / (65536 - $this_block['time_constant'])) / $info_audio['channels']); rlm@3: break; rlm@3: rlm@3: case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit rlm@3: $block_data .= fread($getid3->fp, 12); rlm@3: if ($getid3->info['avdataoffset'] <= $original_av_data_offset) { rlm@3: $getid3->info['avdataoffset'] = ftell($getid3->fp); rlm@3: } rlm@3: fseek($getid3->fp, $block_size - 12, SEEK_CUR); rlm@3: rlm@3: getid3_lib::ReadSequence('LittleEndian2Int', $this_block, $block_data, 4, rlm@3: array ( rlm@3: 'sample_rate' => 4, rlm@3: 'bits_per_sample' => 1, rlm@3: 'channels' => 1, rlm@3: 'wFormat' => 2 rlm@3: ) rlm@3: ); rlm@3: rlm@3: $this_block['compression_name'] = getid3_voc::VOCwFormatLookup($this_block['wFormat']); rlm@3: if (getid3_voc::VOCwFormatActualBitsPerSampleLookup($this_block['wFormat'])) { rlm@3: $info_voc['compressed_bits_per_sample'] = getid3_voc::VOCwFormatActualBitsPerSampleLookup($this_block['wFormat']); rlm@3: } rlm@3: rlm@3: $info_audio['sample_rate'] = $this_block['sample_rate']; rlm@3: $info_audio['bits_per_sample'] = $this_block['bits_per_sample']; rlm@3: $info_audio['channels'] = $this_block['channels']; rlm@3: break; rlm@3: rlm@3: default: rlm@3: $getid3->warning('Unhandled block type "'.$block_type.'" at offset '.$block_offset); rlm@3: fseek($getid3->fp, $block_size, SEEK_CUR); rlm@3: break; rlm@3: } rlm@3: rlm@3: if (!empty($this_block)) { rlm@3: $this_block['block_offset'] = $block_offset; rlm@3: $this_block['block_size'] = $block_size; rlm@3: $this_block['block_type_id'] = $block_type; rlm@3: $info_voc['blocks'][] = $this_block; rlm@3: } rlm@3: rlm@3: } while (!feof($getid3->fp) && ($block_type != 0)); rlm@3: rlm@3: // Terminator block doesn't have size field, so seek back 3 spaces rlm@3: fseek($getid3->fp, -3, SEEK_CUR); rlm@3: rlm@3: ksort($info_voc['blocktypes']); rlm@3: rlm@3: if (!empty($info_voc['compressed_bits_per_sample'])) { rlm@3: $getid3->info['playtime_seconds'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_voc['compressed_bits_per_sample'] * $info_audio['channels'] * $info_audio['sample_rate']); rlm@3: $info_audio['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds']; rlm@3: } rlm@3: rlm@3: return true; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function VOCcompressionTypeLookup($index) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 0 => '8-bit', rlm@3: 1 => '4-bit', rlm@3: 2 => '2.6-bit', rlm@3: 3 => '2-bit' rlm@3: ); rlm@3: return (isset($lookup[$index]) ? $lookup[$index] : 'Multi DAC ('.($index - 3).') channels'); rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function VOCwFormatLookup($index) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 0x0000 => '8-bit unsigned PCM', rlm@3: 0x0001 => 'Creative 8-bit to 4-bit ADPCM', rlm@3: 0x0002 => 'Creative 8-bit to 3-bit ADPCM', rlm@3: 0x0003 => 'Creative 8-bit to 2-bit ADPCM', rlm@3: 0x0004 => '16-bit signed PCM', rlm@3: 0x0006 => 'CCITT a-Law', rlm@3: 0x0007 => 'CCITT u-Law', rlm@3: 0x2000 => 'Creative 16-bit to 4-bit ADPCM' rlm@3: ); rlm@3: return (isset($lookup[$index]) ? $lookup[$index] : false); rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function VOCwFormatActualBitsPerSampleLookup($index) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 0x0000 => 8, rlm@3: 0x0001 => 4, rlm@3: 0x0002 => 3, rlm@3: 0x0003 => 2, rlm@3: 0x0004 => 16, rlm@3: 0x0006 => 8, rlm@3: 0x0007 => 8, rlm@3: 0x2000 => 4 rlm@3: ); rlm@3: return (isset($lookup[$index]) ? $lookup[$index] : false); rlm@3: } rlm@3: rlm@3: } rlm@3: rlm@3: rlm@3: ?>