mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge pull request #204 from Namaneo/add-pcm-wrapper
Add PCM audio sample wrapper
Former-commit-id: a1dbc3bb47
This commit is contained in:
commit
6ab2bc0830
5 changed files with 246 additions and 2 deletions
|
@ -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<IAudioSample>
|
||||
{
|
||||
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<IAudioSample>
|
||||
{
|
||||
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<IAudioSample>
|
||||
{
|
||||
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<IAudioSample>
|
||||
{
|
||||
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<IAudioSample>())
|
||||
{
|
||||
Channels = 0,
|
||||
};
|
||||
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => 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<IAudioSample>())
|
||||
{
|
||||
Format = "s8le",
|
||||
};
|
||||
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => 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<IAudioSample>())
|
||||
{
|
||||
SampleRate = 0,
|
||||
};
|
||||
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
27
FFMpegCore/Extend/PcmAudioSampleWrapper.cs
Normal file
27
FFMpegCore/Extend/PcmAudioSampleWrapper.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
16
FFMpegCore/FFMpeg/Pipes/IAudioSample.cs
Normal file
16
FFMpegCore/FFMpeg/Pipes/IAudioSample.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Pipes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for Audio sample
|
||||
/// </summary>
|
||||
public interface IAudioSample
|
||||
{
|
||||
void Serialize(Stream stream);
|
||||
|
||||
Task SerializeAsync(Stream stream, CancellationToken token);
|
||||
}
|
||||
}
|
46
FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs
Normal file
46
FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Pipes
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IPipeSource"/> for a raw audio stream that is gathered from <see cref="IEnumerator{IAudioFrame}"/>.
|
||||
/// It is the user's responbility to make sure the enumerated samples match the configuration provided to this pipe.
|
||||
/// </summary>
|
||||
public class RawAudioPipeSource : IPipeSource
|
||||
{
|
||||
private readonly IEnumerator<IAudioSample> _sampleEnumerator;
|
||||
|
||||
public string Format { get; set; } = "s16le";
|
||||
public uint SampleRate { get; set; } = 8000;
|
||||
public uint Channels { get; set; } = 1;
|
||||
|
||||
public RawAudioPipeSource(IEnumerator<IAudioSample> sampleEnumerator)
|
||||
{
|
||||
_sampleEnumerator = sampleEnumerator;
|
||||
}
|
||||
|
||||
public RawAudioPipeSource(IEnumerable<IAudioSample> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue