diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-06-20 00:50:30 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-06-20 00:50:30 -0400 |
| commit | 439839378364466ae7795c99b67a123bd1008945 (patch) | |
| tree | 0923656b58425f72df354eb71fd69109eddbe8c8 /MediaBrowser.Api | |
| parent | e666fee20dd4a8343a262b9225023996b54b1004 (diff) | |
close ffmpeg more gracefully
Diffstat (limited to 'MediaBrowser.Api')
| -rw-r--r-- | MediaBrowser.Api/ApiEntryPoint.cs | 76 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/BaseStreamingService.cs | 17 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 4 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 76 | ||||
| -rw-r--r-- | MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 5 |
5 files changed, 115 insertions, 63 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index f711807541..3e9a0926be 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -3,15 +3,14 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Session; namespace MediaBrowser.Api { @@ -100,7 +99,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), KillTranscodingJob); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -291,16 +290,16 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; - KillTranscodingJob(job); + KillTranscodingJob(job, true); } /// <summary> /// Kills the single transcoding job. /// </summary> /// <param name="deviceId">The device id.</param> - /// <param name="isVideo">if set to <c>true</c> [is video].</param> + /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param> /// <exception cref="System.ArgumentNullException">sourcePath</exception> - internal void KillTranscodingJobs(string deviceId, bool isVideo) + internal void KillTranscodingJobs(string deviceId, bool deleteFiles) { if (string.IsNullOrEmpty(deviceId)) { @@ -318,7 +317,7 @@ namespace MediaBrowser.Api foreach (var job in jobs) { - KillTranscodingJob(job); + KillTranscodingJob(job, deleteFiles); } } @@ -326,7 +325,8 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> - private void KillTranscodingJob(TranscodingJob job) + /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param> + private void KillTranscodingJob(TranscodingJob job, bool deleteFiles) { lock (_activeTranscodingJobs) { @@ -344,48 +344,44 @@ namespace MediaBrowser.Api } } - var process = job.Process; - - var hasExited = true; - - try + lock (job.ProcessLock) { - hasExited = process.HasExited; - } - catch (Exception ex) - { - Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); - } - - if (!hasExited) - { - try - { - Logger.Info("Killing ffmpeg process for {0}", job.Path); + var process = job.Process; - process.Kill(); + var hasExited = true; - // Need to wait because killing is asynchronous - process.WaitForExit(5000); - } - catch (Win32Exception ex) + try { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + hasExited = process.HasExited; } - catch (InvalidOperationException ex) + catch (Exception ex) { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } - catch (NotSupportedException ex) + + if (!hasExited) { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + try + { + Logger.Info("Killing ffmpeg process for {0}", job.Path); + + //process.Kill(); + process.StandardInput.WriteLine("q"); + + // Need to wait because killing is asynchronous + process.WaitForExit(5000); + } + catch (Exception ex) + { + Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + } } } - // Dispose the process - process.Dispose(); - - DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + if (deleteFiles) + { + DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + } } private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) @@ -494,6 +490,8 @@ namespace MediaBrowser.Api public string DeviceId { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; } + + public object ProcessLock = new object(); } /// <summary> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index d2369c4101..0ecc5d9d19 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -816,6 +816,7 @@ namespace MediaBrowser.Api.Playback // Must consume both stdout and stderr or deadlocks may occur RedirectStandardOutput = true, RedirectStandardError = true, + RedirectStandardInput = true, FileName = MediaEncoder.EncoderPath, WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath), @@ -1073,8 +1074,9 @@ namespace MediaBrowser.Api.Playback /// </summary> /// <param name="process">The process.</param> /// <param name="state">The state.</param> - protected void OnFfMpegProcessExited(Process process, StreamState state) + private void OnFfMpegProcessExited(Process process, StreamState state) { + Logger.Debug("Disposing stream resources"); state.Dispose(); try @@ -1083,8 +1085,19 @@ namespace MediaBrowser.Api.Playback } catch { - Logger.Info("FFMpeg exited with an error."); + Logger.Error("FFMpeg exited with an error."); } + + // This causes on exited to be called twice: + //try + //{ + // // Dispose the process + // process.Dispose(); + //} + //catch (Exception ex) + //{ + // Logger.ErrorException("Error disposing ffmpeg.", ex); + //} } protected double? GetFramerateParam(StreamState state) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 5de0709fd2..39163a1037 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls { var cancellationTokenSource = new CancellationTokenSource(); - var state = GetState(request, cancellationTokenSource.Token).Result; + var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); var playlist = state.OutputFilePath; @@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback.Hls /// <returns>System.Int32.</returns> protected int GetSegmentWait() { - var minimumSegmentCount = 3; + var minimumSegmentCount = 2; var quality = GetQualitySetting(); if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 13e858aa53..bb547bbffd 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -1,13 +1,10 @@ using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using ServiceStack; using System; @@ -17,7 +14,6 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using MimeTypes = ServiceStack.MimeTypes; namespace MediaBrowser.Api.Playback.Hls { @@ -83,29 +79,75 @@ namespace MediaBrowser.Api.Playback.Hls return GetDynamicSegment(request, true).Result; } + private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain) { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + var state = await GetState(request, cancellationToken).ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); - var path = GetSegmentPath(playlistPath, index); + var segmentPath = GetSegmentPath(playlistPath, index); - if (File.Exists(path)) + if (File.Exists(segmentPath)) { - return GetSegementResult(path); + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + return GetSegementResult(segmentPath); } - if (!File.Exists(playlistPath)) + await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + try + { + if (File.Exists(segmentPath)) + { + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + return GetSegementResult(segmentPath); + } + else + { + if (index == 0) + { + // If the playlist doesn't already exist, startup ffmpeg + try + { + ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, false); + + await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); + } + catch + { + state.Dispose(); + throw; + } + + await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + finally { - await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false); + FfmpegStartLock.Release(); + } - await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false); + Logger.Info("waiting for {0}", segmentPath); + while (!File.Exists(segmentPath)) + { + await Task.Delay(50, cancellationToken).ConfigureAwait(false); } - return GetSegementResult(path); + Logger.Info("returning {0}", segmentPath); + return GetSegementResult(segmentPath); + } + + protected override int GetStartNumber(StreamState state) + { + var request = (GetDynamicHlsVideoSegment) state.Request; + + return int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); } private string GetSegmentPath(string playlist, int index) @@ -120,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls private object GetSegementResult(string path) { // TODO: Handle if it's currently being written to - return ResultFactory.GetStaticFileResult(Request, path); + return ResultFactory.GetStaticFileResult(Request, path, FileShare.ReadWrite); } private async Task<object> GetAsync(GetMasterHlsVideoStream request) @@ -143,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); + return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate) @@ -226,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls var playlistText = builder.ToString(); - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); + return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } protected override string GetAudioArguments(StreamState state) @@ -274,7 +316,9 @@ namespace MediaBrowser.Api.Playback.Hls return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy"; } - const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; + var keyFrameArg = state.ReadInputAtNativeFramerate ? + " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" : + " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 337cd88f25..d65d1030ca 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,19 +1,16 @@ -using System.Threading; using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using ServiceStack; using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api.Playback.Hls |
