From 4eafa4533cbdf68ba63e0c91a1f414740e04d8cb Mon Sep 17 00:00:00 2001 From: Julien Loir Date: Mon, 12 Apr 2021 13:48:55 +0200 Subject: [PATCH 1/5] Move `System.IO` in `using`s on some classes Former-commit-id: e10657169ea002cee05e7cd3d809ec936d678cfb --- FFMpegCore/Extend/BitmapVideoFrameWrapper.cs | 9 +++++---- FFMpegCore/FFMpeg/Pipes/IPipeSink.cs | 5 +++-- FFMpegCore/FFMpeg/Pipes/IPipeSource.cs | 5 +++-- FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs | 7 ++++--- FFMpegCore/FFMpeg/Pipes/RawVideoPipeSource.cs | 3 ++- FFMpegCore/FFMpeg/Pipes/StreamPipeSink.cs | 2 +- FFMpegCore/FFMpeg/Pipes/StreamPipeSource.cs | 5 +++-- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs index e2f0737..36790d4 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -1,10 +1,11 @@ -using System; +using FFMpegCore.Pipes; +using System; using System.Drawing; using System.Drawing.Imaging; +using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using FFMpegCore.Pipes; namespace FFMpegCore.Extend { @@ -24,7 +25,7 @@ public BitmapVideoFrameWrapper(Bitmap bitmap) Format = ConvertStreamFormat(bitmap.PixelFormat); } - public void Serialize(System.IO.Stream stream) + public void Serialize(Stream stream) { var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat); @@ -40,7 +41,7 @@ public void Serialize(System.IO.Stream stream) } } - public async Task SerializeAsync(System.IO.Stream stream, CancellationToken token) + public async Task SerializeAsync(Stream stream, CancellationToken token) { var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat); diff --git a/FFMpegCore/FFMpeg/Pipes/IPipeSink.cs b/FFMpegCore/FFMpeg/Pipes/IPipeSink.cs index 875407e..e5f2bf4 100644 --- a/FFMpegCore/FFMpeg/Pipes/IPipeSink.cs +++ b/FFMpegCore/FFMpeg/Pipes/IPipeSink.cs @@ -1,11 +1,12 @@ -using System.Threading; +using System.IO; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.Pipes { public interface IPipeSink { - Task ReadAsync(System.IO.Stream inputStream, CancellationToken cancellationToken); + Task ReadAsync(Stream inputStream, CancellationToken cancellationToken); string GetFormat(); } } diff --git a/FFMpegCore/FFMpeg/Pipes/IPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/IPipeSource.cs index cdd5139..c250421 100644 --- a/FFMpegCore/FFMpeg/Pipes/IPipeSource.cs +++ b/FFMpegCore/FFMpeg/Pipes/IPipeSource.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.IO; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.Pipes @@ -9,6 +10,6 @@ namespace FFMpegCore.Pipes public interface IPipeSource { string GetStreamArguments(); - Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken); + Task WriteAsync(Stream outputStream, CancellationToken cancellationToken); } } diff --git a/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs b/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs index 094040b..dd583d9 100644 --- a/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs +++ b/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.IO; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.Pipes @@ -12,7 +13,7 @@ public interface IVideoFrame int Height { get; } string Format { get; } - void Serialize(System.IO.Stream pipe); - Task SerializeAsync(System.IO.Stream pipe, CancellationToken token); + void Serialize(Stream pipe); + Task SerializeAsync(Stream pipe, CancellationToken token); } } diff --git a/FFMpegCore/FFMpeg/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/RawVideoPipeSource.cs index 65f622e..0e3ab61 100644 --- a/FFMpegCore/FFMpeg/Pipes/RawVideoPipeSource.cs +++ b/FFMpegCore/FFMpeg/Pipes/RawVideoPipeSource.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading; using System.Threading.Tasks; using FFMpegCore.Exceptions; @@ -46,7 +47,7 @@ public string GetStreamArguments() return $"-f rawvideo -r {FrameRate.ToString(CultureInfo.InvariantCulture)} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } - public async Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken) + public async Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) { if (_framesEnumerator.Current != null) { diff --git a/FFMpegCore/FFMpeg/Pipes/StreamPipeSink.cs b/FFMpegCore/FFMpeg/Pipes/StreamPipeSink.cs index cd13f40..addc14e 100644 --- a/FFMpegCore/FFMpeg/Pipes/StreamPipeSink.cs +++ b/FFMpegCore/FFMpeg/Pipes/StreamPipeSink.cs @@ -20,7 +20,7 @@ public StreamPipeSink(Stream destination) Writer = (inputStream, cancellationToken) => inputStream.CopyToAsync(destination, BlockSize, cancellationToken); } - public Task ReadAsync(System.IO.Stream inputStream, CancellationToken cancellationToken) + public Task ReadAsync(Stream inputStream, CancellationToken cancellationToken) => Writer(inputStream, cancellationToken); public string GetFormat() => Format; diff --git a/FFMpegCore/FFMpeg/Pipes/StreamPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/StreamPipeSource.cs index 404029f..87eaee7 100644 --- a/FFMpegCore/FFMpeg/Pipes/StreamPipeSource.cs +++ b/FFMpegCore/FFMpeg/Pipes/StreamPipeSource.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.IO; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.Pipes @@ -19,6 +20,6 @@ public StreamPipeSource(System.IO.Stream source) public string GetStreamArguments() => StreamFormat; - public Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken) => Source.CopyToAsync(outputStream, BlockSize, cancellationToken); + public Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) => Source.CopyToAsync(outputStream, BlockSize, cancellationToken); } } From 7bee36e2cacee71a0211d2306151ed19890a4c5a Mon Sep 17 00:00:00 2001 From: Julien Loir Date: Mon, 12 Apr 2021 13:49:36 +0200 Subject: [PATCH 2/5] Add support for `Format16bppRgb555` in Bitmap wrapper Former-commit-id: 1711f98bd535609355dd7cae45e1edb389c784dd --- FFMpegCore/Extend/BitmapVideoFrameWrapper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs index 36790d4..bc81643 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -68,6 +68,8 @@ private static string ConvertStreamFormat(PixelFormat fmt) { case PixelFormat.Format16bppGrayScale: return "gray16le"; + case PixelFormat.Format16bppRgb555: + return "bgr555le"; case PixelFormat.Format16bppRgb565: return "bgr565le"; case PixelFormat.Format24bppRgb: From 32dfb34724cb881e40f7808eeea1a880bf303393 Mon Sep 17 00:00:00 2001 From: Julien Loir Date: Mon, 12 Apr 2021 13:50:27 +0200 Subject: [PATCH 3/5] Add simple support for PCM audio source wrapping Former-commit-id: d8810682efdfc63aa194d16f39211736cd3a514d --- FFMpegCore/Extend/PcmAudioSampleWrapper.cs | 27 +++++++++++ FFMpegCore/FFMpeg/Pipes/IAudioSample.cs | 16 +++++++ FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs | 45 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 FFMpegCore/Extend/PcmAudioSampleWrapper.cs create mode 100644 FFMpegCore/FFMpeg/Pipes/IAudioSample.cs create mode 100644 FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs diff --git a/FFMpegCore/Extend/PcmAudioSampleWrapper.cs b/FFMpegCore/Extend/PcmAudioSampleWrapper.cs new file mode 100644 index 0000000..d67038b --- /dev/null +++ b/FFMpegCore/Extend/PcmAudioSampleWrapper.cs @@ -0,0 +1,27 @@ +using FFMpegCore.Pipes; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +public class PcmAudioSampleWrapper : IAudioSample +{ + //This could actually be short or int, but copies would be inefficient. + //Handling bytes lets the user decide on the conversion, and abstract the library + //from handling shorts, unsigned shorts, integers, unsigned integers and floats. + private readonly byte[] _sample; + + public PcmAudioSampleWrapper(byte[] sample) + { + _sample = sample; + } + + public void Serialize(Stream stream) + { + stream.Write(_sample, 0, _sample.Length); + } + + public async Task SerializeAsync(Stream stream, CancellationToken token) + { + await stream.WriteAsync(_sample, 0, _sample.Length, token); + } +} diff --git a/FFMpegCore/FFMpeg/Pipes/IAudioSample.cs b/FFMpegCore/FFMpeg/Pipes/IAudioSample.cs new file mode 100644 index 0000000..c7dea65 --- /dev/null +++ b/FFMpegCore/FFMpeg/Pipes/IAudioSample.cs @@ -0,0 +1,16 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace FFMpegCore.Pipes +{ + /// + /// Interface for Audio sample + /// + public interface IAudioSample + { + void Serialize(Stream stream); + + Task SerializeAsync(Stream stream, CancellationToken token); + } +} diff --git a/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs new file mode 100644 index 0000000..40b964c --- /dev/null +++ b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace FFMpegCore.Pipes +{ + /// + /// Implementation of for a raw audio stream that is gathered from + /// + public class RawAudioPipeSource : IPipeSource + { + private readonly IEnumerator _sampleEnumerator; + + public string Format { get; set; } = "s16le"; + public uint SampleRate { get; set; } = 8000; + public uint Channels { get; set; } = 1; + + public RawAudioPipeSource(IEnumerator sampleEnumerator) + { + _sampleEnumerator = sampleEnumerator; + } + + public RawAudioPipeSource(IEnumerable sampleEnumerator) + : this(sampleEnumerator.GetEnumerator()) { } + + public string GetStreamArguments() + { + return $"-f {Format} -ar {SampleRate} -ac {Channels}"; + } + + public async Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) + { + if (_sampleEnumerator.Current != null) + { + await _sampleEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(false); + } + + while (_sampleEnumerator.MoveNext()) + { + await _sampleEnumerator.Current!.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(false); + } + } + } +} From 2ad03814131d3ebabe63434713138dae610b21eb Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 15 May 2021 11:19:22 +0200 Subject: [PATCH 4/5] Reorder using directives Former-commit-id: be32a14619c37cc3fa8b4538544e33ede295204f --- FFMpegCore/Extend/BitmapVideoFrameWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs index bc81643..678bdcb 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -1,11 +1,11 @@ -using FFMpegCore.Pipes; -using System; +using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using FFMpegCore.Pipes; namespace FFMpegCore.Extend { From 7e9b3d20cdc4c77633980363ef6f80bd9986517c Mon Sep 17 00:00:00 2001 From: Julien Loir Date: Tue, 25 May 2021 16:14:40 +0200 Subject: [PATCH 5/5] Add test for the new audio pipe Former-commit-id: acd462a5276eea2cf75cb3e6d7e209bd7157f1a8 --- FFMpegCore.Test/AudioTest.cs | 157 +++++++++++++++++- FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs | 3 +- 2 files changed, 157 insertions(+), 3 deletions(-) diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 2f7e4e9..f1abb72 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -1,10 +1,13 @@ -using System; +using FFMpegCore.Enums; +using FFMpegCore.Exceptions; +using FFMpegCore.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using FFMpegCore.Pipes; namespace FFMpegCore.Test { @@ -69,5 +72,155 @@ public void Image_AddAudio() Assert.IsTrue(analysis.Duration.TotalSeconds > 0); Assert.IsTrue(File.Exists(outputFile)); } + + [TestMethod, Timeout(10000)] + public void Audio_ToAAC_Args_Pipe() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var samples = new List + { + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + }; + + var audioSamplesSource = new RawAudioPipeSource(samples) + { + Channels = 2, + Format = "s8", + SampleRate = 8000, + }; + + var success = FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessSynchronously(); + Assert.IsTrue(success); + } + + [TestMethod, Timeout(10000)] + public void Audio_ToLibVorbis_Args_Pipe() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var samples = new List + { + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + }; + + var audioSamplesSource = new RawAudioPipeSource(samples) + { + Channels = 2, + Format = "s8", + SampleRate = 8000, + }; + + var success = FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.LibVorbis)) + .ProcessSynchronously(); + Assert.IsTrue(success); + } + + [TestMethod, Timeout(10000)] + public async Task Audio_ToAAC_Args_Pipe_Async() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var samples = new List + { + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + }; + + var audioSamplesSource = new RawAudioPipeSource(samples) + { + Channels = 2, + Format = "s8", + SampleRate = 8000, + }; + + var success = await FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessAsynchronously(); + Assert.IsTrue(success); + } + + [TestMethod, Timeout(10000)] + public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var samples = new List + { + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + new PcmAudioSampleWrapper(new byte[] { 0, 0 }), + }; + + var audioSamplesSource = new RawAudioPipeSource(samples); + + var success = FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessSynchronously(); + Assert.IsTrue(success); + } + + [TestMethod, Timeout(10000)] + public void Audio_ToAAC_Args_Pipe_InvalidChannels() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var audioSamplesSource = new RawAudioPipeSource(new List()) + { + Channels = 0, + }; + + var ex = Assert.ThrowsException(() => FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessSynchronously()); + } + + [TestMethod, Timeout(10000)] + public void Audio_ToAAC_Args_Pipe_InvalidFormat() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var audioSamplesSource = new RawAudioPipeSource(new List()) + { + Format = "s8le", + }; + + var ex = Assert.ThrowsException(() => FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessSynchronously()); + } + + [TestMethod, Timeout(10000)] + public void Audio_ToAAC_Args_Pipe_InvalidSampleRate() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var audioSamplesSource = new RawAudioPipeSource(new List()) + { + SampleRate = 0, + }; + + var ex = Assert.ThrowsException(() => FFMpegArguments + .FromPipeInput(audioSamplesSource) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac)) + .ProcessSynchronously()); + } } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs index 40b964c..8797694 100644 --- a/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs +++ b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs @@ -6,7 +6,8 @@ namespace FFMpegCore.Pipes { /// - /// Implementation of for a raw audio stream that is gathered from + /// Implementation of for a raw audio stream that is gathered from . + /// It is the user's responbility to make sure the enumerated samples match the configuration provided to this pipe. /// public class RawAudioPipeSource : IPipeSource {