Embedded : : Linux/: : ALSA

[ALSA] 10. ASoC Topology 프레임워크

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

 

 

  10.  ASoC Topology 프레임워크

 

  10.1  ASoC Topology 프레임워크

 

ASoC Topology 프레임워크는 한마디로 "오디오 하드웨어 구성을 소스 코드 수정 없이 유연하게 변경하기 위한 설계도"라고 정의할 수 있습니다.

과거에는 오디오 경로(Routing), 컨트롤(Mixer), DAI 링크 등을 커널 드라이버의 C 코드에 일일이 하드코딩해야 했습니다. 하지만 최신 DSP(Digital Signal Processor) 기반 시스템은 구조가 너무 복잡하고 가변적이어서, 이를 매번 커널 컴파일로 대응하기엔 한계가 있었습니다. 이를 해결하기 위해 등장한 것이 Topology입니다.

 

 

1. 핵심 개념: "오디오판 디바이스 트리(Device Tree)"

디바이스 트리가 하드웨어의 물리적 연결 정보를 커널에 전달하듯, Topology는 오디오 구성 요소(Widgets, Routes, Controls 등)를 바이너리 파일(.tplg) 형태로 커널에 전달합니다.

  • Generic Driver: 커널 드라이버는 공통적인 하드웨어 조작 로직만 가집니다.
  • Topology File: 실제 어떤 믹서가 있고, 어떤 경로로 소리가 흐르는지는 외부 바이너리 파일이 결정합니다.
  • 런타임 로딩: 드라이버가 로드될 때 이 바이너리를 읽어와서 오디오 카드를 동적으로 구성합니다.

 

 

2. Topology로 정의할 수 있는 것들

Topology 프레임워크를 사용하면 다음 요소들을 커널 수정 없이 정의할 수 있습니다.

  1. DAPM Widgets: 믹서(Mixer), 멀티플렉서(Mux), PGA, DAC/ADC 등의 컴포넌트.
  2. DAPM Routes: 위젯 간의 오디오 신호 흐름(Graph).
  3. Controls: 볼륨 조절, 뮤트 스위치, 열거형(Enum) 설정값.
  4. DAI Links: 앞서 설명한 **FE(Front-End)**와 BE(Back-End) 간의 연결 관계 및 물리적 인터페이스 설정.
  5. Private Data: 특정 DSP 알고리즘에 필요한 전용 파라미터나 설정 데이터.

 

 

3. 워크플로우 (생성 과정)

  1. 텍스트 파일 작성 (.conf): ALSA에서 정의한 문법에 맞춰 오디오 구성 요소를 텍스트로 작성합니다.
  2. 컴파일 (alsatplg): alsatplg라는 유틸리티를 사용하여 텍스트 파일을 커널이 읽을 수 있는 **바이너리 파일(.tplg)**로 변환합니다.
    • 명령어 예시: alsatplg -c my_audio.conf -o my_audio.tplg
  3. 커널 로딩: 커널 드라이버(request_firmware 등 활용)가 부팅 시 혹은 드라이버 로드 시 이 .tplg 파일을 읽어들여 가상의 오디오 구조를 완성합니다.

 

 

4. 왜 중요한가요? (장점)

  • 유연성: 같은 오디오 코덱을 써도 모델마다 라우팅이 다를 수 있는데, 이때 드라이버 수정 없이 바이너리 파일만 교체하면 됩니다.
  • DSP 펌웨어 연동: 특히 Intel의 SST(Smart Sound Technology)나 Qualcomm의 ADSP처럼 강력한 DSP를 사용하는 환경에서는 DSP 내부의 복잡한 파이프라인을 정의하는 데 필수적입니다.
  • 업데이트 용이: 오디오 튜닝 중 경로를 바꾸거나 믹서를 추가해야 할 때, 커널을 다시 빌드하고 배포할 필요가 없습니다.

 

 

요약하자면

ASoC Topology는 오디오 드라이버에서 "하드웨어 의존적인 구성 요소"를 별도의 파일로 분리하여, 시스템을 더 유연하고 관리하기 쉽게 만드는 프레임워크입니다. DPCM과 결합하면 복잡한 현대 오디오 시스템의 정교한 라우팅을 완벽하게 제어할 수 있습니다.

혹시 특정 플랫폼(예: Intel, Qualcomm, Rockchip)에서 실제 .conf 파일을 작성하는 문법이나 alsatplg 사용 예시가 궁금하신가요? 구체적인 예제 코드를 보여드릴 수 있습니다.

 

 

  10.2  Topology 파일 포맷

 

