Embedded : : Linux/: : ALSA

[ALSA] 13. USB Audio

Jay.P Morgan 2026. 3. 11. 15:26

 

 

  13.  USB Audio 심화

 

  13.1  USB Audio

 

USB Audio는 PC, 스마트폰, 또는 i.MX8M Plus와 같은 임베디드 기기에 USB 포트를 통해 오디오 장치를 연결하는 표준 방식입니다. 가장 큰 특징은 하드웨어 제조사가 달라도 전 세계적으로 통용되는 USB Audio Class (UAC) 규격을 따른다는 점입니다.

 

 

1. USB Audio Class (UAC)의 세대별 차이

USB 오디오 장치는 별도의 드라이버 설치 없이도 작동하는 'Class Compliant' 방식을 지향합니다.

기  능 UAC 1.0 UAC 2.0 UAC 3.0
주요 특징 USB 1.1 기반,
최대 24-bit/96kHz 지원
USB 2.0 기반,
고해상도(32-bit/384kHz+),
다채널 지원
전력 효율 개선,
USB-C 타입 최적화
최대 샘플레이트 96 kHz 384 kHz 768 kHz
최대 비트 깊이 24-bit 32-bit 32-bit
최대 채널 8 32 32
클럭 소스 고정 다중 클럭 소스 다중 + 동기화
전원 관리 기본 고급 (Suspend/Resume) 매우 고급 (Low Power)
효과/처리 제한적 Effect Units Enhanced Processing
OS 지원 모든 OS Windows 10+, Linux 2.6.35+ Windows 10 RS5+, Linux 4.20+
사용처 구형 기기, 간단한 헤드셋 최신 DAC, 오디오 인터페이스 최신 스마트폰 및 모바일 기기
 

2. 데이터 전송 방식: 등시성 전송 (Isochronous Transfer)

USB 오디오는 파일 복사와 달리 시간적 정확성이 중요합니다. 이를 위해 Isochronous 전송 방식을 사용합니다.

       대역폭 보장: 다른 USB 장치(마우스, 키보드)가 데이터를 써도 오디오를 위한 길을 미리 비워둡니다.

       에러 무시: 데이터가 약간 깨지더라도 멈추지 않고 계속 재생합니다 (재전송 안 함).

 

3. 클럭 동기화: 지터(Jitter)와의 싸움

오디오 데이터가 흐를 때 "박자(Clock)"를 누가 맞추느냐에 따라 품질이 달라집니다.

  1. Synchronous: 호스트(PC)가 주는 박자에 장치가 무조건 맞춥니다. (음질 저하 가능성)
  2. Asynchronous (비동기): 장치(DAC) 내부에 정밀한 클럭을 두고, 호스트에게 "데이터를 조금 더 빨리/천천히 보내줘"라고 피드백을 보냅니다. 고음질 하이파이 장비의 필수 조건입니다.

 

4. 리눅스 커널에서의 USB Audio 흐름

우리가 지금까지 배운 구조와 연결해 보면 다음과 같습니다.

       드라이버: 커널의 sound/usb/card.c에 위치한 snd-usb-audio 드라이버가 담당합니다.

       인식 과정: 1. USB를 꽂으면 장치가 "나는 오디오 클래스 2.0 장치다"라고 자기소개(Descriptor)를 보냅니다. 2. 커널 드라이버가 이를 읽고 자동으로 ALSA PCM 장치를 생성합니다.

       토폴로지와의 차이: USB 오디오는 내부 경로가 이미 칩 안에 굳어져(Fixed) 오는 경우가 많아, SOF처럼 복잡한 토폴로지 파일을 외부에서 주입하기보다 장치가 보내주는 정보를 그대로 신뢰합니다.

 

 

5. i.MX8M Plus에서의 USB Audio

i.MX8M Plus는 USB 컨트롤러를 내장하고 있어 두 가지 모드로 동작할 수 있습니다.

       Host 모드: i.MX8M Plus에 USB 헤드셋이나 DAC를 연결하여 소리를 출력합니다.

       Gadget 모드: i.MX8M Plus 자체가 PC에 연결되는 'USB 스피커'처럼 동작하게 만들 수도 있습니다. (이때는 f_uac2 커널 모듈을 사용합니다.)

 

