From c30b82ab44401cf86f6723834d8d5b1ea8aac66c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 29 Nov 2016 14:13:20 -0500 Subject: [PATCH] update recording encoder --- .../EnvironmentInfo/EnvironmentInfo.cs | 5 ++ Emby.Server.Core/ApplicationHost.cs | 3 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 83 ++++--------------- .../MediaEncoding/IMediaEncoder.cs | 3 + .../Encoder/MediaEncoder.cs | 59 ++++++++++++- MediaBrowser.Model/System/IEnvironmentInfo.cs | 8 +- 6 files changed, 88 insertions(+), 73 deletions(-) diff --git a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs index ebd596dc41..6cc4626eaf 100644 --- a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs @@ -110,5 +110,10 @@ namespace Emby.Common.Implementations.EnvironmentInfo { get { return Environment.StackTrace; } } + + public void SetProcessEnvironmentVariable(string name, string value) + { + Environment.SetEnvironmentVariable(name, value); + } } } diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 90848d930e..f7fe02fbaa 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -789,7 +789,8 @@ namespace Emby.Server.Core MemoryStreamFactory, ProcessFactory, (Environment.ProcessorCount > 2 ? 14000 : 40000), - EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); + EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows, + EnvironmentInfo); MediaEncoder = mediaEncoder; RegisterSingleInstance(MediaEncoder); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 5e55b893f3..5bb65b6506 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -31,7 +31,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IServerApplicationPaths _appPaths; private readonly LiveTvOptions _liveTvOptions; private bool _hasExited; - private Stream _logFileStream; private string _targetPath; private IProcess _process; private readonly IProcessFactory _processFactory; @@ -85,22 +84,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); + var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid().ToString("N") + ".txt"); + _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); + var process = _processFactory.Create(new ProcessOptions { CreateNoWindow = true, - UseShellExecute = false, + UseShellExecute = true, // Must consume both stdout and stderr or deadlocks may occur //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, + RedirectStandardError = false, + RedirectStandardInput = false, FileName = _mediaEncoder.EncoderPath, Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration), IsHidden = true, ErrorDialog = false, - EnableRaisingEvents = true + EnableRaisingEvents = true, + WorkingDirectory = Path.GetDirectoryName(logFilePath) }); _process = process; @@ -108,14 +111,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; _logger.Info(commandLineLogMessage); - var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); + _mediaEncoder.SetLogFilename(Path.GetFileName(logFilePath)); - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); + //var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile); @@ -128,10 +126,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV onStarted(); - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - StartStreamingLog(process.StandardError.BaseStream, _logFileStream); - _logger.Info("ffmpeg recording process started for {0}", _targetPath); + _mediaEncoder.ClearLogFilename(); return _taskCompletionSource.Task; } @@ -154,7 +150,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; - var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -loglevel info -y \"{1}\""; long startTimeTicks = 0; //if (mediaSource.DateLiveStreamOpened.HasValue) @@ -234,16 +230,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return output; } + private bool _isCancelled; private void Stop() { if (!_hasExited) { try { + _isCancelled = true; + _logger.Info("Killing ffmpeg recording process for {0}", _targetPath); - //process.Kill(); - _process.StandardInput.WriteLine("q"); + _process.Kill(); + //_process.StandardInput.WriteLine("q"); } catch (Exception ex) { @@ -259,11 +258,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _hasExited = true; - DisposeLogStream(); - try { - var exitCode = process.ExitCode; + var exitCode = _isCancelled ? 0 : process.ExitCode; _logger.Info("FFMpeg recording exited with code {0} for {1}", exitCode, _targetPath); @@ -282,49 +279,5 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); } } - - private void DisposeLogStream() - { - if (_logFileStream != null) - { - try - { - _logFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing recording log stream", ex); - } - - _logFileStream = null; - } - } - - private async void StartStreamingLog(Stream source, Stream target) - { - try - { - using (var reader = new StreamReader(source)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); - - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - await target.FlushAsync().ConfigureAwait(false); - } - } - } - catch (ObjectDisposedException) - { - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } - catch (Exception ex) - { - _logger.ErrorException("Error reading ffmpeg recording log", ex); - } - } } } \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 866a08aa1f..45aaa8e8e6 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -135,5 +135,8 @@ namespace MediaBrowser.Controller.MediaEncoding Task UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); bool IsDefaultEncoderPath { get; } + + void SetLogFilename(string name); + void ClearLogFilename(); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 719083e732..a8ab1a4c0e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -24,6 +24,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.System; namespace MediaBrowser.MediaEncoding.Encoder { @@ -88,9 +89,11 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly int DefaultImageExtractionTimeoutMs; private readonly bool EnableEncoderFontFile; + private readonly IEnvironmentInfo _environmentInfo; + public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, int defaultImageExtractionTimeoutMs, - bool enableEncoderFontFile) + bool enableEncoderFontFile, IEnvironmentInfo environmentInfo) { _logger = logger; _jsonSerializer = jsonSerializer; @@ -109,12 +112,66 @@ namespace MediaBrowser.MediaEncoding.Encoder _processFactory = processFactory; DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; EnableEncoderFontFile = enableEncoderFontFile; + _environmentInfo = environmentInfo; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; _originalFFProbePath = ffProbePath; _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; + + SetEnvironmentVariable(); + } + + private readonly object _logLock = new object(); + public void SetLogFilename(string name) + { + lock (_logLock) + { + try + { + _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + } + } + + public void ClearLogFilename() + { + lock (_logLock) + { + try + { + _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null); + } + catch (Exception ex) + { + //_logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + } + } + + private void SetEnvironmentVariable() + { + try + { + //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + try + { + //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } } public string EncoderLocationType diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs index f9795a5684..abe39fa03d 100644 --- a/MediaBrowser.Model/System/IEnvironmentInfo.cs +++ b/MediaBrowser.Model/System/IEnvironmentInfo.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace MediaBrowser.Model.System { public interface IEnvironmentInfo @@ -13,6 +8,7 @@ namespace MediaBrowser.Model.System string OperatingSystemVersion { get; } Architecture SystemArchitecture { get; } string GetEnvironmentVariable(string name); + void SetProcessEnvironmentVariable(string name, string value); string GetUserId(); string StackTrace { get; } }