ASoC Topology는 오디오 그래프 (widgets, routes, kcontrols)를 바이너리 파일로 정의하여 런타임에 로드하는 메커니즘입니다. 주로 DSP 기반 시스템에서 사용됩니다.

 
  # topology.conf (alsatplg 입력 파일)
  SectionPCM."Media Playback" {
          id 0
          dai."SSP2 Pin" {
                  id 0
          }
          pcm."playback" {
                  capabilities "SND_SOC_TPLG_DAI_PCM_CAP_PLAYBACK"
          }
  }
 
  SectionWidget."media0 in" {
          type "aif_in"
          stream_name "ssp2-0 Rx"
  }
 
  SectionWidget."Output Mixer" {
          type "mixer"
          mixer {
                  name "Media0 Playback"
                  channel "left"
                  mux 0
          }
  }
 
  SectionGraph."dsp-graph" {
          lines [
                    "media0 in, , SSP2 CODEC IN"
                    "Output Mixer, Media0 Playback, media0 in"
                    "SSP2 CODEC OUT, , Output Mixer"
          ]
  }
 

 

alsatplg 도구로 텍스트 파일을 바이너리 .tplg 파일로 컴파일:

 
  $ alsatplg -c topology.conf -o dsp-topology.tplg
  $ cp dsp-topology.tplg /lib/firmware/
 

 

 

ALSA ASoC Topology를 위한 .conf 파일은 오디오 경로와 컨트롤을 정의하는 설계 도면과 같습니다. 이 파일은 텍스트 형식이지만, alsatplg를 거치면 커널이 이해할 수 있는 바이너리(.tplg)로 변환됩니다.

주요 문법 구조와 실제 작성 예시를 정리해 드릴게요.

 

1. .conf 파일 주요 섹션 및 문법

Topology 파일은 크게 Control(제어), Widget(노드), Graph(연결), PCM(인터페이스) 섹션으로 나뉩니다.

① SectionControlMixer (볼륨 및 스위치)

사용자가 amixer나 설정 앱에서 조절할 수 있는 항목을 정의합니다.

 
// Plaintext  
 
 
  SectionControlMixer."Master Playback Volume" {
         comment "메인 볼륨 조절기"
         index "1"
         channel."Front" {
                  reg "0"                   // 레지스터 주소
                  shift "0"                 // 비트 시프트
                  mask "0xff"           // 마스크값
         }
         max "255"                      // 최대치
         invert "false"                  // 값 반전 여부
         tlv "db_scale_7000"     // dB 스케일 정의 연결
  }
 

 

 

② SectionWidget (오디오 컴포넌트)

실제 소리가 지나가는 '지점'을 정의합니다. (믹서, PGA, DAC 등)

 
  SectionWidget."Speaker" {
         index "1"
         type "PGA"                // Programmable Gain Amplifier 타입
         no_pm "true"            // 전원 관리 사용 안 함 (항상 켜짐)
         mixer "Master Playback Volume"   // 위에서 정의한 컨트롤 연결
  }
 

 

③ SectionGraph (라우팅/연결)

위젯과 위젯 사이의 신호 흐름을 정의합니다. (오디오판 '선 잇기')

 
  SectionGraph."MyAudioPath" {
         index "1"
         lines [
                  // "목적지, 컨트롤(있을 경우), 출발지"
                  "Speaker, , DAC"
                  "DAC, , Audio Playback"
         ]
  }
 

 

 

2. 전체적인 예시 구조 (Simple Loop)

FE(Front-End)에서 들어온 소리가 DAC를 거쳐 스피커로 나가는 아주 간단한 예시입니다.

 
  // 1. 볼륨 정의
  SectionControlMixer."Speaker Volume" {
         index "1"
         channel."Front" { reg "1"; shift "0"; mask "0x7f" }
         max "127"
  }

  // 2. 스피커 위젯 정의
  SectionWidget."Speaker" {
         index "1"
         type "PGA"
         mixer "Speaker Volume"
  }

  // 3. PCM (FE DAI) 정의
  SectionPCM."HiFi Playback" {
         index "1"
         id "0"
         dai."HiFi Playback" {
                  id "0"
         }
         pcm."HiFi Playback" {
                  playback "true"
                  capture "false"
         }
  }

  // 4. 경로 연결
  SectionGraph."Route" {
         index "1"
         lines [
                  "Speaker, , HiFi Playback"
         ]
  }
 

 

 

