function DecodeFragment($frag, $fragNum, $opt = array()) { $ad = null; $flvFile = null; $flvWrite = true; extract($opt, EXTR_IF_EXISTS); $debug = $this->debug; if ($this->decoderTest) { $debug = false; } $flvData = ""; $flvTag = ""; $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; } /** * Initialize Akamai decryptor * @var AkamaiDecryptor $ad */ $ad->debug = $this->debug; $ad->decryptorTest = $this->decoderTest; $ad->InitDecryptor(); 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; $tagHeader = substr($frag, $fragPos, $this->tagHeaderLen); $tagData = substr($frag, $fragPos + $this->tagHeaderLen, $packetSize); // Remove Akamai encryption if ($packetType == AKAMAI_ENC_AUDIO or $packetType == AKAMAI_ENC_VIDEO) { $opt['auth'] = $this->media['queryString']; $opt['baseUrl'] = $this->baseUrl; $tagData = $ad->Decrypt($tagData, 0, $opt); $packetType = $packetType == AKAMAI_ENC_AUDIO ? AUDIO : VIDEO; $packetSize = strlen($tagData); WriteByte($tagHeader, 0, $packetType); WriteInt24($tagHeader, 1, $packetSize); $this->sessionID = $ad->sessionID; } // 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($tagHeader, 0, $packetTS); } switch ($packetType) { case AUDIO: if ($packetTS > $this->prevAudioTS - $this->fixWindow) { $FrameInfo = ReadByte($tagData, 0); $CodecID = ($FrameInfo & 0xf0) >> 4; if ($CodecID == CODEC_ID_AAC) { $AAC_PacketType = ReadByte($tagData, 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($tagHeader, 0, $packetTS); } } $flvTag = $tagHeader . $tagData; $flvTagLen = strlen($flvTag); WriteInt32($flvTag, $flvTagLen, $flvTagLen); $flvTagLen = strlen($flvTag); if ($flvWrite and is_resource($flvFile)) { $this->pAudioTagPos = ftell($flvFile); $status = fwrite($flvFile, $flvTag, $flvTagLen); 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 .= $flvTag; 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 = $flvTagLen; } 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($tagData, 0); $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($tagData, 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($tagData, 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($tagHeader, 0, $packetTS); } } $flvTag = $tagHeader . $tagData; $flvTagLen = strlen($flvTag); WriteInt32($flvTag, $flvTagLen, $flvTagLen); $flvTagLen = strlen($flvTag); if ($flvWrite and is_resource($flvFile)) { $this->pVideoTagPos = ftell($flvFile); $status = fwrite($flvFile, $flvTag, $flvTagLen); 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 .= $flvTag; 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 = $flvTagLen; } 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 == 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 ($flvWrite and is_resource($flvFile)) { $this->filesize = ftell($flvFile) / (1024 * 1024); return true; } else { return $flvData; } }
function WriteMetadata($f4f, $flv = false) { if (isset($f4f->media) and $f4f->media['metadata']) { $metadataSize = strlen($f4f->media['metadata']); WriteByte($metadata, 0, SCRIPT_DATA); WriteInt24($metadata, 1, $metadataSize); WriteInt24($metadata, 4, 0); WriteInt32($metadata, 7, 0); $metadata = implode("", $metadata) . $f4f->media['metadata']; WriteByte($metadata, $f4f->tagHeaderLen + $metadataSize - 1, 0x9); WriteInt32($metadata, $f4f->tagHeaderLen + $metadataSize, $f4f->tagHeaderLen + $metadataSize); if (is_resource($flv)) { fwrite($flv, $metadata, $f4f->tagHeaderLen + $metadataSize + $f4f->prevTagSize); return true; } else { return $metadata; } } return false; }
if ($version == 2) { $codecTag = " "; WriteByte($codecTag, 0, 175); WriteByte($codecTag, 1, $config ? 0 : 1); } else { $codecTag = ""; } // Create flv tag $flvTag = " "; WriteByte($flvTag, 0, 8); WriteInt24($flvTag, 1, $rawLength + strlen($codecTag)); WriteFlvTimestamp($flvTag, 0, $time); WriteInt24($flvTag, 8, 0); // Write flv tag footer $audioTag = $flvTag . $codecTag . $data; WriteInt32($audioTag, strlen($audioTag), strlen($audioTag)); if ($config) { $aacCfgW ? $audioTag = "" : ($aacCfgW = true); } $flvData .= $audioTag; } $filePos += $dataLength; $cFilePos = floor($filePos / (1024 * 1024)); if ($cFilePos > $pFilePos) { LogInfo(sprintf("Processed %d/%.2f MB", $cFilePos, $fileSize), true); $pFilePos = $cFilePos; } } // Write output file if (!is_resource($flv)) { $flv = WriteFlvFile($outFile);