private bool SetAsfFileProperties(AsfFileConfiguration asfConfig, FileInfo fi) { bool isKeyframe = false; uint startTimeOffsetVideo = 0; uint endTimeOffsetVideo = 0; uint startTimeOffsetAudio = 0; uint endTimeOffsetAudio = 0; bool hasVideoStream = asfConfig.ImageWidth > 0; using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { fs.Seek(asfConfig.AsfHeaderSize, SeekOrigin.Begin); long maxOffset = asfConfig.AsfHeaderSize + (asfConfig.AsfPacketCount - 1) * asfConfig.AsfPacketSize; if (maxOffset < 0) { return(false); } if (hasVideoStream) { if (!FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref startTimeOffsetVideo, asfConfig.AsfVideoStreamId)) { return(false); } fs.Seek(asfConfig.AsfHeaderSize, SeekOrigin.Begin); } if (!FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref startTimeOffsetAudio, asfConfig.AsfAudioStreamId)) { return(false); } //now position at last packet if (hasVideoStream) { fs.Seek(maxOffset, SeekOrigin.Begin); while (!FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref endTimeOffsetVideo, asfConfig.AsfVideoStreamId, false)) { maxOffset -= asfConfig.AsfPacketSize; if (maxOffset <= asfConfig.AsfHeaderSize) { return(false); } fs.Seek(maxOffset, SeekOrigin.Begin); } } //now position at last packet fs.Seek(maxOffset, SeekOrigin.Begin); while (!FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref endTimeOffsetAudio, asfConfig.AsfAudioStreamId, false)) { maxOffset -= asfConfig.AsfPacketSize; if (maxOffset <= asfConfig.AsfHeaderSize) { return(false); } fs.Seek(maxOffset, SeekOrigin.Begin); } } double fileDuration = 0; if (hasVideoStream) { fileDuration = endTimeOffsetVideo - startTimeOffsetVideo; } else { fileDuration = endTimeOffsetAudio - startTimeOffsetAudio; } fileDuration /= 1000; //set to milisecond resolution StartTimeOffsetVideo = startTimeOffsetVideo; EndTimeOffsetVideo = endTimeOffsetVideo; StartTimeOffsetAudio = startTimeOffsetAudio; EndTimeOffsetAudio = endTimeOffsetAudio; var dataObject = GetAsfObject <AsfDataObject>(); EndOffset = dataObject.Position + dataObject.Size; MediaType = endTimeOffsetVideo > 0 ? FileMediaType.Video : FileMediaType.Audio; return(true); }
/// <summary> /// Correct presentation and send time stamps based on the stream info /// <param name="configuration">The ASF configuration</param> /// <param name="asfStreamInfo">The stream info</param> /// </summary> public void SetFollowup(AsfFileConfiguration configuration, AsfStreamInfo asfStreamInfo) { Int64 packetSendTime = (Int64)SendTime - asfStreamInfo.StartSendTime; if (packetSendTime - AsfConstants.ASF_SEND_SAFTEY_THRESHOLD > 0) { packetSendTime -= AsfConstants.ASF_SEND_SAFTEY_THRESHOLD; } else { packetSendTime = 0; } SendTime = (uint)packetSendTime; //base payload on packet send time, add delta to zero based send time for (int i = 0; i < Payload.Count; i++) { Int64 payloadPresentationTime = (Int64)Payload[i].PresentationTime - asfStreamInfo.StartTimeOffset; if (payloadPresentationTime < _asfConfig.AsfPreroll) { //an audio payload before the preroll must be eliminated, in this case we assign it a private stream id if (Payload[i].StreamId == configuration.AsfAudioStreamId && asfStreamInfo.StreamType != AsfStreamType.asfUnaltered) { payloadPresentationTime = SendTime + _asfConfig.AsfPreroll; MovePayloadPrivate(Payload[i], (uint)payloadPresentationTime); } else { //set the time slightly before the preroll time - not used by renderer, but decoded so first frame (seek point) can be delta frame payloadPresentationTime = asfStreamInfo.StreamType == AsfStreamType.asfImage ? (_asfConfig.AsfPreroll - 100) : _asfConfig.AsfPreroll; } } //remove unnecesscary audio data for image stream, that means only one stream is remaining which sets the timeline if (asfStreamInfo.StreamType == AsfStreamType.asfImage && Payload[i].StreamId == configuration.AsfAudioStreamId) { MovePayloadPrivate(Payload[i], SendTime + _asfConfig.AsfPreroll); } //Packet Sendtime must be earlier than Payload presentation times if (payloadPresentationTime < SendTime) { SendTime = Math.Max((uint)payloadPresentationTime, asfStreamInfo.MinPacketSendTime); } SetPayloadPresentationTime(Payload[i], (uint)payloadPresentationTime); if ((asfStreamInfo.StreamType != AsfStreamType.asfUnaltered) && asfStreamInfo.StreamType != AsfStreamType.asfImage && Payload[i].PresentationTime > _asfConfig.AsfPreroll && Payload[i].PresentationTime - _asfConfig.AsfPreroll > (asfStreamInfo.EndTimeOffset - asfStreamInfo.StartTimeOffset)) { //Crop both audio and video at the end of the segment payloadPresentationTime = (asfStreamInfo.EndTimeOffset - asfStreamInfo.StartTimeOffset) + _asfConfig.AsfPreroll; MovePayloadPrivate(Payload[i], (uint)payloadPresentationTime); } //Handle media object id's: must be consecutive, starting at zero, roll over at 255 uint maxPresentationTime = 0; asfStreamInfo.MaxPresentationTime.TryGetValue(Payload[i].StreamId, out maxPresentationTime); if (maxPresentationTime < Payload[i].PresentationTime) { asfStreamInfo.MaxPresentationTime[Payload[i].StreamId] = Payload[i].PresentationTime; } if ((asfStreamInfo.MediaObjectId[Payload[i].StreamId] == 0 && asfStreamInfo.PrevMediaObjectId[Payload[i].StreamId] == 0) || asfStreamInfo.PrevMediaObjectId[Payload[i].StreamId] != Payload[i].MediaObjectNumber) { asfStreamInfo.MediaObjectId[Payload[i].StreamId]++; } asfStreamInfo.PrevMediaObjectId[Payload[i].StreamId] = Payload[i].MediaObjectNumber; SetMediaObjectNumber(i, asfStreamInfo.MediaObjectId[Payload[i].StreamId]); } //the send time of the next packet must be larger or equal than the send time of the current packet, keep track of send time asfStreamInfo.MinPacketSendTime = SendTime; }
protected FilePosition GetFilePosition(double searchOffset, AsfStreamType streamType, bool isStart) { FilePosition requestedPosition = new FilePosition(FileName, StartTimeOffsetVideo, 0, MediaType, (int)(searchOffset * 1000)); bool found = false; bool isKeyframe = false; bool wasKeyFrame = false; long maxOffset = 0; long minOffset = 0; UInt32 startTimeOffset = requestedPosition.TimeOffset; int diff = requestedPosition.Delta; int prevDiff = 0; FileStream fs = null; long fileOffset = 0; uint targetTimeOffset; string fileName = requestedPosition.FileName; AsfFileConfiguration asfConfig = _asfConfig; uint TargetStreamId = streamType == AsfStreamType.asfAudio ? _asfConfig.AsfAudioStreamId : _asfConfig.AsfVideoStreamId; UInt32 finalTimeOffset = (UInt32)Math.Max(0, startTimeOffset + diff); targetTimeOffset = finalTimeOffset; UInt32 maxTimeDifference = streamType == AsfStreamType.asfAudio ? AsfConstants.ASF_TIME_THRESHOLD_START_AUDIO : AsfConstants.ASF_TIME_THRESHOLD; uint averagePacketDuration = GetAveragePacketDuration(); minOffset = asfConfig.AsfHeaderSize; //set maxOffset to start of last packet maxOffset = asfConfig.AsfHeaderSize + (asfConfig.AsfPacketCount - 1) * asfConfig.AsfPacketSize; long packetCount = asfConfig.AsfPacketCount; // open the file try { fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } catch (Exception) { return(null); } int prevPacketJumpCount = 0; bool wasBroadSearch = false; int totalJumpCount = 0; int packetJumpCount = 0; long searchMaxOffset = maxOffset; long searchMinOffset = minOffset; fileOffset = minOffset; while (!found) { totalJumpCount++; if (totalJumpCount > 500) //totally arbitrary number after which to give up { return(null); } packetJumpCount = (int)Math.Round((float)diff / averagePacketDuration); if (Math.Abs(packetJumpCount) >= Math.Abs(prevPacketJumpCount) && Math.Abs(prevPacketJumpCount) > 0) { //we have to make sure that we converge, for this reason the number of packets we jump has to decrease at every iteration if (Math.Abs(prevPacketJumpCount) > 1) { packetJumpCount = packetJumpCount > 0 ? Math.Abs(prevPacketJumpCount) - 1 : -Math.Abs(prevPacketJumpCount) + 1; } else { packetJumpCount = packetJumpCount > 0 ? 1 : -1; } } prevPacketJumpCount = packetJumpCount; long byteJump = asfConfig.AsfPacketSize * packetJumpCount; if (fileOffset + byteJump > maxOffset) { byteJump = maxOffset - fileOffset; packetJumpCount = (int)(byteJump / asfConfig.AsfPacketSize); prevPacketJumpCount = packetJumpCount; } else if (fileOffset + byteJump < minOffset) { byteJump = minOffset - fileOffset; packetJumpCount = (int)(byteJump / asfConfig.AsfPacketSize); prevPacketJumpCount = packetJumpCount; } if (fs.Seek(fileOffset + byteJump, SeekOrigin.Begin) != fileOffset + byteJump) { return(null); } fileOffset += byteJump; //track backwards if we cannot find target stream in this packet while (!FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref startTimeOffset, TargetStreamId)) { fileOffset -= asfConfig.AsfPacketSize; if (fs.Seek(fileOffset, SeekOrigin.Begin) != fileOffset) { return(null); } } diff = (int)((long)finalTimeOffset - (long)startTimeOffset); if (Math.Abs(diff) <= maxTimeDifference) { found = true; if (isStart) { string.Format("Found video match at offset: {0} bytes, delta = {1} ms", fileOffset, diff).Log(LogLevel.logDetail); } } else { //handle case of multiple frames in packet, return the packet right after target time since we will track back to previous keyframe anyway if (diff > 0 && prevDiff < 0 && Math.Abs(packetJumpCount) == 1 && !wasBroadSearch) { found = true; if (isStart) { string.Format("Found video match at offset: {0} bytes, delta = {1} ms", fileOffset, diff).Log(LogLevel.logDetail); } } wasBroadSearch = false; } prevDiff = diff; } if (found && !isStart) { bool foundAudio = false; bool temp; //keep moving forward until we are PAST the requested end time or reach the end of the current asset while ((!foundAudio || (foundAudio && diff > 0)) && fileOffset <= maxOffset) { if (fileOffset <= maxOffset) { if (fs.Seek(fileOffset + asfConfig.AsfPacketSize, SeekOrigin.Begin) != fileOffset + asfConfig.AsfPacketSize) { break; } foundAudio = FindNextTimeOffset(asfConfig, fs, out temp, ref startTimeOffset, asfConfig.AsfAudioStreamId, false); fileOffset += asfConfig.AsfPacketSize; } //calculate again if (foundAudio) { diff = (int)((long)finalTimeOffset - (long)startTimeOffset); targetTimeOffset = startTimeOffset; } } } else if (found && isStart) { //now we have to find the previous keyframe, which also might be in the previous file if any exists //even if current frame is a keyframe we have to go back if the keyframe is after the match time so the correct still frame is shown in all cases //save the presentation Time of the match though, this time all earlier packets will be set to before preroll, so the //stream "fast forwards" until the match time targetTimeOffset = finalTimeOffset; if (streamType == AsfStreamType.asfAudio) { wasKeyFrame = true; // Every packet in audio is a keyframe. isKeyframe = true; } else //Video { wasKeyFrame = isKeyframe; isKeyframe = false; //always go back to the previous key frame if possible to avoid edge condition with multiple frames in the same packet } while (((streamType != AsfStreamType.asfAudio && !isKeyframe) || diff < 0) && fileOffset > minOffset) { fileOffset -= asfConfig.AsfPacketSize; if (fileOffset >= asfConfig.AsfHeaderSize) { fs.Seek(fileOffset, SeekOrigin.Begin); FindNextTimeOffset(asfConfig, fs, out isKeyframe, ref startTimeOffset, TargetStreamId); diff = (int)((long)finalTimeOffset - (long)startTimeOffset); } else { fileOffset = minOffset; fs.Dispose(); //cannot find any earlier keyframe, just return position/file as is if (found && wasKeyFrame) { return(new FilePosition(fileName, targetTimeOffset, fileOffset, requestedPosition.MediaType, diff)); } else { return(null); } } } //while //calculate again diff = (int)((long)finalTimeOffset - (long)startTimeOffset); string.Format("Found keyframe at offset : {0} ms, delta = {1} ms", startTimeOffset, diff).Log(LogLevel.logDetail); } if (!isStart && fileOffset <= maxOffset) { fileOffset += asfConfig.AsfPacketSize; //include current packet } fs.Dispose(); if (found) { return(new FilePosition(fileName, targetTimeOffset, fileOffset, requestedPosition.MediaType, diff)); } else { return(null); //cannot find any earlier keyframe, just return position/file as is } }