3. alsatplg 사용법 (컴파일)

텍스트 파일을 작성했다면, ALSA 유틸리티를 사용하여 바이너리로 변환해야 합니다.

① 컴파일 (Text -> Binary)

 

 
  # -c: 입력 설정 파일, -o: 출력 바이너리 파일
  alsatplg -c my_audio_config.conf -o my_audio_topology.tplg
 

② 유효성 검사 (옵션)

컴파일 시 문법 오류가 있다면 터미널에 에러 메시지와 함께 줄 번호가 표시됩니다.

 

③ 커널에 적용

생성된 .tplg 파일을 커널이 찾는 경로(보통 /lib/firmware/)에 넣고 드라이버를 로드하면, 드라이버 내의 request_firmware() 로직에 의해 이 토폴로지가 로딩됩니다.

 

 

4. 실무 팁

  • Index 활용: index 값은 여러 개의 오디오 카드가 있을 때 구분하는 용도로 쓰입니다. 보통 0이나 1을 사용합니다.
  • TLV (Threshold Level Value): 볼륨 수치를 단순 숫자가 아닌 -30dB 처럼 실제 감도로 표시하고 싶을 때 SectionTLV를 별도로 정의해서 연결합니다.
  • Vendor Specific Data: 특정 DSP 알고리즘(예: 노이즈 캔슬링)에 필요한 파라미터는 SectionVendorTuples를 통해 바이너리 안에 숨겨서 보낼 수 있습니다.

 

요약

.conf 작성은 **"무슨 버튼이 있고(Control), 어떤 부품이 있으며(Widget), 어떻게 연결되는지(Graph)"**를 적는 과정입니다. alsatplg는 이를 커널이 먹기 좋게 바이너리로 요리해 주는 도구입니다.

이제 작성하신 토폴로지가 커널 로그(dmesg)에서 정상적으로 로드되었는지 확인하는 방법이나, amixer로 컨트롤을 확인하는 법을 알려드릴까요?

 

  10.3  커널 Topology API

 

앞서 설명드린 .conf 파일이 설계도이고, alsatplg로 만든 .tplg 파일이 벽돌이라면, Kernel Topology API는 그 벽돌을 쌓아 실제로 집을 짓는 현장 소장(인터프리터) 역할을 합니다.

커널 소스 코드 내 sound/soc/soc-topology.c에 구현되어 있으며, 바이너리 파일을 해석하여 실시간으로 ALSA 오디오 카드 구성을 빌드합니다.

 

1. 커널 토폴로지 API의 핵심 역할

이 API는 크게 두 가지 일을 합니다.

  1. 바이너리 파싱: 유저 공간에서 넘어온 .tplg 파일(바이너리 블록)을 읽어 헤더와 데이터를 구분합니다.
  2. 객체 생성: 파싱된 정보를 바탕으로 커널 내부의 snd_kcontrol, snd_soc_dapm_widget, snd_soc_dai_link 객체를 동적으로 생성하고 등록합니다.

 

2. 주요 데이터 구조: snd_soc_tplg_ops

커널 드라이버 개발자에게 가장 중요한 인터페이스입니다. 토폴로지 프레임워크는 범용적이지만, 실제 하드웨어 제어는 드라이버마다 다르기 때문에 콜백 함수를 통해 드라이버와 통신합니다.

 
  static struct snd_soc_tplg_ops my_tplg_ops = {
         /* 위젯 로드 시 호출: 하드웨어별 특수 설정 가능 */
         .widget_load = my_widget_load,
         .widget_ready = my_widget_ready,
 
         /* 컨트롤(믹서) 로드 시 호출 */
         .control_load = my_control_load,
 
         /* DAI 링크(FE/BE) 로드 시 호출 */
         .dai_load = my_dai_load,
         .link_load = my_link_load,
 
         /* 벤더 전용 프라이빗 데이터 처리 (가장 많이 쓰임) */
         .vendor_tuples = my_vendor_tuples,
         .vendor_values = my_vendor_values,
  };
 

 

