본문 바로가기
개발언어/NAudio

[NAudio] 2. Audio Playback

by 창용이랑 2021. 7. 8.
728x90

2강은 오디오 재생에 관한 내용이었다.

아주 직관적이고, 가장 근본이 되는 내용이라고 할 수 있겠다.

내가 필요한 것은 물론 각종 편집 기능이지만 일단 재생 기능이 없다면 편집 기능도 무의미하니까.

 

*어지간하면 예제 코드를 첨부하고 싶었는데, 2강의 예제 코드도 없고 직접 짜기에는 내가 지금 너무 피곤하다.

스킵하고 다음 기회가 된다면(아마 안 되겠지만) 하도록 하자.

 

 

약간 깨는 얘기부터 가자면, 일단 오디오 재생 자체는 NAudio를 사용할 필요가 없다.

NAudio에는 'output device' 라고 부르는 게 있는데, 직관적인 네이밍으로 말하자면 소리 출력을 위한 클래스이다. 

play 나 stop 등의 기능을 가지고 있는 클래스들이며 여기서 play 함수를 부르면 어쩌구 저쩌구한 과정을 통해(캡슐화) 스피커에서 소리가 나온다.

 

NAudio는 네 가지의 output device 를 제공하는데, 이들은 자체적으로 제작한 것이 아니라 기존에 존재하는 소리 출력용 클래스들을 래핑한 것이므로 그것들을 사용할 수 있다면 굳이 NAudio를 사용할 필요는 없다.

물론, 나는 그것만 할 게 아니기 때문에 쓸 것이다. 언젠가 다음 기회에는 오픈소스를 배제해야 한다면 그럴지도 모르지.(그러나 그럴 일이 생길까나? 요즘 세상에.)

 

그렇기 때문에, NAudio를 쓰지 않는 소리 재생은 그냥 사진으로 넘어가자.

 

 

 

음, 그렇다.

그러면 본격적인 부분으로 넘어가자.

 

Audio Playback

 

그래서 NAudio에서 어떻게 재생을 하느냐.

이건 일종의 signal chain 이다. 입력부터 출력까지 이어지며, 출력이 사운드카드인 것 뿐이다. (출력은 파일일수도 있다)

가장 짧게 생각하면, mp3Reader 로 파일을 읽어들여 WaveOut 등의 output device 에 전달하면 되는 것이다.

 

이 output device란, 간단하게 말하자면 IWavePlayer 라는 인터페이스를 구현한 것들이다. 그리고 이들은 IWaveProvider를 input으로 받아 클래스를 생성한다. 저 IWaveProvider는 간단하게 이전 signal chain의 output이라고 생각하자.

 

 

이런 인터페이스이다. 직접 이걸 상속하는 클래스를 만들자면 만들 수 있지만, 굳이 그런 짓은 하지 말자. 제작자가 만들어 제공하는 걸 쓰는 쪽이 현명하다.

 

 

물론 말로 하자니 잘 전달이 안되니, 간단하게 코드 그림을 보자.

 

 

뒤로 갈수록 다른 output device를 사용한다던가, 반복 재생이나 위치 이동, 볼륨 조절 등 다양한 기능이 들어가지만 기본적인 코드는 이렇다.

파일로부터 입력을 받고(reader 클래스를 써서) 받은 걸 WaveOut 클래스에 넣어서 재생하는 거다.

이게 가장 기본적인 NAduio에서 오디오 파일을 재생하는 방법이다.

 

 

정지(stop, pause)

 

먼저, pause와 stop의 차이를 알아보자.

둘 다 상당히 기본기인데, pause는 사운드카드가 버퍼를 유지하지만 stop 은 버퍼를 flush해 버려서 다시 재생 시에 좀더 자원을 소모한다고 한다. (시간이나 에너지 등일듯)

그리고 잘 이해가 안 되는 문맥이 있는데, 약간 넘겨짚어 보자면 pause 상태에서 reposition을 할 경우(다른 위치로 이동) stop 후 재생과 같은 효과를 낸다는 것 같다. 확실하지 않다.

 

stop은 몇 가지 경우가 있다. 

먼저 당연히 직접 정지하는 경우가 있고, 자동으로 정지될 수도 있고, 이벤트가 생기면(PlaybackSotpped event) 그때는 직접 정지시켜줘야 한다.

 

이벤트는 아래 두 가지이다.