요약

USB Audio는 표준 규격(UAC) 덕분에 별도 설정 없이 바로 쓸 수 있는 편리한 기술입니다. 내부적으로는 비동기 전송 등시성 채널을 통해 고음질을 유지하며, 리눅스에서는 snd-usb-audio 드라이버가 이 모든 복잡한 과정을 대행해 줍니다.

 

 

  13.2  Isochronous 전송 모드

 

USB Audio는 Isochronous Transfer로 오디오 스트림을 전송합니다. 지연시간 보장을 위해 에러 재전송 없이 고정 대역폭을 할당합니다.

USB Audio에서 등시성(Isochronous) 전송은 데이터의 정확성보다 전송 시간의 일정함(Timeliness)을 최우선으로 하는 방식입니다. 실시간성이 생명인 오디오 스트리밍에 최적화된 모드라고 할 수 있습니다.

 

 

1. 등시성(Isochronous) 전송의 핵심 특징

       대역폭 예약 (Guaranteed Bandwidth): USB 버스 내에서 오디오 데이터를 위한 일정한 시간 슬롯을 미리 예약합니다. 다른 USB 장치(마우스, 키보드 등)가 데이터를 아무리 많이 써도 오디오 전송을 위한 통로는 항상 비어 있습니다.

       에러 재전송 없음 (No Retransmission): 일반적인 파일 복사(Bulk 전송)와 달리, 데이터에 에러가 발생해도 다시 보내지 않습니다. 재전송을 기다리다 보면 소리가 밀리거나 끊기기 때문에, 약간의 노이즈가 발생하더라도 "지금 당장" 데이터를 보내는 것을 선택한 것입니다.

       실시간 스트리밍: 데이터가 일정한 간격(예: 1ms 또는 125µs)으로 계속 흘러가야 하는 오디오와 비디오 장치에 필수적입니다.

 

 

2. 세 가지 세부 전송 모드 (동기화 방식)

데이터를 보낼 때 호스트(PC)와 장치(DAC) 사이의 박자(Clock)를 어떻게 맞추느냐에 따라 세 가지로 나뉩니다.

① 동기 방식 (Synchronous)

       Synchronous: 디바이스와 호스트가 동일 클럭 공유 (드물음).

       장치가 호스트의 USB 프레임 속도에 자신의 클럭을 강제로 맞춥니다.

       호스트의 클럭이 불안정하면 소리에 지터(Jitter)가 발생하여 음질이 떨어질 수 있습니다.

② 적응 방식 (Adaptive)

     •  Adaptive: 디바이스가 호스트 SOF (Start of Frame) 클럭에 적응.

       장치가 호스트가 보내주는 데이터의 속도를 모니터링하면서 자신의 클럭을 유연하게 조정합니다.

       주로 저가형 USB 오디오 장치에서 사용됩니다.

③ 비동기 방식 (Asynchronous) - 고음질의 핵심

       Asynchronous: 디바이스가 자체 클럭 사용. 피드백 엔드포인트로 샘플레이트 조정 요청.

       장치(DAC) 내부에 매우 정밀한 독립 클럭을 가집니다.

       장치가 주도권을 갖고 호스트에게 "지금 데이터가 너무 빠르니 조금 천천히 보내줘" 또는 "더 빨리 보내줘"라고 피드백(Feedback)을 보냅니다. 호스트의 불안정한 클럭 영향을 받지 않아 지터가 매우 적고 음질이 뛰어납니다.

 

 

3. 데이터 흐름 요약

USB Audio는 이 등시성 전송을 통해 다음과 같은 흐름을 유지합니다.

  1. 호스트: 예약된 대역폭에 맞춰 오디오 샘플을 패킷 단위로 송신합니다.
  2. USB 버스: 등시성 전송 규칙에 따라 정해진 시간 간격마다 패킷을 장치로 배달합니다.
  3. 장치 (Endpoint): 수신된 데이터를 내부 버퍼에 쌓고, 비동기 모드인 경우 호스트와 통신하며 전송 속도를 정밀하게 제어합니다.

 

 

요약