3. 실행 프로세스 (런타임 흐름)

  1. 로드 요청: 드라이버가 snd_soc_tplg_component_load() 함수를 호출합니다. 이때 보통 request_firmware()를 통해 .tplg 파일을 메모리로 읽어옵니다.
  2. 헤더 검사: 커널은 바이너리의 snd_soc_tplg_hdr를 확인하여 버전이 맞는지, 데이터가 깨지지 않았는지 검사합니다.
  3. 객체 빌드 루프:
    • Controls: 믹서와 스위치를 생성하여 오디오 카드에 등록합니다.
    • DAPM Widgets: 믹서, Mux, PGA 등을 커널 메모리에 할당합니다.
    • DAPM Routes: 위젯 간의 경로를 연결합니다.
    • DAI Links: DPCM에서 배운 FE와 BE의 연결 설정을 완료합니다.
  4. Vendor Data 전달: 설정 파일에 적어둔 특수 파라미터(Tuples)가 있다면, 이를 드라이버의 ops 콜백으로 넘겨주어 DSP가 로드될 때 설정값으로 쓰이게 합니다.

 

4. 왜 API 수준에서 이해해야 할까요?

  • 동적 하드웨어 대응: 제품마다 스피커 개수가 다르거나 마이크 경로가 바뀔 때, 커널 소스를 수정하지 않고 드라이버가 실행 시점에 스스로의 형태를 결정하게 할 수 있습니다.
  • 복잡한 DSP 제어: Intel이나 Qualcomm 같은 대형 SoC의 경우, DSP 내부에 수백 개의 파이프라인이 존재합니다. 이를 C 코드로 관리하면 코드가 수만 줄이 되지만, Topology API를 쓰면 드라이버는 공통 로직만 갖고 설정은 외부 파일로 뺄 수 있습니다.

 

5. 실전 확인법

드라이버가 토폴로지를 정상적으로 로드했는지 확인하려면 커널 로그(dmesg)를 확인해야 합니다.

 
  dmesg | grep tplg
  # 정상적인 경우: "ASoC: topology: loading xxxx widgets, yyyy controls..." 메시지 출력
 

또한, 로드된 후에는 /proc/asound/cards나 amixer를 통해 토폴로지 파일에 정의했던 컨트롤들이 실제로 생겼는지 확인할 수 있습니다.

 

요약

Kernel Topology API는 외부 바이너리(.tplg)를 읽어서 커널 오디오 서브시스템의 핵심 객체들을 실시간으로 조립해주는 파서이자 팩토리(Factory)입니다.

이제 실제 드라이버 소스 코드에서 snd_soc_tplg_component_load를 호출하는 구체적인 구현 패턴을 보여드릴까요?

 

 
  /* sound/soc/soc-topology.c */
  struct snd_soc_tplg_ops {
        int (*widget_load)(struct snd_soc_component *,
                                         int index,
                                         struct snd_soc_dapm_widget *,
                                         struct snd_soc_tplg_dapm_widget *);
        int (*dai_load)(struct snd_soc_component *,
                                  int index,
                                  struct snd_soc_dai_driver *,
                                  struct snd_soc_tplg_pcm *,
                                  struct snd_soc_dai *);
        int (*link_load)(struct snd_soc_component *,
                                   int index,
                                   struct snd_soc_dai_link *,
                                   struct snd_soc_tplg_link_config *);
        int (*manifest)(struct snd_soc_component *,
                                  int index,
                                  struct snd_soc_tplg_manifest *);
  };
 
  /* Topology 로드 */
  int err = snd_soc_tplg_component_load(component,
                                                                        &my_tplg_ops,
                                                                        firmware,
                                                                        SND_SOC_TPLG_INDEX_ALL);
 

 

 

 

  10.4  커널 드라이버에서 Topology 로드

 

실제 커널 드라이버에서 Topology를 로드하는 과정은 보통 ASoC 컴포넌트 드라이버(Component Driver) probe 단계에서 이루어집니다.

전형적인 구현 패턴은 1) 콜백 함수(Ops) 정의, 2) 펌웨어 요청, 3) 토폴로지 로드 순서로 진행됩니다. 코드로 살펴보겠습니다.

 

 

1. 전형적인 구현 패턴 (C 코드)