1. 에러 발생(헤드셋 선이 뽑혔다던가 등)

2. Raised on the GUI thread if possible (SynchronizationContext)

2번은 무슨 소린지 잘 모르겠다...

 

자동 정지는 보통 파일의 끝에 도달하면 하는데, 이 조건은  IWaveProvider.read 가 0을 리턴하는 것이다. 그러나 일부 IWaveProvider.read 는 0을 리턴하지 않으므로 무조건 파일이 끝났다고 해서 재생이 끝나는 건 아니다. 아마 반복재생이 디폴트인 게 있나보다.

 

 

치 지정(Repositioning)

 

예를 들자면 파일을 읽을 때 커서의 위치를(포인터를) 옮김으로서 원하는 내용만 읽을 수 있듯이, 오디오에서도 이게 비슷한 기능을 한다.

실제로 오디오 플레이어 등을 보면 하단 사이드바 등으로 원하는 위치에서 재생하는 그것 말이다. 당장 유튜브에서도 쓰는 그거.

 

이 기능은 output device에서 제공하지 않는다. 걔들은 그냥 파일을 받아서 읽을 뿐이다.

파일을 주는 역할(waveStream)에서 변경해야 한다. 내부 구조는 모르지만 이쪽도 파일을 읽어오는 read pointer를 바꾸는 것 같다. 

 

다만 여기에는 언제나 입출력이 그렇듯이 멀티스레드 상에서 문제가 발생할 수 있는데, 제작자의 말에 따르면 NAudio는 lock등을 사용하여 그 문제를 최선을 다해 해결했으므로 직접 구현하지 않는 이상 큰 문제가 없을 거라고 한다. 믿어보자.

 

하나의 이슈는 밀리초 단위로 위치를 바꿀 수 없다는 점이다. 위치 변경은 반드시 BlockAlign의 배수 단위로만 기능한다. 아니면 망가진다.

 

 

 

위와 같은 식의 직관적인 UI로 나오게 된다. 슬라이드바를 움직이면 거기에 맞춰서 reposition을 하는 거다.

드래그가 멈추는 순간 이벤트가 뜨고, 그 이벤트 안에서 처리해주는 간단한 방식 되겠다.

이런 처리를 위해서 reader 클래스는 함수 안에서 만들지 않고 클래스의 멤버변수로 만들어놔야 한다.

 

 

 

Output Device 선택

이제 어떻게 하는지는 얘기가 나왔으니, 어떤 걸 골라서 하면 될지의 차례이다.

일단 4가지의 output device가 존재한다. (직접 만드는건 다시 말하지만 안할거다) 모두들 IWavePlayer를 구현한 클래스들이다.

 

WaveOut & WaveOutEvent

DirectSoundOut

WasapiOut

AsioOut

 

그럼 하나씩 까보자.

 

WaveOut

 

이 클래스는 가장 '전통적인' 클래스이다. 기본적인 기능은 대체로 보유하고 있으며, 윈도우 xp에서도 지원되는 아주 오래된 api이다.

그래서 넓은 범위를 지원하고자 할 경우에는 이 클래스가 좋다. 단 windows store app은 안된단다.

콜백 모델을 선택할 수 있는데, 에러 나는 함수형을 제외하면 Windows messages 타입과 events 타입이 있다. 이중에서 event 타입을 쓰려하고 할 경우 WaveOut 클래스가 아니라 WaveOutEvent 클래스를 사용해야 한다.

 

이 waveout은 오래된 만큼 매우 안정적이고, 직관적이다. 즉 사용하기 쉽다.

자동으로 resampleing 기능을 포함하고 있어서 다른 sample rate의 소리들을 재생하기 쉽고, 볼륨 조절 기능도 포함되어 있다. 그리고 GUI 스레드에서도 작업할 수 있다(Can work on GUI thread). 음, 마지막 건 뭔지 잘 모르겠다. 메인 스레드를 말하는 건가?

 

다만 단점이 2가지 있다.

하나는 디스코드 등 녹음-재생이 실시간이어야 하는 경우엔 부적합하다. latency가 충분히 low하지 않다고 한다.

그리고 사소한 문제지만, 사운드카드 디바이스 이름이 31자로 제한되어 있다. 만들 때는 그랬었나 보다. 그런데 사운드카드 이름은 수십자도 있다 보니 잘린다고 한다.

 

콜백 모델은 그냥 아래 슬라이드를 참조하자.

 

 

 