Isochronous 전송은 "틀린 데이터는 참아도, 늦는 데이터는 못 참는다"는 원칙으로 동작합니다. 이를 통해 오디오 재생 시 끊김 없는 실시간성을 확보하며, 특히 비동기(Asynchronous) 모드를 결합해 지터 없는 고음질을 구현합니다.

 

 

 
  /* sound/usb/pcm.c */
  static int configure_endpoint(struct snd_usb_substream *subs)
  {
         struct audioformat *fmt = subs->cur_audiofmt;
         unsigned int maxsize;
 
         /* 패킷 크기 계산 */
         maxsize = ((snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) ?
                             (125 * fmt->maxpacksize) : fmt->maxpacksize);
 
         if (fmt->sync_ep) {
                  /* Async mode: 피드백 엔드포인트 설정 */
                  snd_usb_init_pitch(subs->stream->chip, subs->interface,
                                                     subs->altset_idx, fmt);
         }
 
         return snd_usb_init_sample_rate(subs->stream->chip,
                                                                    subs->interface,
                                                                    subs->altset_idx,
                                                                    fmt,
                                                                    fmt->rate_max);
  }
 

 

코드는 리눅스 커널의 USB 오디오 드라이버가 등시성(Isochronous) 전송을 시작하기 전, 하드웨어 통로(Endpoint)를 최종적으로 설정하는 핵심 로직입니다.

주요 포인트를 3가지로 압축해 설명해 드릴게요.

 

1. 전송 속도에 따른 패킷 크기 최적화

코드의 maxsize 계산 부분을 보면 USB 속도(USB_SPEED_HIGH)를 체크합니다.

  • High Speed (USB 2.0): 125µs(마이크로초) 단위의 마이크로프레임을 사용하므로, 패킷 크기를 이에 맞춰 확장 계산합니다.
  • Full Speed (USB 1.1): 1ms 단위의 프레임을 기준으로 계산합니다.
  • 이는 앞서 설명한 등시성 전송에서 대역폭을 예약하기 위한 기초 작업입니다.

 

2. 비동기(Asynchronous) 모드 대응

if (fmt->sync_ep) 블록이 핵심입니다.

  • 만약 연결된 USB DAC가 비동기 방식이라면, 데이터를 보내는 통로 외에 장치로부터 "빨리 보내/천천히 보내"라는 피드백을 받을 별도의 피드백 엔드포인트를 초기화합니다 (snd_usb_init_pitch).
  • 이를 통해 지터(Jitter) 없는 고음질 재생 환경을 구축합니다.

 

3. 샘플 레이트 결정

마지막 snd_usb_init_sample_rate 함수를 통해, 실제 오디오 칩셋이 지원하는 최대 샘플 레이트(예: 44.1kHz, 192kHz 등)를 장치에 명령어로 전달하여 최종적으로 소리를 낼 준비를 마칩니다.

 

 

💡 전체 흐름 요약

  1. 계산: 내 장치의 속도(USB 2.0 등)에 맞는 데이터 덩어리 크기를 정한다.
  2. 동기화: 비동기 장치일 경우 피드백 통로를 연다.
  3. 확정: 장치에 "이 샘플 레이트로 시작한다!"라고 알려준다.

 

 

 

  13.3  snd-usb-audio 드라이버

 

snd-usb-audio는 리눅스 커널에서 USB 오디오 장치를 담당하는 표준 클래스 드라이버입니다. 특정 제조사의 전용 드라이버가 아니라, USB 오디오 규격(UAC)을 따르는 모든 장치(헤드셋, 마이크, 하이파이 DAC 등)를 범용적으로 지원하는 것이 특징입니다.

우리가 앞서 배운 configure_endpoint 코드도 바로 이 드라이버의 일부분입니다.

 

 

1. 드라이버의 핵심 역할

이 드라이버는 USB 장치와 ALSA(사운드 시스템) 사이에서 통번역가 역할을 합니다.

  1. 장치 발견 및 열거 (Enumeration): USB 장치가 꽂히면 장치 안에 저장된 Descriptor(설명서)를 읽어, 이 장치가 스피커인지 마이크인지, 어떤 샘플 레이트(44.1kHz 등)를 지원하는지 파악합니다.
  2. ALSA 인터페이스 생성: 파악된 정보를 바탕으로 유저가 쓸 수 있는 /dev/snd/pcmC... 장치 파일을 자동으로 만듭니다.
  3. 데이터 전송 관리 (URB 처리): ALSA의 오디오 데이터를 USB 전송 단위인 URB(USB Request Block) 패킷으로 쪼개서 등시성(Isochronous) 방식으로 쏘아 올립니다.

 

 

