C# 音频倍速播放

操作环境:

win11

.net framework 4.5

使用到NAudio、SoundTouch

步骤如下:

1 nuget安装NAudio,版本选择1.9.0

2 下载SoundTouch,找到SoundTouch.dll和SoundTouch_x64.dll,放到运行目录

https://codeload.github.com/naudio/varispeed-sample/zip/refs/heads/master

3 新建类SoundTouchInterop32、SoundTouchInterop64、SoundTouchProfile、SoundTouchSettings、SoundTouch、VarispeedSampleProvider如下:

cs 复制代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Swisslog.CSSD.UI.SoundTouch
{
    class SoundTouchInterop32
    {
        private const string SoundTouchDllName = "SoundTouch.dll";

        /// <summary>
        /// Create a new instance of SoundTouch processor.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr soundtouch_createInstance();

        /// <summary>
        /// Destroys a SoundTouch processor instance.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_destroyInstance(IntPtr h);

        /// <summary>
        /// Get SoundTouch library version string - alternative function for 
        /// environments that can't properly handle character string as return value
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_getVersionString2(StringBuilder versionString, int bufferSize);

        /// <summary>
        /// Get SoundTouch library version Id
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern uint soundtouch_getVersionId();

        /// <summary>
        /// Sets new rate control value. Normal rate = 1.0, smaller values
        /// represent slower rate, larger faster rates.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setRate(IntPtr h, float newRate);

        /// <summary>
        /// Sets new tempo control value. Normal tempo = 1.0, smaller values
        /// represent slower tempo, larger faster tempo.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setTempo(IntPtr h, float newTempo);

        /// <summary>
        /// Sets new rate control value as a difference in percents compared
        /// to the original rate (-50 .. +100 %);
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setRateChange(IntPtr h, float newRate);

        /// <summary>
        /// Sets new tempo control value as a difference in percents compared
        /// to the original tempo (-50 .. +100 %);
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setTempoChange(IntPtr h, float newTempo);

        /// <summary>
        /// Sets new pitch control value. Original pitch = 1.0, smaller values
        /// represent lower pitches, larger values higher pitch.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setPitch(IntPtr h, float newPitch);

        /// <summary>
        /// Sets pitch change in octaves compared to the original pitch  
        /// (-1.00 .. +1.00);
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setPitchOctaves(IntPtr h, float newPitch);

        /// <summary>
        /// Sets pitch change in semi-tones compared to the original pitch
        /// (-12 .. +12);
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setPitchSemiTones(IntPtr h, float newPitch);

        /// <summary>
        /// Sets the number of channels, 1 = mono, 2 = stereo
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setChannels(IntPtr h, uint numChannels);

        /// <summary>
        /// Sets sample rate.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_setSampleRate(IntPtr h, uint srate);

        /// <summary>
        /// Flushes the last samples from the processing pipeline to the output.
        /// Clears also the internal processing buffers.
        ///
        /// Note: This function is meant for extracting the last samples of a sound
        /// stream. This function may introduce additional blank samples in the end
        /// of the sound stream, and thus it's not recommended to call this function
        /// in the middle of a sound stream.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_flush(IntPtr h);

        /// <summary>
        /// Adds 'numSamples' pcs of samples from the 'samples' memory position into
        /// the input of the object. Notice that sample rate _has_to_ be set before
        /// calling this function, otherwise throws a runtime_error exception.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="samples">Pointer to sample buffer.</param>
        /// <param name="numSamples">Number of samples in buffer. Notice that in case of stereo-sound a single sample contains data for both channels.</param>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_putSamples(IntPtr h, [MarshalAs(UnmanagedType.LPArray)] float[] samples, int numSamples);

        /// <summary>
        /// Clears all the samples in the object's output and internal processing
        /// buffers.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void soundtouch_clear(IntPtr h);

        /// <summary>
        /// Changes a setting controlling the processing system behaviour. See the
        /// 'SETTING_...' defines for available setting ID's.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="settingId">Setting ID number, see SETTING_... defines.</param>
        /// <param name="value">New setting value.</param>
        /// <returns>'TRUE' if the setting was succesfully changed</returns>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern bool soundtouch_setSetting(IntPtr h, SoundTouchSettings settingId, int value);

        /// <summary>
        /// Reads a setting controlling the processing system behaviour. See the
        /// 'SETTING_...' defines for available setting ID's.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="settingId">Setting ID number, see SETTING_... defines.</param>
        /// <returns>The setting value</returns>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern int soundtouch_getSetting(IntPtr h, SoundTouchSettings settingId);

        /// <summary>
        /// Returns number of samples currently unprocessed.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern int soundtouch_numUnprocessedSamples(IntPtr h);

        /// <summary>
        ///  Adjusts book-keeping so that given number of samples are removed from beginning of the 
        ///  sample buffer without copying them anywhere. 
        /// 
        ///  Used to reduce the number of samples in the buffer when accessing the sample buffer directly
        ///  with 'ptrBegin' function.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="outBuffer">Buffer where to copy output samples.</param>
        /// <param name="maxSamples">How many samples to receive at max.</param>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern uint soundtouch_receiveSamples(IntPtr h, [MarshalAs(UnmanagedType.LPArray)] float[] outBuffer, uint maxSamples);

        /// <summary>
        /// Returns number of samples currently available.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern uint soundtouch_numSamples(IntPtr h);

        /// <summary>
        /// Returns nonzero if there aren't any samples available for outputting.
        /// </summary>
        [DllImport(SoundTouchDllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern int soundtouch_isEmpty(IntPtr h);

    }
}
cs 复制代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Swisslog.CSSD.UI.SoundTouch
{
    class SoundTouchInterop64
    {
        private const string SoundTouchDllName = "SoundTouch_x64.dll";

        /// <summary>
        /// Create a new instance of SoundTouch processor.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern IntPtr soundtouch_createInstance();

        /// <summary>
        /// Destroys a SoundTouch processor instance.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_destroyInstance(IntPtr h);

        /// <summary>
        /// Get SoundTouch library version string - alternative function for 
        /// environments that can't properly handle character string as return value
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_getVersionString2(StringBuilder versionString, int bufferSize);

        /// <summary>
        /// Get SoundTouch library version Id
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern uint soundtouch_getVersionId();

        /// <summary>
        /// Sets new rate control value. Normal rate = 1.0, smaller values
        /// represent slower rate, larger faster rates.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setRate(IntPtr h, float newRate);

        /// <summary>
        /// Sets new tempo control value. Normal tempo = 1.0, smaller values
        /// represent slower tempo, larger faster tempo.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setTempo(IntPtr h, float newTempo);

        /// <summary>
        /// Sets new rate control value as a difference in percents compared
        /// to the original rate (-50 .. +100 %);
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setRateChange(IntPtr h, float newRate);

        /// <summary>
        /// Sets new tempo control value as a difference in percents compared
        /// to the original tempo (-50 .. +100 %);
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setTempoChange(IntPtr h, float newTempo);

        /// <summary>
        /// Sets new pitch control value. Original pitch = 1.0, smaller values
        /// represent lower pitches, larger values higher pitch.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setPitch(IntPtr h, float newPitch);

        /// <summary>
        /// Sets pitch change in octaves compared to the original pitch  
        /// (-1.00 .. +1.00);
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setPitchOctaves(IntPtr h, float newPitch);

        /// <summary>
        /// Sets pitch change in semi-tones compared to the original pitch
        /// (-12 .. +12);
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setPitchSemiTones(IntPtr h, float newPitch);

        /// <summary>
        /// Sets the number of channels, 1 = mono, 2 = stereo
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setChannels(IntPtr h, uint numChannels);

        /// <summary>
        /// Sets sample rate.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_setSampleRate(IntPtr h, uint srate);

        /// <summary>
        /// Flushes the last samples from the processing pipeline to the output.
        /// Clears also the internal processing buffers.
        ///
        /// Note: This function is meant for extracting the last samples of a sound
        /// stream. This function may introduce additional blank samples in the end
        /// of the sound stream, and thus it's not recommended to call this function
        /// in the middle of a sound stream.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_flush(IntPtr h);

        /// <summary>
        /// Adds 'numSamples' pcs of samples from the 'samples' memory position into
        /// the input of the object. Notice that sample rate _has_to_ be set before
        /// calling this function, otherwise throws a runtime_error exception.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="samples">Pointer to sample buffer.</param>
        /// <param name="numSamples">Number of samples in buffer. Notice that in case of stereo-sound a single sample contains data for both channels.</param>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_putSamples(IntPtr h, [MarshalAs(UnmanagedType.LPArray)] float[] samples, int numSamples);

        /// <summary>
        /// Clears all the samples in the object's output and internal processing
        /// buffers.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern void soundtouch_clear(IntPtr h);

        /// <summary>
        /// Changes a setting controlling the processing system behaviour. See the
        /// 'SETTING_...' defines for available setting ID's.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="settingId">Setting ID number, see SETTING_... defines.</param>
        /// <param name="value">New setting value.</param>
        /// <returns>'TRUE' if the setting was succesfully changed</returns>
        [DllImport(SoundTouchDllName)]
        public static extern bool soundtouch_setSetting(IntPtr h, SoundTouchSettings settingId, int value);

        /// <summary>
        /// Reads a setting controlling the processing system behaviour. See the
        /// 'SETTING_...' defines for available setting ID's.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="settingId">Setting ID number, see SETTING_... defines.</param>
        /// <returns>The setting value</returns>
        [DllImport(SoundTouchDllName)]
        public static extern int soundtouch_getSetting(IntPtr h, SoundTouchSettings settingId);

        /// <summary>
        /// Returns number of samples currently unprocessed.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern int soundtouch_numUnprocessedSamples(IntPtr h);

        /// <summary>
        ///  Adjusts book-keeping so that given number of samples are removed from beginning of the 
        ///  sample buffer without copying them anywhere. 
        /// 
        ///  Used to reduce the number of samples in the buffer when accessing the sample buffer directly
        ///  with 'ptrBegin' function.
        /// </summary>
        /// <param name="h">Handle</param>
        /// <param name="outBuffer">Buffer where to copy output samples.</param>
        /// <param name="maxSamples">How many samples to receive at max.</param>
        [DllImport(SoundTouchDllName)]
        public static extern uint soundtouch_receiveSamples(IntPtr h, [MarshalAs(UnmanagedType.LPArray)] float[] outBuffer, uint maxSamples);

        /// <summary>
        /// Returns number of samples currently available.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern uint soundtouch_numSamples(IntPtr h);

        /// <summary>
        /// Returns nonzero if there aren't any samples available for outputting.
        /// </summary>
        [DllImport(SoundTouchDllName)]
        public static extern int soundtouch_isEmpty(IntPtr h);

    }
}
cs 复制代码
namespace Swisslog.CSSD.UI.SoundTouch
{
    internal class SoundTouchProfile
    {
        public bool UseTempo { get; set; }
        public bool UseAntiAliasing { get; set; }
        public bool UseQuickSeek { get; set; }

        public SoundTouchProfile(bool useTempo, bool useAntiAliasing)
        {
            UseTempo = useTempo;
            UseAntiAliasing = useAntiAliasing;
        }
    }
}
cs 复制代码
namespace Swisslog.CSSD.UI.SoundTouch
{
    enum SoundTouchSettings
    {
        /// <summary>
        /// Available setting IDs for the 'setSetting' and 'get_setting' functions.
        /// Enable/disable anti-alias filter in pitch transposer (0 = disable)
        /// </summary>
        UseAaFilter = 0,

        /// <summary>
        /// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32)
        /// </summary>
        AaFilterLength = 1,

        /// <summary>
        /// Enable/disable quick seeking algorithm in tempo changer routine
        /// (enabling quick seeking lowers CPU utilization but causes a minor sound
        ///  quality compromising)
        /// </summary>
        UseQuickSeek = 2,

        /// <summary>
        /// Time-stretch algorithm single processing sequence length in milliseconds. This determines 
        /// to how long sequences the original sound is chopped in the time-stretch algorithm. 
        /// See "STTypes.h" or README for more information.
        /// </summary>
        SequenceMs = 3,

        /// <summary>
        /// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the 
        /// best possible overlapping location. This determines from how wide window the algorithm 
        /// may look for an optimal joining location when mixing the sound sequences back together. 
        /// See "STTypes.h" or README for more information.
        /// </summary>
        SeekWindowMs = 4,

        /// <summary>
        /// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences 
        /// are mixed back together, to form a continuous sound stream, this parameter defines over 
        /// how long period the two consecutive sequences are let to overlap each other. 
        /// See "STTypes.h" or README for more information.
        /// </summary>
        OverlapMs = 5
    };
}
cs 复制代码
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Swisslog.CSSD.UI.SoundTouch
{
    class SoundTouch : IDisposable
    {
        private IntPtr handle;
        private string versionString;
        private readonly bool is64Bit;
        public SoundTouch()
        {
            is64Bit = false;

            handle = is64Bit ? SoundTouchInterop64.soundtouch_createInstance() :
                SoundTouchInterop32.soundtouch_createInstance();
        }

        public string VersionString
        {
            get
            {
                if (versionString == null)
                {
                    var s = new StringBuilder(100);
                    if (is64Bit)
                        SoundTouchInterop64.soundtouch_getVersionString2(s, s.Capacity);
                    else
                        SoundTouchInterop32.soundtouch_getVersionString2(s, s.Capacity);
                    versionString = s.ToString();
                }
                return versionString;
            }
        }

        public void SetPitchOctaves(float pitchOctaves)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setPitchOctaves(handle, pitchOctaves);
            else
                SoundTouchInterop32.soundtouch_setPitchOctaves(handle, pitchOctaves);
        }

        public void SetSampleRate(int sampleRate)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setSampleRate(handle, (uint) sampleRate);
            else 
                SoundTouchInterop32.soundtouch_setSampleRate(handle, (uint)sampleRate);
        }

        public void SetChannels(int channels)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setChannels(handle, (uint) channels);
            else
                SoundTouchInterop32.soundtouch_setChannels(handle, (uint)channels);
        }

        private void DestroyInstance()
        {
            if (handle != IntPtr.Zero)
            {
                if (is64Bit)
                    SoundTouchInterop64.soundtouch_destroyInstance(handle);
                else
                    SoundTouchInterop32.soundtouch_destroyInstance(handle);
                handle = IntPtr.Zero;
            }
        }

        public void Dispose()
        {
            DestroyInstance();
            GC.SuppressFinalize(this);
        }

        ~SoundTouch()
        {
            DestroyInstance();
        }

        public void PutSamples(float[] samples, int numSamples)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_putSamples(handle, samples, numSamples);
            else
                SoundTouchInterop32.soundtouch_putSamples(handle, samples, numSamples);
        }

        public int ReceiveSamples(float[] outBuffer, int maxSamples)
        {
            if (is64Bit)
                return (int)SoundTouchInterop64.soundtouch_receiveSamples(handle, outBuffer, (uint)maxSamples);
            return (int)SoundTouchInterop32.soundtouch_receiveSamples(handle, outBuffer, (uint)maxSamples);
        }

        public bool IsEmpty
        {
            get
            {
                if (is64Bit)
                    return SoundTouchInterop64.soundtouch_isEmpty(handle) != 0;
                return SoundTouchInterop32.soundtouch_isEmpty(handle) != 0;
            }
        }

        public int NumberOfSamplesAvailable
        {
            get
            {
                if (is64Bit)
                   return (int)SoundTouchInterop64.soundtouch_numSamples(handle);
                return (int)SoundTouchInterop32.soundtouch_numSamples(handle);
            }
        }

        public int NumberOfUnprocessedSamples
        {
            get
            {
                if (is64Bit)
                    return SoundTouchInterop64.soundtouch_numUnprocessedSamples(handle);
                return SoundTouchInterop32.soundtouch_numUnprocessedSamples(handle);
            }
        }

        public void Flush()
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_flush(handle);
            else
                SoundTouchInterop32.soundtouch_flush(handle);
        }

        public void Clear()
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_clear(handle);
            else
                SoundTouchInterop32.soundtouch_clear(handle);
        }

        public void SetRate(float newRate)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setRate(handle, newRate);
            else
                SoundTouchInterop32.soundtouch_setRate(handle, newRate);
        }

        public void SetTempo(float newTempo)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setTempo(handle, newTempo);
            else
                SoundTouchInterop32.soundtouch_setTempo(handle, newTempo);
        }

        public int GetUseAntiAliasing()
        {
            if (is64Bit)
                return SoundTouchInterop64.soundtouch_getSetting(handle, SoundTouchSettings.UseAaFilter);
            return SoundTouchInterop32.soundtouch_getSetting(handle, SoundTouchSettings.UseAaFilter);
        }

        public void SetUseAntiAliasing(bool useAntiAliasing)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setSetting(handle, SoundTouchSettings.UseAaFilter, useAntiAliasing ? 1 : 0);
            else
                SoundTouchInterop32.soundtouch_setSetting(handle, SoundTouchSettings.UseAaFilter, useAntiAliasing ? 1 : 0);
        }

        public void SetUseQuickSeek(bool useQuickSeek)
        {
            if (is64Bit)
                SoundTouchInterop64.soundtouch_setSetting(handle, SoundTouchSettings.UseQuickSeek, useQuickSeek ? 1 : 0);
            else
                SoundTouchInterop32.soundtouch_setSetting(handle, SoundTouchSettings.UseQuickSeek, useQuickSeek ? 1 : 0);
        }

        public int GetUseQuickSeek()
        {
            if (is64Bit)
                return SoundTouchInterop64.soundtouch_getSetting(handle, SoundTouchSettings.UseQuickSeek);
            return SoundTouchInterop32.soundtouch_getSetting(handle, SoundTouchSettings.UseQuickSeek);
        }
    }
}
cs 复制代码
using System;
using NAudio.Wave;

