private bool EnableThrottling(StreamState state) { var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); // enable throttling when NOT using hardware acceleration if (encodingOptions.HardwareAccelerationType == string.Empty) { return(state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks&& state.IsInputVideo && state.VideoType == VideoType.VideoFile && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)); } return(false); }
private bool EnableThrottling(StreamState state) { var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); // enable throttling when NOT using hardware acceleration if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { return(state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks&& state.IsInputVideo && state.VideoType == VideoType.VideoFile && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)); } return(false); }
/// <summary> /// Gets the state. /// </summary> /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>StreamState.</returns> protected async Task <StreamState> GetState(StreamRequest request, CancellationToken cancellationToken) { ParseDlnaHeaders(request); if (!string.IsNullOrWhiteSpace(request.Params)) { ParseParams(request); } ParseStreamOptions(request); var url = Request.PathInfo; if (string.IsNullOrEmpty(request.AudioCodec)) { request.AudioCodec = EncodingHelper.InferAudioCodec(url); } var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) || string.Equals(GetHeader("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase); var state = new StreamState(MediaSourceManager, TranscodingJobType) { Request = request, RequestedUrl = url, UserAgent = Request.UserAgent, EnableDlnaHeaders = enableDlnaHeaders }; var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (!auth.UserId.Equals(Guid.Empty)) { state.User = UserManager.GetUserById(auth.UserId); } //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) //{ // state.SegmentLength = 6; //} if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) { state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(request.AudioCodec)) { state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) ?? state.SupportedAudioCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) { state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i)) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } var item = LibraryManager.GetItemById(request.Id); state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); //if (primaryImage != null) //{ // state.AlbumCoverPath = primaryImage.Path; //} MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { var currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) : null; if (currentJob != null) { mediaSource = currentJob.MediaSource; } if (mediaSource == null) { var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList(); mediaSource = string.IsNullOrEmpty(request.MediaSourceId) ? mediaSources[0] : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId)); if (mediaSource == null && request.MediaSourceId.Equals(request.Id)) { mediaSource = mediaSources[0]; } } } else { var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); mediaSource = liveStreamInfo.Item1; state.DirectStreamProvider = liveStreamInfo.Item2; } var videoRequest = request as VideoStreamRequest; EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url); var container = Path.GetExtension(state.RequestedUrl); if (string.IsNullOrEmpty(container)) { container = request.Container; } if (string.IsNullOrEmpty(container)) { container = request.Static ? StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : GetOutputFileExtension(state); } state.OutputContainer = (container ?? string.Empty).TrimStart('.'); state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream); state.OutputAudioCodec = state.Request.AudioCodec; state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); if (videoRequest != null) { state.OutputVideoCodec = state.VideoRequest.VideoCodec; state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); if (videoRequest != null) { EncodingHelper.TryStreamCopy(state); } if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var resolution = ResolutionNormalizer.Normalize( state.VideoStream?.BitRate, state.VideoStream?.Width, state.VideoStream?.Height, state.OutputVideoBitrate.Value, state.VideoStream?.Codec, state.OutputVideoCodec, videoRequest.MaxWidth, videoRequest.MaxHeight); videoRequest.MaxWidth = resolution.MaxWidth; videoRequest.MaxHeight = resolution.MaxHeight; } } ApplyDeviceProfileSettings(state); var ext = string.IsNullOrWhiteSpace(state.OutputContainer) ? GetOutputFileExtension(state) : ('.' + state.OutputContainer); var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext); return(state); }
/// <summary> /// Starts the FFMPEG. /// </summary> /// <param name="state">The state.</param> /// <param name="outputPath">The output path.</param> /// <param name="cancellationTokenSource">The cancellation token source.</param> /// <param name="workingDirectory">The working directory.</param> /// <returns>Task.</returns> protected async Task <TranscodingJob> StartFfMpeg( StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource, string workingDirectory = null) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) { ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); throw new ArgumentException("User does not have access to video transcoding"); } } var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); var process = new Process() { StartInfo = new ProcessStartInfo() { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false, // Must consume both stdout and stderr or deadlocks may occur //RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, FileName = MediaEncoder.EncoderPath, Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true), WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory, ErrorDialog = false }, EnableRaisingEvents = true }; var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, state.Request.PlaySessionId, state.MediaSource.LiveStreamId, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), TranscodingJobType, process, state.Request.DeviceId, state, cancellationTokenSource); var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; Logger.LogInformation(commandLineLogMessage); var logFilePrefix = "ffmpeg-transcode"; if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) { logFilePrefix = "ffmpeg-directstream"; } else { logFilePrefix = "ffmpeg-remux"; } } var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); try { process.Start(); } catch (Exception ex) { Logger.LogError(ex, "Error starting ffmpeg"); ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); throw; } Logger.LogDebug("Launched ffmpeg process"); state.TranscodingJob = transcodingJob; // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream); // Wait for the file to exist before proceeeding var ffmpegTargetFile = state.WaitForPath ?? outputPath; Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) { await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) { await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) { await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); } } if (!transcodingJob.HasExited) { StartThrottler(state, transcodingJob); } Logger.LogDebug("StartFfMpeg() finished successfully"); return(transcodingJob); }