PortAudioでのマイク入力をSpeexでエンコード/デコードしてそのまま鳴らす

この例だとNSpeexが負荷の高いノイズフィルタとしか扱えてないのだけど、検証コードとしては十分。

NSpeexエンコーダのポイントとしては、

  • 16bit MONO
  • サンプルレート: NB=8KHz, WD=16KHz, UWD=32KHz
  • フレームサイズ: NB=160, WD=320, UWD=640
  • NSpeex.SpeexEncoder.Encode()の第3引数(inCount)は配列サイズではなくフレームバッファの数を指定する
    • PortAudioのFramesPerBufferを1280、NSpeexEncoderでBandMode.Wide(FrameSize=320)に指定している場合は、inCount=4を指定する
class Program
{
    private const double SampleRate = 16000.0;
    private const int FramesPerBuffer = 320;

    private short[] paBuffer = new short[FramesPerBuffer];
    private byte[] spxBuffer = new byte[FramesPerBuffer];
    private SpeexEncoder encorder = new SpeexEncoder(BandMode.Wide);
    private SpeexDecoder decoder = new SpeexDecoder(BandMode.Wide);
    private PaStreamCallbackDelegate callback;

    public Program()
    {
        callback = new PaStreamCallbackDelegate(HandleCallback);
    }

    public void Run()
    {
        Pa_Initialize();

        var outputDevice = Pa_GetDefaultInputDevice();
        var inputDeviceInfo = (PaDeviceInfo)Marshal.PtrToStructure(
                                Pa_GetDeviceInfo(inputDevice), typeof(PaDeviceInfo));
        var outputDevice = Pa_GetDefaultOutputDevice();
        var outputDeviceInfo = (PaDeviceInfo)Marshal.PtrToStructure(
                                Pa_GetDeviceInfo(outputDevice), typeof(PaDeviceInfo));

        var inParams = new PaStreamParameters()
        {
            channelCount = 1,
            device = inputDevice,
            sampleFormat = PaSampleFormat.Float32,
            suggestedLatency = inputDeviceInfo.defaultLowInputLatency,
        };

        var outParams = new PaStreamParameters()
        {
            channelCount = 1,
            device = outputDevice,
            sampleFormat = PaSampleFormat.Float32,
            suggestedLatency = outputDeviceInfo.defaultLowInputLatency,
        };

        var stream = IntPtr.Zero;
        Pa_OpenStream(
            out stream,
            ref inParams,
            ref outParams,
            SampleRate,
            FramesPerBuffer,
            PaStreamFlags.NoFlag,
            callback,
            IntPtr.Zero);
        Pa_StartStream(stream);
        System.Threading.Thread.Sleep(30000);
        Pa_StopStream(stream);
        
        Pa_Terminate();
    }

    public PaStreamCallbackResult HandleCallback(
            IntPtr input,
            IntPtr output,
            uint frameCount,
            ref PaStreamCallbackTimeInfo timeInfo,
            PaStreamCallbackFlags statusFlags,
            IntPtr userData)
    {
        try
        {
            // encode
            Marshal.Copy(input, paBuffer, 0, FramesPerBuffer);
            var encSize = encorder.Encode(paBuffer, 0, 1, spxBuffer, 0, FramesPerBuffer);

            // decode
            var decSize = decoder.Decode(spxBuffer, 0, encSize, paBuffer, 0, false);
            Marshal.Copy(paBuffer, 0, output, decSize); // decSize = FramesPerBuffer になるはず
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
        return PaStreamCallbackResult.paContinue;
    }

    // P/Invoke関係のコードは省略
}