namespace Swisslog.CSSD.UI.SoundTouch
{
    class VarispeedSampleProvider : ISampleProvider, IDisposable
    {
        private readonly ISampleProvider sourceProvider;
        private readonly SoundTouch soundTouch;
        private readonly float[] sourceReadBuffer;
        private readonly float[] soundTouchReadBuffer;
        private readonly int channelCount;
        private float playbackRate = 1.0f;
        private SoundTouchProfile currentSoundTouchProfile;
        private bool repositionRequested;
        private float PlaySpeed=1.0f;
        // =========新增字段:标记源是否已经读到末尾(提升为类成员)=========
        private bool _sourceEof;

        public VarispeedSampleProvider(ISampleProvider sourceProvider, int readDurationMilliseconds, SoundTouchProfile soundTouchProfile)
        {
            soundTouch = new SoundTouch();
            // explore what the default values are before we change them:
            //Debug.WriteLine(String.Format("SoundTouch Version {0}", soundTouch.VersionString));
            //Debug.WriteLine("Use QuickSeek: {0}", soundTouch.GetUseQuickSeek());
            //Debug.WriteLine("Use AntiAliasing: {0}", soundTouch.GetUseAntiAliasing());

            SetSoundTouchProfile(soundTouchProfile);
            this.sourceProvider = sourceProvider;
            soundTouch.SetSampleRate(WaveFormat.SampleRate);
            channelCount = WaveFormat.Channels;
            soundTouch.SetChannels(channelCount);
            sourceReadBuffer = new float[(WaveFormat.SampleRate * channelCount * (long)readDurationMilliseconds) / 1000];
            soundTouchReadBuffer = new float[sourceReadBuffer.Length * 10]; // support down to 0.1 speed

            _sourceEof = false; //初始化

        }

       
        public int Read(float[] buffer, int offset, int count)
        {
            if (repositionRequested)
            {
                soundTouch.Clear();
                _sourceEof = false; // Seek跳转后重置eof标记!重要
                repositionRequested = false;
            }

            int pos = 0;
            while (pos < count)
            {
                // 只有源还没有eof,才继续去读源数据
                if (!_sourceEof && soundTouch.NumberOfSamplesAvailable == 0)
                {
                    var readFromSource = sourceProvider.Read(sourceReadBuffer, 0, sourceReadBuffer.Length);
                    if (readFromSource > 0)
                    {
                        soundTouch.PutSamples(sourceReadBuffer, readFromSource / channelCount);
                    }
                    else
                    {
                        // 源读到末尾,设置全局eof标记,执行Flush
                        _sourceEof = true;
                        soundTouch.Flush();
                    }
                }

                var desiredSampleFrames = (count - pos) / channelCount;
                var received = soundTouch.ReceiveSamples(soundTouchReadBuffer, desiredSampleFrames) * channelCount;

                for (int n = 0; n < received; n++)
                {
                    buffer[offset + pos++] = soundTouchReadBuffer[n];
                }

                // 源已经eof,并且soundTouch也拿不出样本了 → 彻底结束,跳出循环
                if (_sourceEof && received <= 0)
                {
                    break;
                }
            }
            return pos;
        }