위에 사운드카드 얘기를 했듯이, init 전에 몇 가지 설정이 가능하다.

먼저 어떤 사운드카드에서 재생할 건지(보통은 하나지만, usb 헤드셋 등에는 들어있다), 사운드 버퍼는 몇 개를 쓸 건지 등이다.

버퍼는 기본 2개며 늘릴 수 있고, 크기를 줄일 수도 있지만 100ms 미만으로 줄이면 소리가 깨지니까 건드리지 말자.

볼륨도 설정 가능하지만 증폭(1.0f 이상)은 안된다.

 

 

DirectSoundOut

 

waveout 에 비해 별 장점이 없다. 같은 os이고. 장점이라면 디바이스 이름은 잘리지 않는다고 한다.

볼륨 기능이 지원되지 않는다. 왜 있는 걸까 이거.

 

 

WasapiOut

 

vista 부터 지원되는 api로, waveout의 차세대 버전이라고 할 수 있다. 이걸 쓰는게 일반적으로 권장된다고 한다.

windows store app 에서 쓰기 위해서는 WasapiOutRT 라는 클래스를 쓰면 된다고 한다. 뭔가 내부적으로 다른 모양이다.

 

단점으로 자동 리샘플링이 되지 않아서 DMO Reampler를 써야 한다는 단점이 있는데, NAudio에서는 래핑 과정에서 이걸 추가해놓아 프로그래머가 직접 할 필요는 없다고 한다. 잘됐네.

 

옵션으로 사운드카드 독점 여부(다른 애플리케이션이 쓰지 못하게) 할 수 있는데, 일부 녹음의 경우에는 사용하는 경우가 있다고 한다. 물론 대부분의 경우에는 쓰지 않는다.

wasapi는 백그라운드 스레드를 사용하여 오디오 버퍼를 채우는 모양인데, 그 이벤트를 sync로 할지 sleep으로 할지 선택하는 옵션도 있다. 그런데 이건 어떤 차이가 있는지 말로는 감이 안와서 써봐야 알 것 같다.

 

볼륨 조절 기능은 없긴 한데, 이것도 NAudio가 지원한다고 한다.

 

 

AsioOut

 

이건 앞에서도 설명했다시피 전용 드라이버와 고오급 사운드카드가 필요하다. 신경쓰지 말자.

 

 

 

Volume

 

볼륨 조절 기능은 위에서도 조금씩 나왔지만, 출력 볼륨을 줄이는 방향 말고 실제 샘플 레벨에서 볼륨을 변경하는 걸 생각해 보자.

이 경우의 장점은 역시 100% 이상으로 소리를 키울 수 있다는 점이다. 물론, clipping 이슈는 고려해야 할 것이다.

 

이 경우에도 방법은 간단한데, signal chain에 볼륨 조절을 담당하는 애를 넣으면 된다. UI에 세로 드래그바 등을 넣고.

그러면 입력->출력 에서 입력->소리조절->출력 으로 바뀐다.

 

 

 

위와 같이 추가적인 애를 만드는 것이다.

다만 뭐, 복잡하기 때문에 reader와 볼륨 조절 기능을 합친 클래스인 AudioFileReader 를 쓰면 된다고 한다.

 

 

 

 

참 단순한 내용임에도 이해하고 블로그 적는데까지 3시간 정도는 걸렸다.

강의를 다 봐야 일을 시작이라도 할텐데 조금 막막하네...

 

한 가지 의문이, 디바이스 선택 옵션을 '디폴트' 옵션을 찾아 넣는 부분이 있었는데 그러면 그걸 안하면 디폴트에서 실행되지 않는다는 뜻인가? 여러 사운드카드가 있을 경우, 혹은 여러 종류의 출력장치를 쓸 경우에 대한 처리를 따로 해 줘야 하나?

내가 사운드카드가 여러개가 아니라서 실험은 못 해보겠는데...흐음...

 

출처 : https://m.blog.naver.com/luku756/221892825967

'개발언어 > NAudio' 카테고리의 다른 글

[NAudio] 6. Recording Audio  (0) 2021.07.08
[NAudio] 5. Working With Codecs  (0) 2021.07.08
[NAuidio] 4. Changing Wave Formats  (0) 2021.07.08
[NAuido] 3. Working With Files  (0) 2021.07.08
[NAudio] 1. Introducing NAudio  (0) 2021.07.08