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 } }
internal bool SetOffsetRange(double startOffset, double endOffset, out FilePosition requestStartPosition, out FilePosition requestEndPosition, AsfStreamType streamType) { requestStartPosition = null; requestEndPosition = null; // find the file and offset for the starting time requestStartPosition = GetFilePosition(startOffset, streamType, true); if (requestStartPosition == null) { return(false); } if (endOffset == 0) { long endFileOffset = EndOffset; requestEndPosition = new FilePosition(requestStartPosition.FileName, uint.MaxValue, endFileOffset); } else { // find the file and offset for the ending time requestEndPosition = GetFilePosition(endOffset, streamType, false); if (requestEndPosition == null) { return(false); } } StartOffset = requestStartPosition.FileOffset; EndOffset = requestEndPosition.FileOffset; Length = Convert.ToUInt32(EndOffset - StartOffset); if (_fileStream != null) { _fileStream.Dispose(); } _fileStream = null; return(true); }