        public WaveFormat WaveFormat => sourceProvider.WaveFormat;

        public float PlaybackRate
        {
            get
            {
                return playbackRate;
            }
            set
            {
                if (playbackRate != value)
                {
                    UpdatePlaybackRate(value);
                    playbackRate = value;
                }
            }
        }

        public void SetPlaySpeed(float playSpeed)
        {
            soundTouch.SetRate(playSpeed);
        }

        private void UpdatePlaybackRate(float value)
        {
            if (value != 0)
            {
                if (currentSoundTouchProfile.UseTempo)
                {
                    soundTouch.SetTempo(value);
                }
                else
                {
                    soundTouch.SetRate(value);
                }
            }
        }

        public void Dispose()
        {
            soundTouch.Dispose();
        }

        public void SetSoundTouchProfile(SoundTouchProfile soundTouchProfile)
        {
            if (currentSoundTouchProfile != null && 
                playbackRate != 1.0f && 
                soundTouchProfile.UseTempo != currentSoundTouchProfile.UseTempo)
            {
                if (soundTouchProfile.UseTempo)
                {
                    soundTouch.SetRate(PlaySpeed);
                    soundTouch.SetPitchOctaves(0f);
                    soundTouch.SetTempo(1.0f);
                }
                else
                {
                    soundTouch.SetTempo(1.0f);
                    soundTouch.SetRate(playbackRate);
                }
            }
            this.currentSoundTouchProfile = soundTouchProfile;
            soundTouch.SetUseAntiAliasing(soundTouchProfile.UseAntiAliasing);
            soundTouch.SetUseQuickSeek(soundTouchProfile.UseQuickSeek);
        }
        public void Reposition()
        {
            repositionRequested = true;            
        }
    }
}

