function DecodeFragment($frag, $fragNum, $opt = array()) { $debug = $this->debug; $flv = false; extract($opt, EXTR_IF_EXISTS); $flvData = ""; $fragPos = 0; $packetTS = 0; $fragLen = strlen($frag); if (!$this->VerifyFragment($frag)) { LogInfo("Skipping fragment number {$fragNum}"); return false; } while ($fragPos < $fragLen) { ReadBoxHeader($frag, $fragPos, $boxType, $boxSize); if ($boxType == "mdat") { $fragLen = $fragPos + $boxSize; break; } $fragPos += $boxSize; } LogDebug(sprintf("\nFragment %d:\n" . $this->format . "%-16s", $fragNum, "Type", "CurrentTS", "PreviousTS", "Size", "Position"), $debug); while ($fragPos < $fragLen) { $packetType = ReadByte($frag, $fragPos); $packetSize = ReadInt24($frag, $fragPos + 1); $packetTS = ReadInt24($frag, $fragPos + 4); $packetTS = $packetTS | ReadByte($frag, $fragPos + 7) << 24; if ($packetTS & 0x80000000) { $packetTS &= 0x7fffffff; } $totalTagLen = $this->tagHeaderLen + $packetSize + $this->prevTagSize; // Try to fix the odd timestamps and make them zero based $currentTS = $packetTS; $lastTS = $this->prevVideoTS >= $this->prevAudioTS ? $this->prevVideoTS : $this->prevAudioTS; $fixedTS = $lastTS + FRAMEFIX_STEP; if ($this->baseTS == INVALID_TIMESTAMP and ($packetType == AUDIO or $packetType == VIDEO)) { $this->baseTS = $packetTS; } if ($this->baseTS > 1000 and $packetTS >= $this->baseTS) { $packetTS -= $this->baseTS; } if ($lastTS != INVALID_TIMESTAMP) { $timeShift = $packetTS - $lastTS; if ($timeShift > $this->fixWindow) { LogDebug("Timestamp gap detected: PacketTS=" . $packetTS . " LastTS=" . $lastTS . " Timeshift=" . $timeShift, $debug); if ($this->baseTS < $packetTS) { $this->baseTS += $timeShift - FRAMEFIX_STEP; } else { $this->baseTS = $timeShift - FRAMEFIX_STEP; } $packetTS = $fixedTS; } else { $lastTS = $packetType == VIDEO ? $this->prevVideoTS : $this->prevAudioTS; if ($packetTS < $lastTS - $this->fixWindow) { if ($this->negTS != INVALID_TIMESTAMP and $packetTS + $this->negTS < $lastTS - $this->fixWindow) { $this->negTS = INVALID_TIMESTAMP; } if ($this->negTS == INVALID_TIMESTAMP) { $this->negTS = $fixedTS - $packetTS; LogDebug("Negative timestamp detected: PacketTS=" . $packetTS . " LastTS=" . $lastTS . " NegativeTS=" . $this->negTS, $debug); $packetTS = $fixedTS; } else { if ($packetTS + $this->negTS <= $lastTS + $this->fixWindow) { $packetTS += $this->negTS; } else { $this->negTS = $fixedTS - $packetTS; LogDebug("Negative timestamp override: PacketTS=" . $packetTS . " LastTS=" . $lastTS . " NegativeTS=" . $this->negTS, $debug); $packetTS = $fixedTS; } } } } } if ($packetTS != $currentTS) { WriteFlvTimestamp($frag, $fragPos, $packetTS); } switch ($packetType) { case AUDIO: if ($packetTS > $this->prevAudioTS - $this->fixWindow) { $FrameInfo = ReadByte($frag, $fragPos + $this->tagHeaderLen); $CodecID = ($FrameInfo & 0xf0) >> 4; if ($CodecID == CODEC_ID_AAC) { $AAC_PacketType = ReadByte($frag, $fragPos + $this->tagHeaderLen + 1); if ($AAC_PacketType == AAC_SEQUENCE_HEADER) { if ($this->AAC_HeaderWritten) { LogDebug(sprintf("%s\n" . $this->format, "Skipping AAC sequence header", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug); break; } else { LogDebug("Writing AAC sequence header", $debug); $this->AAC_HeaderWritten = true; } } else { if (!$this->AAC_HeaderWritten) { LogDebug(sprintf("%s\n" . $this->format, "Discarding audio packet received before AAC sequence header", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug); break; } } } if ($packetSize > 0) { // Check for packets with non-monotonic audio timestamps and fix them if (!($CodecID == CODEC_ID_AAC and ($AAC_PacketType == AAC_SEQUENCE_HEADER or $this->prevAAC_Header))) { if ($this->prevAudioTS != INVALID_TIMESTAMP and $packetTS <= $this->prevAudioTS) { LogDebug(sprintf("%s\n" . $this->format, "Fixing audio timestamp", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug); $packetTS += FRAMEFIX_STEP / 5 + ($this->prevAudioTS - $packetTS); WriteFlvTimestamp($frag, $fragPos, $packetTS); } } if (is_resource($flv)) { $this->pAudioTagPos = ftell($flv); $status = fwrite($flv, substr($frag, $fragPos, $totalTagLen), $totalTagLen); if (!$status) { LogError("Failed to write flv data to file"); } if ($debug) { LogDebug(sprintf($this->format . "%-16s", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize, $this->pAudioTagPos)); } } else { $flvData .= substr($frag, $fragPos, $totalTagLen); if ($debug) { LogDebug(sprintf($this->format, "AUDIO", $packetTS, $this->prevAudioTS, $packetSize)); } } if ($CodecID == CODEC_ID_AAC and $AAC_PacketType == AAC_SEQUENCE_HEADER) { $this->prevAAC_Header = true; } else { $this->prevAAC_Header = false; } $this->prevAudioTS = $packetTS; $this->pAudioTagLen = $totalTagLen; } else { LogDebug(sprintf("%s\n" . $this->format, "Skipping small sized audio packet", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug); } } else { LogDebug(sprintf("%s\n" . $this->format, "Skipping audio packet in fragment {$fragNum}", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug); } if (!$this->audio) { $this->audio = true; } break; case VIDEO: if ($packetTS > $this->prevVideoTS - $this->fixWindow) { $FrameInfo = ReadByte($frag, $fragPos + $this->tagHeaderLen); $FrameType = ($FrameInfo & 0xf0) >> 4; $CodecID = $FrameInfo & 0xf; if ($FrameType == FRAME_TYPE_INFO) { LogDebug(sprintf("%s\n" . $this->format, "Skipping video info frame", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); break; } if ($CodecID == CODEC_ID_AVC) { $AVC_PacketType = ReadByte($frag, $fragPos + $this->tagHeaderLen + 1); if ($AVC_PacketType == AVC_SEQUENCE_HEADER) { if ($this->AVC_HeaderWritten) { LogDebug(sprintf("%s\n" . $this->format, "Skipping AVC sequence header", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); break; } else { LogDebug("Writing AVC sequence header", $debug); $this->AVC_HeaderWritten = true; } } else { if (!$this->AVC_HeaderWritten) { LogDebug(sprintf("%s\n" . $this->format, "Discarding video packet received before AVC sequence header", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); break; } } } if ($packetSize > 0) { $pts = $packetTS; if ($CodecID == CODEC_ID_AVC and $AVC_PacketType == AVC_NALU) { $cts = ReadInt24($frag, $fragPos + $this->tagHeaderLen + 2); $cts = $cts + 0xff800000 ^ 0xff800000; $pts = $packetTS + $cts; if ($cts != 0) { LogDebug("DTS: {$packetTS} CTS: {$cts} PTS: {$pts}", $debug); } } // Check for packets with non-monotonic video timestamps and fix them if (!($CodecID == CODEC_ID_AVC and ($AVC_PacketType == AVC_SEQUENCE_HEADER or $AVC_PacketType == AVC_SEQUENCE_END or $this->prevAVC_Header))) { if ($this->prevVideoTS != INVALID_TIMESTAMP and $packetTS <= $this->prevVideoTS) { LogDebug(sprintf("%s\n" . $this->format, "Fixing video timestamp", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); $packetTS += FRAMEFIX_STEP / 5 + ($this->prevVideoTS - $packetTS); WriteFlvTimestamp($frag, $fragPos, $packetTS); } } if (is_resource($flv)) { $this->pVideoTagPos = ftell($flv); $status = fwrite($flv, substr($frag, $fragPos, $totalTagLen), $totalTagLen); if (!$status) { LogError("Failed to write flv data to file"); } if ($debug) { LogDebug(sprintf($this->format . "%-16s", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize, $this->pVideoTagPos)); } } else { $flvData .= substr($frag, $fragPos, $totalTagLen); if ($debug) { LogDebug(sprintf($this->format, "VIDEO", $packetTS, $this->prevVideoTS, $packetSize)); } } if ($CodecID == CODEC_ID_AVC and $AVC_PacketType == AVC_SEQUENCE_HEADER) { $this->prevAVC_Header = true; } else { $this->prevAVC_Header = false; } $this->prevVideoTS = $packetTS; $this->pVideoTagLen = $totalTagLen; } else { LogDebug(sprintf("%s\n" . $this->format, "Skipping small sized video packet", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); } } else { LogDebug(sprintf("%s\n" . $this->format, "Skipping video packet in fragment {$fragNum}", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug); } if (!$this->video) { $this->video = true; } break; case SCRIPT_DATA: break; default: if ($packetType == 10 or $packetType == 11) { LogError("This stream is encrypted with Akamai DRM. Decryption of such streams isn't currently possible with this script.", 2); } else { if ($packetType == 40 or $packetType == 41) { LogError("This stream is encrypted with FlashAccess DRM. Decryption of such streams isn't currently possible with this script.", 2); } else { LogInfo("Unknown packet type " . $packetType . " encountered! Unable to process fragment {$fragNum}"); break 2; } } } $fragPos += $totalTagLen; } $this->duration = round($packetTS / 1000, 0); if (is_resource($flv)) { $this->filesize = ftell($flv) / (1024 * 1024); return true; } else { return $flvData; } }
break; } else { LogDebug("Writing AVC sequence header"); $AVC_HeaderWritten = true; } } else { if (!$AVC_HeaderWritten) { LogDebug(sprintf("%s\n" . $format, "Discarding video packet received before AVC sequence header", "VIDEO", $packetTS, $prevVideoTS, $packetSize)); break; } } } if ($packetSize > 0) { $pts = $packetTS; if ($CodecID == CODEC_ID_AVC and $AVC_PacketType == AVC_NALU) { $cts = ReadInt24($flvTag, $tagPos + $tagHeaderLen + 2); $cts = $cts + 0xff800000 ^ 0xff800000; $pts = $packetTS + $cts; if ($cts != 0) { LogDebug("DTS: {$packetTS} CTS: {$cts} PTS: {$pts}"); } } // Check for packets with non-monotonic video timestamps and fix them if (!($CodecID == CODEC_ID_AVC and ($AVC_PacketType == AVC_SEQUENCE_HEADER or $AVC_PacketType == AVC_SEQUENCE_END or $prevAVC_Header))) { if ($prevVideoTS != INVALID_TIMESTAMP and $packetTS <= $prevVideoTS) { LogDebug(sprintf("%s\n" . $format, "Fixing video timestamp", "VIDEO", $packetTS, $prevVideoTS, $packetSize)); $packetTS += FRAMEFIX_STEP / 5 + ($prevVideoTS - $packetTS); WriteFlvTimestamp($flvTag, $tagPos, $packetTS); } } $pVideoTagPos = ftell($flvOut);