C# 환경에서 오디오 입출력을 다뤄야 할 때, 플랫폼별 API를 일일이 대응하는 것은 부담스러운 작업이다. PortAudioSharp2는 PortAudio를 C#에 바인딩한 라이브러리로, Linux·macOS·Windows를 단일 코드베이스로 지원하며 nuget 패키지에 네이티브 바이너리를 내장해 배포와 설정이 간편하다.
핵심 설계와 구조
PortAudioSharp2의 가장 큰 장점은 런타임 자동 로딩이다. 기존 바인딩들은 개발자가 직접 libportaudio를 시스템에 설치하거나 경로를 지정해야 했으나, 이 패키지는 RID(Runtime Identifier) 기반으로 적절한 네이티브 라이브러리를 자동으로 로드한다.
초기화 패턴
라이브러리 사용 전후에는 반드시 초기화와 종료가 필요하다. IDisposable 패턴을 활용하면 안전한 자원 관리가 가능하다.
using PortAudioSharp;
public sealed class AudioContext : IDisposable
{
private bool _disposed;
public AudioContext()
{
var err = PortAudioNative.Pa_Initialize();
if (err != PaError.paNoError)
throw new PortAudioException(err);
}
public void Dispose()
{
if (_disposed) return;
PortAudioNative.Pa_Terminate();
_disposed = true;
}
}
장치 탐색과 스트림 구성
입력·출력 장치의 인덱스를 획득한 뒤, PaStreamParameters로 스트림 속성을 정의한다.
var hostApiCount = PortAudioNative.Pa_GetHostApiCount();
Console.WriteLine($"사용 가능한 Host API 수: {hostApiCount}");
// 기본 출력 장치 확인
var defaultOutput = PortAudioNative.Pa_GetDefaultOutputDevice();
var outParams = new PaStreamParameters
{
device = defaultOutput,
channelCount = 2, // 스테레오
sampleFormat = PaSampleFormat.paFloat32, // 32비트 부동소수점
suggestedLatency = PortAudioNative.Pa_GetDeviceInfo(defaultOutput).defaultLowOutputLatency
};
콜백 기반 오디오 스트림
실시간 오디오 처리의 핵심은 콜백 함수다. PortAudioSharp2는 PaStreamCallback 대리자를 통해 오디오 버퍼 단위의 처리를 수행한다.
unsafe PaStreamCallbackResult OnAudioCallback(
IntPtr inputBuffer,
IntPtr outputBuffer,
uint framesPerBuffer,
ref PaStreamCallbackTimeInfo timeInfo,
PaStreamCallbackFlags statusFlags,
IntPtr userData)
{
var buffer = new Span<float>((void*)outputBuffer, (int)(framesPerBuffer * 2));
for (int i = 0; i < buffer.Length; i += 2)
{
float sample = GenerateSineWave(_phase); // 사용자 정의 파형 생성
buffer[i] = sample; // 좌 채널
buffer[i + 1] = sample; // 우 채널
_phase += 2.0 * Math.PI * 440.0 / 48000.0;
}
return PaStreamCallbackResult.paContinue;
}
스트림 열기와 제어
콜백과 파라미터를 연결하여 스트림을 개방하고, 논블로킹 방식으로 시작·중지한다.
unsafe
{
PaStream* stream;
var callback: PaStreamCallback = OnAudioCallback;
var openErr = PortAudioNative.Pa_OpenStream(
&stream,
null, // 입력 없음 (출력 전용)
&outParams,
48000, // 샘플레이트
256, // 프레임/버퍼
PaStreamFlags.paNoFlag,
callback,
IntPtr.Zero);
if (openErr != PaError.paNoError)
throw new PortAudioException(openErr);
PortAudioNative.Pa_StartStream(stream);
// 재생 중...
Thread.Sleep(5000);
PortAudioNative.Pa_StopStream(stream);
PortAudioNative.Pa_CloseStream(stream);
}
오류 진단
모든 PortAudio 함수는 PaError를 반환한다. Pa_GetErrorText로 상세 메시지를 얻을 수 있다.
static void EnsureSuccess(PaError result)
{
if (result == PaError.paNoError) return;
var msg = Marshal.PtrToStringAnsi(PortAudioNative.Pa_GetErrorText(result));
throw new InvalidOperationException($"PortAudio 오류: {msg}");
}
실전 활용 방향
- 실시간 통신: WebRTC나 커스텀 프로토콜과 결합해 음성 채팅 엔진 구축
- 신호 분석: FFT 라이브러리와 연계해 주파수 스펙트럼 실시간 시각화
- DAW 도구: MIDI 입력과 연동한 가상 악기, 오디오 이펙트 체인 구현
- 테스트 자동화: 하드웨어 없이 루프백 장치로 오디오 파이프라인 검증
PortAudioSharp2는 복잡한 네이티브 의존성을 추상화하면서도 저지연 실시간 처리 성능을 유지하는 균형점을 잘 잡고 있다. .NET生태계에서 오디오 기능을 추가해야 한다면 우선순위로 검토할 만한 선택지다.