Added input piping

Former-commit-id: 13d5e3d191
This commit is contained in:
Максим Багрянцев 2020-04-27 19:23:31 +03:00
parent 74c1222b9c
commit 2c2ceacb41
10 changed files with 389 additions and 38 deletions

View file

@ -0,0 +1,89 @@
using FFMpegCore.FFMPEG.Pipes;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.Extend
{
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
{
public int Width => Source.Width;
public int Height => Source.Height;
public string Format { get; private set; }
public Bitmap Source { get; private set; }
public BitmapVideoFrameWrapper(Bitmap bitmap)
{
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
Format = ConvertStreamFormat(bitmap.PixelFormat);
}
public void Serialize(IInputPipe pipe)
{
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
try
{
var buffer = new byte[data.Stride * data.Height];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
pipe.Write(buffer, 0, buffer.Length);
}
finally
{
Source.UnlockBits(data);
}
}
public async Task SerializeAsync(IInputPipe pipe)
{
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
try
{
var buffer = new byte[data.Stride * data.Height];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
await pipe.WriteAsync(buffer, 0, buffer.Length);
}
finally
{
Source.UnlockBits(data);
}
}
public void Dispose()
{
Source.Dispose();
}
private static string ConvertStreamFormat(PixelFormat fmt)
{
switch (fmt)
{
case PixelFormat.Format16bppGrayScale:
return "gray16le";
case PixelFormat.Format16bppRgb565:
return "bgr565le";
case PixelFormat.Format24bppRgb:
return "rgb24";
case PixelFormat.Format32bppArgb:
return "rgba";
case PixelFormat.Format32bppPArgb:
//This is not really same as argb32
return "argb";
case PixelFormat.Format32bppRgb:
return "rgba";
case PixelFormat.Format48bppRgb:
return "rgb48le";
default:
throw new NotSupportedException($"Not supported pixel format {fmt}");
}
}
}
}

View file

@ -102,8 +102,9 @@ public void Add(params Argument[] values)
/// <returns></returns>
public bool ContainsInputOutput()
{
return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument))) ||
(!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument))))
return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) ||
(!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) ||
(!ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && ContainsKey(typeof(InputPipeArgument))))
&& ContainsKey(typeof(OutputArgument));
}

View file

@ -0,0 +1,74 @@
using FFMpegCore.FFMPEG.Pipes;
using Instances;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Argument
{
public class InputPipeArgument : Argument, IInputPipe
{
public string PipeName { get; private set; }
public IPipeSource Source { get; private set; }
private NamedPipeServerStream pipe;
public InputPipeArgument(IPipeSource source)
{
Source = source;
PipeName = "FFMpegCore_Pipe_" + Guid.NewGuid();
}
public void OpenPipe()
{
if (pipe != null)
throw new InvalidOperationException("Pipe already has been opened");
pipe = new NamedPipeServerStream(PipeName);
}
public void ClosePipe()
{
pipe?.Dispose();
pipe = null;
}
public void Write(byte[] buffer, int offset, int count)
{
if(pipe == null)
throw new InvalidOperationException("Pipe shouled be opened before");
pipe.Write(buffer, offset, count);
}
public Task WriteAsync(byte[] buffer, int offset, int count)
{
if (pipe == null)
throw new InvalidOperationException("Pipe shouled be opened before");
return pipe.WriteAsync(buffer, offset, count);
}
public override string GetStringValue()
{
return $"-y {Source.GetFormat()} -i \\\\.\\pipe\\{PipeName}";
}
public void FlushPipe()
{
pipe.WaitForConnection();
Source.FlushData(this);
}
public async Task FlushPipeAsync()
{
await pipe.WaitForConnectionAsync();
await Source.FlushDataAsync(this);
}
}
}

View file