이 코드는 실제 Intel이나 Qualcomm 드라이버에서 공통적으로 사용하는 구조를 단순화한 것입니다.

 
  #include <sound/soc.h>
  #include <sound/soc-topology.h>
  #include <linux/firmware.h>

  /* 1. 토폴로지 콜백(Ops) 정의 */
  static struct snd_soc_tplg_ops my_tplg_ops = {
         .io_ops = NULL,    // 커스텀 I/O가 필요할 때 사용
         .io_ops_count = 0,
         .bytes_ext_ops = NULL,
         .bytes_ext_ops_count = 0,
 
         /* 바이너리 내의 위젯/컨트롤이 로드될 때 호출되는 핸들러 */
         .widget_load = my_driver_widget_load,
         .dai_load = my_driver_dai_load,
         .complete = my_driver_tplg_complete,   // 로딩 완료 후 호출
  };

  /* 2. 컴포넌트 드라이버의 Probe 함수 */
  static int my_component_probe(struct snd_soc_component *component)
  {
         const struct firmware *fw;
         const char *tplg_filename = "my_audio_map.tplg";
         int ret;

         /* A. 파일 시스템(보통 /lib/firmware)에서 바이너리 읽어오기 */
         ret = request_firmware(&fw, tplg_filename, component->dev);
         if (ret < 0) {
                  dev_err(component->dev, "토폴로지 파일을 찾을 수 없습니다: %d\n", ret);
                  return ret;
         }

         /* B. Kernel Topology API 호출하여 컴포넌트에 로드 */
         // 이 함수가 내부적으로 바이너리를 파싱하고 믹서, 위젯, 경로를 생성함
         ret = snd_soc_tplg_component_load(component, &my_tplg_ops, fw,
                                                                          SND_SOC_TPLG_INDEX_ANY);
 
         if (ret < 0) {
                  dev_err(component->dev, "토폴로지 로드 실패: %d\n", ret);
         } else {
                  dev_info(component->dev, "토폴로지 로드 성공!\n");
         }

         /* C. 사용이 끝난 펌웨어 메모리 해제 */
         release_firmware(fw);
         return ret;
  }
 

  /* 3. 컴포넌트 드라이버 정의 */
  static struct snd_soc_component_driver my_soc_component_driver = {
         .name = "my-audio-component",
         .probe = my_component_probe,   // 여기서 로직 시작
         /* 나머지 설정들... */
  };
 

 

 

 

2. 주요 단계별 상세 설명

 request_firmware()

커널은 보안과 메모리 효율을 위해 바이너리를 코드에 내장하지 않습니다. 드라이버가 로드될 때 유저 공간의 유틸리티에 요청하여 /lib/firmware/ 경로에 있는 .tplg 파일을 메모리로 가져옵니다.

 

 snd_soc_tplg_component_load()

가장 핵심적인 함수입니다.

  • component: 토폴로지 정보가 주입될 대상(오디오 카드 구성 요소)입니다.
  • ops: 드라이버 고유의 로직을 처리할 콜백 뭉치입니다.
  • fw: 메모리에 로드된 바이너리 데이터입니다.
  • index: 여러 카드가 있을 때 구분자입니다. 보통 ANY를 사용합니다.

 

③ 콜백 함수 (widget_load, dai_load)의 역할

토폴로지 바이너리에는 "하드웨어 레지스터 주소""특수 설정값"이 들어있을 수 있습니다. snd_soc_tplg_component_load가 실행되는 동안 위젯 하나를 만들 때마다 이 콜백을 호출해주는데, 이때 드라이버는 하드웨어의 특정 전원을 켜거나 레지스터를 초기화하는 작업을 수행합니다.

 

 

3. 실무에서의 확인 절차

코드를 올바르게 작성했다면, 부팅 후 다음 사항을 확인하여 정상 동작 여부를 판단합니다.

  1. 커널 로그: dmesg | grep ASoC 명령어로 "topology: 로드 완료" 메시지가 뜨는지 확인합니다.
  2. 믹서 확인: amixer -c 0 contents를 실행했을 때, .conf 파일에서 정의했던 이름(예: "Master Volume")이 목록에 나타나야 합니다.
  3. DAPM 경로: /sys/kernel/debug/asound/cardX/dapm_graph를 통해 위젯 간 연결이 의도한 대로 그려졌는지 확인합니다.

 

 

요약

구현 패턴은 매우 정형화되어 있습니다. request_firmware로 파일을 읽고, snd_soc_tplg_component_load로 넘겨주는 것이 전부입니다. 진짜 실력은 토폴로지 바이너리 안에 숨겨진 Vendor Specific Data(Tuples)를 콜백 함수에서 얼마나 잘 파싱하여 DSP를 제어하느냐에서 갈립니다.

혹시 토폴로지 바이너리 안에 사용자 정의 데이터(Vendor Tuples)를 넣고, 이를 커널 드라이버 콜백에서 읽어오는 구체적인 코드가 궁금하신가요?