2. 드라이버 내부 구조와 데이터 흐름

snd-usb-audio는 크게 세 가지 계층을 연결합니다.

  • 상단 (ALSA Core): 애플리케이션으로부터 오디오 데이터를 받습니다.
  • 중간 (USB Audio Driver): 받은 데이터를 USB 패킷 규격에 맞게 가공하고, 앞서 본 비동기(Async) 피드백 등을 처리합니다.
  • 하단 (USB Host Controller): 실제 하드웨어 포트를 통해 데이터를 전기 신호로 내보냅니다.

 

 

3. 주요 소스 파일 구성

리눅스 커널 소스의 sound/usb/ 디렉터리에 위치하며, 주요 파일은 다음과 같습니다.

  • card.c: 드라이버의 시작점. 장치를 인식하고 사운드 카드를 생성합니다.
  • pcm.c: 실제 오디오 데이터 재생/녹음을 담당합니다. (configure_endpoint가 여기 있습니다.)
  • endpoint.c: USB 엔드포인트(통로) 관리 및 피드백 루프를 처리합니다.
  • mixer.c: 하드웨어 볼륨 조절 및 뮤트 기능을 담당합니다.
  • quirks.c: 표준을 지키지 않는 "말 안 듣는" 특정 제조사 장치들을 위한 예외 처리 코드들이 모여 있습니다.

 

 

4. 실무 디버깅 시 유용한 정보

snd-usb-audio 드라이버가 장치를 어떻게 인식했는지 확인하려면 다음 경로를 보면 됩니다.

 
  cat /proc/asound/cardX/usbbus
  cat /proc/asound/cardX/stream0
 
 

이 파일을 열면 현재 장치가 Asynchronous(비동기) 모드로 동작하는지, 어떤 Endpoint 번호를 쓰는지, 현재 흐르는 샘플 레이트가 무엇인지 실시간으로 확인할 수 있습니다.

 

 

요약

snd-usb-audio는 USB 오디오 표준을 구현한 거대한 프레임워크입니다. 하드웨어의 복잡한 USB 통신(URB, Isochronous)을 ALSA라는 표준 사운드 인터페이스로 깔끔하게 포장해주는 고마운 드라이버라고 할 수 있습니다.

리눅스 USB Audio 드라이버는 sound/usb/에 있으며, 대부분의 UAC 1.0/2.0/3.0 디바이스를 자동 지원합니다.

 

특이한 디바이스는 quirks 테이블로 처리:

 
  /* sound/usb/quirks-table.h */
  {
        USB_DEVICE(0x0763, 0x2012), /* M-Audio Fast Track Pro */
        .driver_info = (unsigned long)&(const struct snd_usb_audio_quirk) {
                .vendor_name = "M-Audio",
                .product_name = "Fast Track Pro",
                .ifnum = QUIRK_ANY_INTERFACE,
                .type = QUIRK_COMPOSITE,
                .data = &(const struct snd_usb_audio_quirk[]) {
                        {
                                .ifnum = 0,
                                .type = QUIRK_AUDIO_STANDARD_MIXER,
                        },
                        {
                                .ifnum = 1,
                                .type = QUIRK_AUDIO_FIXED_ENDPOINT,
                                .data = &(const struct audioformat) {
                                        .formats = SNDRV_PCM_FMTBIT_S24_3LE,
                                        .channels = 8,
                                        .iface = 1,
                                        .altsetting = 1,
                                        .altset_idx = 1,
                                        .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
                                        .endpoint = 0x01,
                                        .ep_attr = USB_ENDPOINT_XFER_ISOC |
                                                          USB_ENDPOINT_SYNC_ASYNC,
                                        .rate_min = 44100,
                                        .rate_max = 96000,
                                        .nr_rates = 3,
                                        .rate_table = (unsigned int[]){44100, 48000, 96000},
                                }
                        },
                        {
                                .ifnum = -1
                        }
                }
        }
  },