@ -384,6 +384,7 @@ public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output,
public VideoInfo Convert(ArgumentContainer arguments)
{
var (sources, output) = GetInputOutput(arguments);
if (sources != null)
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
if (!RunProcess(arguments, output))
@ -395,6 +396,7 @@ public VideoInfo Convert(ArgumentContainer arguments)
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments)
{
var (sources, output) = GetInputOutput(arguments);
if (sources != null)
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
if (!await RunProcessAsync(arguments, output))
@ -412,6 +414,8 @@ private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentConta
sources = input.GetAsVideoInfo();
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
sources = concat.GetAsVideoInfo();
else if (arguments.TryGetArgument<InputPipeArgument>(out var pipe))
sources = null;
else
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
return (sources, output);
@ -442,23 +446,68 @@ private bool RunProcess(ArgumentContainer container, FileInfo output)
{
_instance?.Dispose();
var arguments = ArgumentBuilder.BuildArguments(container);
var exitCode = -1;
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
{
inputPipeArgument.OpenPipe();
}
try
{
_instance = new Instance(_ffmpegPath, arguments);
_instance.DataReceived += OutputData;
var exitCode = _instance.BlockUntilFinished();
if (inputPipeArgument != null)
{
try
{
var task = _instance.FinishedRunning();
inputPipeArgument.FlushPipe();
inputPipeArgument.ClosePipe();
task.Wait();
exitCode = task.Result;
}
catch (Exception ex)
{
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
}
}
else
{
exitCode = _instance.BlockUntilFinished();
}
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
return exitCode == 0;
}
finally
{
if (inputPipeArgument != null)
inputPipeArgument.ClosePipe();
}
}
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output)
{
_instance?.Dispose();
var arguments = ArgumentBuilder.BuildArguments(container);
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
{
inputPipeArgument.OpenPipe();
}
try
{
_instance = new Instance(_ffmpegPath, arguments);
_instance.DataReceived += OutputData;
if (inputPipeArgument != null)
{
await inputPipeArgument.FlushPipeAsync();
}
var exitCode = await _instance.FinishedRunning();
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
@ -466,6 +515,14 @@ private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo o
return exitCode == 0;
}
finally
{
if (inputPipeArgument != null)
{
inputPipeArgument.ClosePipe();
}
}
}
private void Cleanup(IEnumerable<string> pathList)
{

View file

@ -47,7 +47,7 @@ public Task<VideoInfo> ParseVideoInfoAsync(string source)
/// <returns>A video info object containing all details necessary.</returns>
public VideoInfo ParseVideoInfo(VideoInfo info)
{
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info)) {DataBufferCapacity = _outputCapacity};
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
instance.BlockUntilFinished();
var output = string.Join("", instance.OutputData);
return ParseVideoInfoInternal(info, output);
@ -59,14 +59,14 @@ public VideoInfo ParseVideoInfo(VideoInfo info)
/// <returns>A video info object containing all details necessary.</returns>
public async Task<VideoInfo> ParseVideoInfoAsync(VideoInfo info)
{
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info)) {DataBufferCapacity = _outputCapacity};
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
await instance.FinishedRunning();
var output = string.Join("", instance.OutputData);
return ParseVideoInfoInternal(info, output);
}
private static string BuildFFProbeArguments(VideoInfo info) =>
$"-v quiet -print_format json -show_streams \"{info.FullName}\"";
private static string BuildFFProbeArguments(string fullPath) =>
$"-v quiet -print_format json -show_streams \"{fullPath}\"";
private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
{
@ -133,5 +133,21 @@ private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
return info;
}
internal FFMpegStreamMetadata GetMetadata(string path)
{
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
instance.BlockUntilFinished();
var output = string.Join("", instance.OutputData);
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
}
internal async Task<FFMpegStreamMetadata> GetMetadataAsync(string path)
{
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
await instance.FinishedRunning();
var output = string.Join("", instance.OutputData);
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
}
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Pipes
{
public interface IInputPipe
{
void Write(byte[] buffer, int offset, int count);
Task WriteAsync(byte[] buffer, int offset, int count);
}
}

View file

@ -0,0 +1,15 @@
using FFMpegCore.FFMPEG.Argument;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Pipes
{
public interface IPipeSource
{
string GetFormat();
void FlushData(IInputPipe pipe);
Task FlushDataAsync(IInputPipe pipe);
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Pipes
{
public interface IVideoFrame
{
int Width { get; }
int Height { get; }
string Format { get; }
void Serialize(IInputPipe pipe);
Task SerializeAsync(IInputPipe pipe);
}
}

View file

@ -0,0 +1,67 @@
using FFMpegCore.FFMPEG.Argument;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FFMpegCore.FFMPEG.Pipes
{
public class RawVideoPipeSource : IPipeSource
{
public string StreamFormat { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int FrameRate { get; set; } = 25;
private IEnumerator<IVideoFrame> framesEnumerator;
public RawVideoPipeSource(IEnumerator<IVideoFrame> framesEnumerator)
{
this.framesEnumerator = framesEnumerator;
}
public RawVideoPipeSource(IEnumerable<IVideoFrame> framesEnumerator) : this(framesEnumerator.GetEnumerator()) { }
public string GetFormat()
{
//see input format references https://lists.ffmpeg.org/pipermail/ffmpeg-user/2012-July/007742.html
if (framesEnumerator.Current == null)
{
if (!framesEnumerator.MoveNext())
throw new InvalidOperationException("Enumerator is empty, unable to get frame");
StreamFormat = framesEnumerator.Current.Format;
Width = framesEnumerator.Current.Width;
Height = framesEnumerator.Current.Height;
}
return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}";
}
public void FlushData(IInputPipe pipe)
{
if (framesEnumerator.Current != null)
{
framesEnumerator.Current.Serialize(pipe);
}
while (framesEnumerator.MoveNext())
{
framesEnumerator.Current.Serialize(pipe);
}
}
public async Task FlushDataAsync(IInputPipe pipe)
{
if (framesEnumerator.Current != null)
{
await framesEnumerator.Current.SerializeAsync(pipe);
}
while (framesEnumerator.MoveNext())
{
await framesEnumerator.Current.SerializeAsync(pipe);
}
}
}
}

View file

@ -1,4 +1,6 @@
using FFMpegCore.FFMPEG;
using FFMpegCore.FFMPEG.Argument;
using FFMpegCore.FFMPEG.Pipes;
using System;
using System.IO;