SHA256
Add Chunked Audio Clip Store
This commit is contained in:
7 files changed
+1412
No files matched your search
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using FFmpeg.AutoGen;
|
||||
|
||||
namespace ADOFAIRenderer.Util.Audio.Clip;
|
||||
|
||||
/// <summary>
|
||||
/// Sample rate converter for interleaved 32-bit float PCM (libswresample).
|
||||
/// Input and output have the same channel count; only the sample rate changes.
|
||||
/// One instance keeps filter state between calls, so consecutive <see cref="Convert"/> calls form one continuous stream.
|
||||
/// Reference: https://ffmpeg.org/doxygen/trunk/group__lswr.html
|
||||
/// </summary>
|
||||
internal sealed unsafe class PCMResampler : IDisposable
|
||||
{
|
||||
private SwrContext* _swr;
|
||||
private bool _flushed;
|
||||
|
||||
public int Channels { get; }
|
||||
public int InputRate { get; }
|
||||
public int OutputRate { get; }
|
||||
|
||||
/// <summary>Value of the SwrContext "filter_size" option (filter length per phase in taps at a 1:1 ratio).</summary>
|
||||
public int FilterSize { get; }
|
||||
|
||||
public PCMResampler(int channels, int inputRate, int outputRate)
|
||||
{
|
||||
if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels));
|
||||
if (inputRate <= 0) throw new ArgumentOutOfRangeException(nameof(inputRate));
|
||||
if (outputRate <= 0) throw new ArgumentOutOfRangeException(nameof(outputRate));
|
||||
|
||||
Channels = channels;
|
||||
InputRate = inputRate;
|
||||
OutputRate = outputRate;
|
||||
|
||||
AVChannelLayout layout;
|
||||
ffmpeg.av_channel_layout_default(&layout, channels);
|
||||
|
||||
SwrContext* swr = null;
|
||||
var ret = ffmpeg.swr_alloc_set_opts2(
|
||||
&swr,
|
||||
&layout, AVSampleFormat.AV_SAMPLE_FMT_FLT, outputRate,
|
||||
&layout, AVSampleFormat.AV_SAMPLE_FMT_FLT, inputRate,
|
||||
0, null);
|
||||
ffmpeg.av_channel_layout_uninit(&layout);
|
||||
if (ret < 0 || swr == null)
|
||||
throw new InvalidOperationException($"swr_alloc_set_opts2 failed ({ret}).");
|
||||
_swr = swr;
|
||||
|
||||
long filterSize;
|
||||
FilterSize = ffmpeg.av_opt_get_int(_swr, "filter_size", 0, &filterSize) >= 0 ? (int)filterSize : 32;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Discards all buffered input and filter history. The next call starts a new stream.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
// swr_init on an initialized context re-initializes it.
|
||||
// Reference: https://ffmpeg.org/doxygen/trunk/group__lswr.html (swr_init, swr_close)
|
||||
var ret = ffmpeg.swr_init(_swr);
|
||||
if (ret < 0)
|
||||
throw new InvalidOperationException($"swr_init failed ({ret}).");
|
||||
_flushed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Feeds <paramref name="inputFrames"/> frames and writes up to <paramref name="outputCapacity"/> frames.
|
||||
/// Input that does not fit in the output is buffered inside the context and returned by later calls.
|
||||
/// <paramref name="inputFrames"/> may be 0 to take only buffered output.
|
||||
/// </summary>
|
||||
/// <returns>Frames written to <paramref name="output"/>.</returns>
|
||||
public int Convert(float* input, int inputFrames, float* output, int outputCapacity)
|
||||
{
|
||||
if (_flushed) throw new InvalidOperationException("Resampler was flushed. Call Reset first.");
|
||||
|
||||
// swr_convert treats a null input array as a flush request, so a non-null pointer is passed even for 0 frames.
|
||||
float dummy = 0;
|
||||
var inPlane = (byte*)(input != null ? input : &dummy);
|
||||
var outPlane = (byte*)output;
|
||||
var ret = ffmpeg.swr_convert(_swr, &outPlane, outputCapacity, &inPlane, inputFrames);
|
||||
if (ret < 0)
|
||||
throw new InvalidOperationException($"swr_convert failed ({ret}).");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the input stream and writes up to <paramref name="outputCapacity"/> remaining frames.
|
||||
/// Call repeatedly until it returns 0. <see cref="Convert"/> is not allowed afterwards until <see cref="Reset"/>.
|
||||
/// </summary>
|
||||
public int Flush(float* output, int outputCapacity)
|
||||
{
|
||||
_flushed = true;
|
||||
var outPlane = (byte*)output;
|
||||
var ret = ffmpeg.swr_convert(_swr, &outPlane, outputCapacity, null, 0);
|
||||
if (ret < 0)
|
||||
throw new InvalidOperationException($"swr_convert (flush) failed ({ret}).");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_swr == null) return;
|
||||
var swr = _swr;
|
||||
ffmpeg.swr_free(&swr);
|
||||
_swr = null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user