| 1 | package com.longtailvideo.jwplayer.demux { |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | import com.longtailvideo.jwplayer.utils.Logger; |
|---|
| 5 | |
|---|
| 6 | import flash.utils.ByteArray; |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | /** Translate a single Packetized Elementary Stream into FLV Tags. **/ |
|---|
| 10 | public class PES { |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | /** The PES data (including headers). **/ |
|---|
| 14 | private var _data:ByteArray; |
|---|
| 15 | /** Timestamp from the PTS header. **/ |
|---|
| 16 | private var _timestamp:Number; |
|---|
| 17 | /** Is it AAC or AVC. **/ |
|---|
| 18 | private var _audio:Boolean; |
|---|
| 19 | /** Is there a keyframe. **/ |
|---|
| 20 | private var _keyframe:Boolean; |
|---|
| 21 | /** ByteArray with FLV tags for this stream. **/ |
|---|
| 22 | private var _tags:ByteArray; |
|---|
| 23 | /** Composition time for video tag. **/ |
|---|
| 24 | private var _compositionTime:Number; |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | /** Save the first chunk of data. **/ |
|---|
| 28 | public function PES(data:ByteArray,audio:Boolean,keyframe:Boolean=false) { |
|---|
| 29 | _data = data; |
|---|
| 30 | _audio = audio; |
|---|
| 31 | _keyframe = keyframe; |
|---|
| 32 | _tags = new ByteArray(); |
|---|
| 33 | }; |
|---|
| 34 | |
|---|
| 35 | |
|---|
| 36 | /** Append data to the AVC stream. **/ |
|---|
| 37 | public function append(data:ByteArray):void { |
|---|
| 38 | _data.writeBytes(data); |
|---|
| 39 | }; |
|---|
| 40 | |
|---|
| 41 | |
|---|
| 42 | /** Return the FLV tags. **/ |
|---|
| 43 | public function get tags():ByteArray { |
|---|
| 44 | return _tags; |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | /** Parse the Elementary Stream. **/ |
|---|
| 49 | public function parse(): void { |
|---|
| 50 | // Start code prefix and packet ID. |
|---|
| 51 | _data.position = 0; |
|---|
| 52 | if((_audio && _data.readUnsignedInt() != 448) || |
|---|
| 53 | (!_audio && _data.readUnsignedInt() != 480)) { |
|---|
| 54 | throw new Error("PES start code not found or not AAC/AVC"); |
|---|
| 55 | } |
|---|
| 56 | // Ignore packet length and marker bits. |
|---|
| 57 | _data.position += 3; |
|---|
| 58 | // Check for PTS |
|---|
| 59 | var flags:uint = (_data.readUnsignedByte() & 192) >> 6; |
|---|
| 60 | if((_audio && flags != 2) || (!_audio && flags != 3)) { |
|---|
| 61 | throw new Error("No PTS/DTS in this PES packet"); |
|---|
| 62 | } |
|---|
| 63 | // Check PES header length |
|---|
| 64 | var length:uint = _data.readUnsignedByte(); |
|---|
| 65 | // Grab the timestamp from PTS data (spread out over 5 bytes): |
|---|
| 66 | // XXXX---X -------- -------X -------- -------X |
|---|
| 67 | var pts:Number = ((_data.readUnsignedByte() & 14) << 29) + |
|---|
| 68 | ((_data.readUnsignedShort() & 65534) << 14) + |
|---|
| 69 | ((_data.readUnsignedShort() & 65534) >> 1); |
|---|
| 70 | // Convert 90kHz clock to milliseconds. |
|---|
| 71 | _timestamp = Math.round(pts/90); |
|---|
| 72 | length -= 5; |
|---|
| 73 | if(!_audio) { |
|---|
| 74 | // Grab the DTS and calculate compositionTime (PTS-DTS) |
|---|
| 75 | var dts:Number = ((_data.readUnsignedByte() & 14) << 29) + |
|---|
| 76 | ((_data.readUnsignedShort() & 65534) << 14) + |
|---|
| 77 | ((_data.readUnsignedShort() & 65534) >> 1); |
|---|
| 78 | _compositionTime = _timestamp - Math.round(dts/90); |
|---|
| 79 | length -= 5; |
|---|
| 80 | } |
|---|
| 81 | // Skip other header data. |
|---|
| 82 | _data.position += length; |
|---|
| 83 | if(_audio) { |
|---|
| 84 | parseAac(); |
|---|
| 85 | } else { |
|---|
| 86 | parseAvc(); |
|---|
| 87 | } |
|---|
| 88 | }; |
|---|
| 89 | |
|---|
| 90 | |
|---|
| 91 | /** Step through AAC to extract ADTS. **/ |
|---|
| 92 | private function parseAac():void { |
|---|
| 93 | var start:Number = 0; |
|---|
| 94 | var rawstart:Number = 0; |
|---|
| 95 | while(_data.bytesAvailable > 1) { |
|---|
| 96 | // ADTS header: Syncword, ID, Layer and PA should be 0xFFF1. |
|---|
| 97 | if(_data.readUnsignedShort() == 65521) { |
|---|
| 98 | // Inject raw AAC preceding this header. |
|---|
| 99 | if(rawstart) { |
|---|
| 100 | writeAudioTag(rawstart,_data.position-rawstart-2,false); |
|---|
| 101 | } |
|---|
| 102 | // Only write first ADTS header of file (7 bytes). Ignore the other. |
|---|
| 103 | if(!rawstart && _keyframe) { |
|---|
| 104 | writeAudioTag(_data.position-2,7,true); |
|---|
| 105 | } |
|---|
| 106 | rawstart = _data.position + 5; |
|---|
| 107 | } |
|---|
| 108 | _data.position--; |
|---|
| 109 | } |
|---|
| 110 | // Raw AAC after last ADTS header. |
|---|
| 111 | writeAudioTag(rawstart,_data.length-rawstart,false); |
|---|
| 112 | }; |
|---|
| 113 | |
|---|
| 114 | |
|---|
| 115 | /** Step through AVC to extract NAL units. **/ |
|---|
| 116 | private function parseAvc():void { |
|---|
| 117 | var units:Array = new Array(); |
|---|
| 118 | var window:uint = 0; |
|---|
| 119 | var started:Number = 0; |
|---|
| 120 | |
|---|
| 121 | // Loop through data to find NAL startcode. |
|---|
| 122 | while(_data.bytesAvailable > 3) { |
|---|
| 123 | window = _data.readUnsignedInt(); |
|---|
| 124 | // Find startcodes |
|---|
| 125 | if((window & 0xFFFFFF00) == 0x100) { |
|---|
| 126 | _data.position -= 5; |
|---|
| 127 | var four:uint = _data.readByte(); |
|---|
| 128 | _data.position += 3; |
|---|
| 129 | var head:uint = _data.readByte(); |
|---|
| 130 | // Now we know the length of the previous NAL |
|---|
| 131 | if(started) { |
|---|
| 132 | var size:Number = _data.position - started - 4; |
|---|
| 133 | if(!four) { size--; } |
|---|
| 134 | units.push(new ByteArray()); |
|---|
| 135 | units[units.length-1].writeBytes(_data,started,size); |
|---|
| 136 | units[units.length-1].position = 0; |
|---|
| 137 | } |
|---|
| 138 | started = _data.position - 1; |
|---|
| 139 | } |
|---|
| 140 | _data.position -= 3; |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | // Save the last NAL to the array. |
|---|
| 144 | if(started) { |
|---|
| 145 | units.push(new ByteArray()); |
|---|
| 146 | units[units.length-1].writeBytes(_data, started); |
|---|
| 147 | units[units.length-1].position = 0; |
|---|
| 148 | } else { |
|---|
| 149 | return; |
|---|
| 150 | throw new Error('No NAL units found.'); |
|---|
| 151 | } |
|---|
| 152 | |
|---|
| 153 | // Separate the SPS/PPS and add size headers to other NALs |
|---|
| 154 | var nalu:ByteArray = new ByteArray(); |
|---|
| 155 | var sps:ByteArray = new ByteArray(); |
|---|
| 156 | var pps:ByteArray = new ByteArray(); |
|---|
| 157 | for(var i:Number = 0; i < units.length; i++) { |
|---|
| 158 | units[i].position = 0; |
|---|
| 159 | var type:Number = units[i].readByte() & 0x1F; |
|---|
| 160 | if(type == 7) { |
|---|
| 161 | sps.writeBytes(units[i],0); |
|---|
| 162 | } else if (type == 8) { |
|---|
| 163 | pps.writeBytes(units[i],0); |
|---|
| 164 | } else { |
|---|
| 165 | // Instead of a startcode, NAL units need a 4-byte length prefix. |
|---|
| 166 | nalu.writeUnsignedInt(units[i].length); |
|---|
| 167 | nalu.writeBytes(units[i],0); |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | // Finally, write the tags |
|---|
| 172 | if(sps.length && pps.length) { |
|---|
| 173 | writeAvccTag(sps,pps); |
|---|
| 174 | } |
|---|
| 175 | writeVideoTag(nalu); |
|---|
| 176 | }; |
|---|
| 177 | |
|---|
| 178 | |
|---|
| 179 | /** Write an FLV tag with audio data. **/ |
|---|
| 180 | private function writeAudioTag(offset:uint,length:uint,adts:Boolean=false):void { |
|---|
| 181 | // Header |
|---|
| 182 | writeTagHeader(8,length + 2); |
|---|
| 183 | |
|---|
| 184 | // SoundFormat, -Rate, -Size, Type. |
|---|
| 185 | _tags.writeByte(0xAF); |
|---|
| 186 | // ADTS or raw AAC. |
|---|
| 187 | if(adts) { |
|---|
| 188 | _tags.writeByte(0x00); |
|---|
| 189 | } else { |
|---|
| 190 | _tags.writeByte(0x01); |
|---|
| 191 | } |
|---|
| 192 | // Write actual data. |
|---|
| 193 | _tags.writeBytes(_data,offset,length); |
|---|
| 194 | // PreviousTagSize |
|---|
| 195 | _tags.writeUnsignedInt(length + 13); |
|---|
| 196 | }; |
|---|
| 197 | |
|---|
| 198 | |
|---|
| 199 | /** Write an FLV tag with SPS/PPS data parsed into avcC. **/ |
|---|
| 200 | private function writeAvccTag(sps:ByteArray,pps:ByteArray):void { |
|---|
| 201 | // Header |
|---|
| 202 | writeTagHeader(9,sps.length + pps.length + 19); |
|---|
| 203 | |
|---|
| 204 | // Keyframe + AVC |
|---|
| 205 | _tags.writeByte(0x17); |
|---|
| 206 | // CompositionTime |
|---|
| 207 | _tags.writeByte(0); |
|---|
| 208 | _tags.writeByte(0); |
|---|
| 209 | _tags.writeByte(0); |
|---|
| 210 | // Sequence header |
|---|
| 211 | _tags.writeUnsignedInt(0x00); |
|---|
| 212 | |
|---|
| 213 | // avcC version. |
|---|
| 214 | _tags.writeByte(0x01); |
|---|
| 215 | // profile, compatibility and level. |
|---|
| 216 | _tags.writeBytes(sps, 1, 3); |
|---|
| 217 | // 111111 + 2 bit NAL size - 11 |
|---|
| 218 | _tags.writeByte(0xFF); |
|---|
| 219 | // Number of SPS. |
|---|
| 220 | _tags.writeByte(0xE1); |
|---|
| 221 | // SPS bytesize (UI16) |
|---|
| 222 | _tags.writeByte(sps.length >> 8); |
|---|
| 223 | _tags.writeByte(sps.length); |
|---|
| 224 | // SPS data block. |
|---|
| 225 | _tags.writeBytes(sps,0); |
|---|
| 226 | // Number of PPS |
|---|
| 227 | _tags.writeByte(0x01); |
|---|
| 228 | // PPS bytesize |
|---|
| 229 | _tags.writeByte(pps.length >> 8); |
|---|
| 230 | _tags.writeByte(pps.length); |
|---|
| 231 | // PPS data block. |
|---|
| 232 | _tags.writeBytes(pps,0); |
|---|
| 233 | |
|---|
| 234 | // PreviousTagSize |
|---|
| 235 | _tags.writeUnsignedInt(sps.length + pps.length + 30); |
|---|
| 236 | }; |
|---|
| 237 | |
|---|
| 238 | |
|---|
| 239 | /** Write an FLV tag with video data. **/ |
|---|
| 240 | private function writeVideoTag(nalu:ByteArray):void { |
|---|
| 241 | // Header |
|---|
| 242 | writeTagHeader(9,nalu.length + 5); |
|---|
| 243 | |
|---|
| 244 | // Keyframe or interframe + AVC |
|---|
| 245 | if(_keyframe) { |
|---|
| 246 | _tags.writeByte(0x17); |
|---|
| 247 | } else { |
|---|
| 248 | _tags.writeByte(0x27); |
|---|
| 249 | } |
|---|
| 250 | // AVC NALU |
|---|
| 251 | _tags.writeByte(0x01); |
|---|
| 252 | // CompositionTime |
|---|
| 253 | _tags.writeByte(_compositionTime >> 16); |
|---|
| 254 | _tags.writeByte(_compositionTime >> 8); |
|---|
| 255 | _tags.writeByte(_compositionTime); |
|---|
| 256 | // Write actual data. |
|---|
| 257 | _tags.writeBytes(nalu,0); |
|---|
| 258 | // Write PreviousTagSize |
|---|
| 259 | _tags.writeUnsignedInt(nalu.length + 16); |
|---|
| 260 | }; |
|---|
| 261 | |
|---|
| 262 | |
|---|
| 263 | /** Write the tag header. **/ |
|---|
| 264 | private function writeTagHeader(type:uint,length:uint):void { |
|---|
| 265 | _tags.writeByte(type); |
|---|
| 266 | // Size of the tag in bytes after StreamID. |
|---|
| 267 | _tags.writeByte(length >> 16); |
|---|
| 268 | _tags.writeByte(length >> 8); |
|---|
| 269 | _tags.writeByte(length); |
|---|
| 270 | // Timestamp (lower 24 plus upper 8) |
|---|
| 271 | _tags.writeByte(_timestamp >> 16); |
|---|
| 272 | _tags.writeByte(_timestamp >> 8); |
|---|
| 273 | _tags.writeByte(_timestamp); |
|---|
| 274 | _tags.writeByte(_timestamp >> 24); |
|---|
| 275 | // StreamID (3 empty bytes) |
|---|
| 276 | _tags.writeByte(0); |
|---|
| 277 | _tags.writeByte(0); |
|---|
| 278 | _tags.writeByte(0); |
|---|
| 279 | }; |
|---|
| 280 | |
|---|
| 281 | } |
|---|
| 282 | |
|---|
| 283 | |
|---|
| 284 | } |
|---|