4 调用如下:

cs 复制代码
using (var fileReader = new AudioFileReader(audioName))
using (var stretch = new VarispeedSampleProvider(fileReader, 50, new SoundTouchProfile(false, false)))
{
    stretch.SetPlaySpeed(1.8f);  //1.8倍速播放
    using (waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
    {
        waveOut.Init(stretch);
        waveOut.Play();
        while (waveOut.PlaybackState == PlaybackState.Playing)
        {
            Thread.Sleep(10);
        }
    }
}
相关推荐
Water_Sunzhipeng5 小时前
HandyControl Demo 设置特定框架记录
c#
czhc11400756635 小时前
插补、另存为、标记过滤、采集超时——今天聊的四件事
c#
小羊没烦恼!6 小时前
Memory 记忆设计讨论:Agent Memory 的数据模型可以怎么设计
java·开发语言·前端·c++·c#
小羊没烦恼!10 小时前
Memory 记忆设计讨论:为什么 Agent Memory 不能只靠向量数据库?
java·开发语言·windows·算法·c#
...62211 小时前
C# 中 Lambda 表达式的全面讲解
c#
林澈在路上11 小时前
能做DJ的AI写歌软件推荐:MELO音乐对比Suno v6
大数据·人工智能·github·音视频·音频
...62212 小时前
WinForm中Socket通信服务端客户端代码实现讲解
tcp/ip·c#·socket
唐青枫1 天前
别只会看日志:C#.NET SOS 调试扩展从 Dump 到内存泄漏排查
c#·.net
yswenli1 天前
基于C#开发网站视频嗅探下载工具——BrowserVideoGrabber
c#·videodownload·webvideodownload·videograbber