/flash/MP3FileReferenceLoaderLib/src/org/audiofx/mp3/MP3Parser.as
ActionScript | 266 lines | 210 code | 34 blank | 22 comment | 9 complexity | cbcd159c146d6505a6df31351d251c6e MD5 | raw file
1/* 2Copyright (c) 2008 Christopher Martin-Sperry (audiofx.org@gmail.com) 3 4Permission is hereby granted, free of charge, to any person obtaining a copy 5of this software and associated documentation files (the "Software"), to deal 6in the Software without restriction, including without limitation the rights 7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8copies of the Software, and to permit persons to whom the Software is 9furnished to do so, subject to the following conditions: 10 11The above copyright notice and this permission notice shall be included in 12all copies or substantial portions of the Software. 13 14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20THE SOFTWARE. 21*/ 22 23package org.audiofx.mp3 24{ 25 import flash.events.Event; 26 import flash.events.EventDispatcher; 27 import flash.events.IOErrorEvent; 28 import flash.net.FileReference; 29 import flash.net.URLLoader; 30 import flash.net.URLLoaderDataFormat; 31 import flash.net.URLRequest; 32 import flash.utils.ByteArray; 33 34 35 36 37 [Event(name="complete", type="flash.events.Event")] 38 internal class MP3Parser extends EventDispatcher 39 { 40 private var mp3Data:ByteArray; 41 private var loader:URLLoader; 42 private var currentPosition:uint; 43 private var sampleRate:uint; 44 private var channels:uint; 45 private var version:uint; 46 private static var bitRates:Array=[-1,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1,-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1]; 47 private static var versions:Array=[2.5,-1,2,1]; 48 private static var samplingRates:Array=[44100,48000,32000]; 49 public function MP3Parser() 50 { 51 52 loader=new URLLoader(); 53 loader.dataFormat=URLLoaderDataFormat.BINARY; 54 loader.addEventListener(Event.COMPLETE,loaderCompleteHandler); 55 56 57 } 58 internal function load(url:String):void 59 { 60 var req:URLRequest=new URLRequest(url); 61 loader.load(req); 62 } 63 internal function loadFileRef(fileRef:FileReference):void 64 { 65 fileRef.addEventListener(Event.COMPLETE,loaderCompleteHandler); 66 fileRef.addEventListener(IOErrorEvent.IO_ERROR,errorHandler); 67 //fileRef.addEventListener(Event.COMPLETE,loaderCompleteHandler); 68 fileRef.load(); 69 } 70 private function errorHandler(ev:IOErrorEvent):void 71 { 72 trace("error\n"+ev.text); 73 } 74 private function loaderCompleteHandler(ev:Event):void 75 { 76 mp3Data=ev.currentTarget.data as ByteArray; 77 currentPosition=getFirstHeaderPosition(); 78 dispatchEvent(ev); 79 } 80 private function getFirstHeaderPosition():uint 81 { 82 mp3Data.position=0; 83 84 85 while(mp3Data.position<mp3Data.length) 86 { 87 var readPosition:uint=mp3Data.position; 88 var str:String=mp3Data.readMultiByte(3,"us-ascii"); 89 90 91 if(str=="ID3") //here's an id3v2 header. fuck that for a laugh. skipping 92 { 93 mp3Data.position+=3; 94 var b3:int=(mp3Data.readByte()&0x7F)<<21; 95 var b2:int=(mp3Data.readByte()&0x7F)<<14; 96 var b1:int=(mp3Data.readByte()&0x7F)<<7; 97 var b0:int=mp3Data.readByte()&0x7F; 98 var headerLength:int=b0+b1+b2+b3; 99 var newPosition:int=mp3Data.position+headerLength; 100 trace("Found id3v2 header, length "+headerLength.toString(16)+" bytes. Moving to "+newPosition.toString(16)); 101 mp3Data.position=newPosition; 102 readPosition=newPosition; 103 } 104 else 105 { 106 mp3Data.position=readPosition; 107 } 108 109 var val:uint=mp3Data.readInt(); 110 111 if(isValidHeader(val)) 112 { 113 parseHeader(val); 114 mp3Data.position=readPosition+getFrameSize(val); 115 if(isValidHeader(mp3Data.readInt())) 116 { 117 return readPosition; 118 } 119 120 } 121 122 } 123 throw(new Error("Could not locate first header. This isn't an MP3 file")); 124 } 125 internal function getNextFrame():ByteArraySegment 126 { 127 mp3Data.position=currentPosition; 128 var headerByte:uint; 129 var frameSize:uint; 130 while(true) 131 { 132 if(currentPosition>(mp3Data.length-4)) 133 { 134 trace("passed eof"); 135 return null; 136 } 137 headerByte=mp3Data.readInt(); 138 if(isValidHeader(headerByte)) 139 { 140 frameSize=getFrameSize(headerByte); 141 if(frameSize!=0xffffffff) 142 { 143 break; 144 } 145 } 146 currentPosition=mp3Data.position; 147 148 } 149 150 mp3Data.position=currentPosition; 151 152 if((currentPosition+frameSize)>mp3Data.length) 153 { 154 return null; 155 } 156 157 currentPosition+=frameSize; 158 return new ByteArraySegment(mp3Data,mp3Data.position,frameSize); 159 } 160 internal function writeSwfFormatByte(byteArray:ByteArray):void 161 { 162 var sampleRateIndex:uint=4-(44100/sampleRate); 163 byteArray.writeByte((2<<4)+(sampleRateIndex<<2)+(1<<1)+(channels-1)); 164 } 165 private function parseHeader(headerBytes:uint):void 166 { 167 var channelMode:uint=getModeIndex(headerBytes); 168 version=getVersionIndex(headerBytes); 169 var samplingRate:uint=getFrequencyIndex(headerBytes); 170 channels=(channelMode>2)?1:2; 171 var actualVersion:Number=versions[version]; 172 var samplingRates:Array=[44100,48000,32000]; 173 sampleRate=samplingRates[samplingRate]; 174 switch(actualVersion) 175 { 176 case 2: 177 sampleRate/=2; 178 break; 179 case 2.5: 180 sampleRate/=4; 181 } 182 183 } 184 private function getFrameSize(headerBytes:uint):uint 185 { 186 187 188 var version:uint=getVersionIndex(headerBytes); 189 var bitRate:uint=getBitrateIndex(headerBytes); 190 var samplingRate:uint=getFrequencyIndex(headerBytes); 191 var padding:uint=getPaddingBit(headerBytes); 192 var channelMode:uint=getModeIndex(headerBytes); 193 var actualVersion:Number=versions[version]; 194 var sampleRate:uint=samplingRates[samplingRate]; 195 if(sampleRate!=this.sampleRate||this.version!=version) 196 { 197 return 0xffffffff; 198 } 199 switch(actualVersion) 200 { 201 case 2: 202 sampleRate/=2; 203 break; 204 case 2.5: 205 sampleRate/=4; 206 } 207 var bitRatesYIndex:uint=((actualVersion==1)?0:1)*bitRates.length/2; 208 var actualBitRate:uint=bitRates[bitRatesYIndex+bitRate]*1000; 209 var frameLength:uint=(((actualVersion==1?144:72)*actualBitRate)/sampleRate)+padding; 210 return frameLength; 211 212 } 213 214 private function isValidHeader(headerBits:uint):Boolean 215 { 216 return (((getFrameSync(headerBits) & 2047)==2047) && 217 ((getVersionIndex(headerBits) & 3)!= 1) && 218 ((getLayerIndex(headerBits) & 3)!= 0) && 219 ((getBitrateIndex(headerBits) & 15)!= 0) && 220 ((getBitrateIndex(headerBits) & 15)!= 15) && 221 ((getFrequencyIndex(headerBits) & 3)!= 3) && 222 ((getEmphasisIndex(headerBits) & 3)!= 2) ); 223 } 224 225 private function getFrameSync(headerBits:uint):uint 226 { 227 return uint((headerBits>>21) & 2047); 228 } 229 230 private function getVersionIndex(headerBits:uint):uint 231 { 232 return uint((headerBits>>19) & 3); 233 } 234 235 private function getLayerIndex(headerBits:uint):uint 236 { 237 return uint((headerBits>>17) & 3); 238 } 239 240 private function getBitrateIndex(headerBits:uint):uint 241 { 242 return uint((headerBits>>12) & 15); 243 } 244 245 private function getFrequencyIndex(headerBits:uint):uint 246 { 247 return uint((headerBits>>10) & 3); 248 } 249 250 private function getPaddingBit(headerBits:uint):uint 251 { 252 return uint((headerBits>>9) & 1); 253 } 254 255 private function getModeIndex(headerBits:uint):uint 256 { 257 return uint((headerBits>>6) & 3); 258 } 259 260 private function getEmphasisIndex(headerBits:uint):uint 261 { 262 return uint(headerBits & 3); 263 } 264 265 } 266}