자동차 오디오

Android Automotive OS(AAOS)는 핵심 Android 오디오 스택을 기반으로 빌드되었으며 차량의 인포테인먼트 시스템으로 작동하는 사용 사례를 지원합니다. AAOS는 미디어, 내비게이션, 통신 등의 인포테인먼트 사운드를 담당하지만 엄격한 가용성 및 타이밍 요구사항이 있는 차임벨 및 경고를 직접적으로 담당하지는 않습니다. AAOS는 차량이 오디오를 관리하는 데 도움이 되는 신호와 메커니즘을 제공하지만, 운전자와 동승자에게 어떤 사운드를 재생해야 할지를 결정하고 안전과 관련된 중요 사운드와 규정에 의거한 사운드가 방해 없이 제대로 들리게 하는 것은 궁극적으로 차량이 담당합니다.

Android가 차량의 미디어 환경을 관리하면 라디오 튜너 같은 외부 미디어 소스는 앱으로 표현되어야 하며 이러한 앱은 오디오 포커스와 소스의 미디어 키 이벤트를 처리할 수 있습니다.

Android 11에서는 자동차 관련 오디오 지원이 다음과 같이 변경되었습니다.

Android 사운드 및 스트림

자동차 오디오 시스템은 다음 사운드와 스트림을 처리합니다.

스트림 중심 아키텍처 다이어그램

그림 1. 스트림 중심 아키텍처 다이어그램

Android는 Android 앱에서 발생하는 사운드를 관리하고 이러한 앱을 제어하며 사운드 유형에 따라 HAL에서 앱의 사운드를 출력 장치로 라우팅합니다.

  • 핵심 오디오 명명법에서 소스라고 알려진 논리적 스트림에는 오디오 속성 태그가 지정됩니다.
  • 핵심 오디오 명명법에서 기기라고 하는 물리적 스트림에는 믹싱 후 컨텍스트 정보가 없습니다.

신뢰성을 위해 독립적인 소스에서 나오는 외부 사운드(예: 안전벨트 경고 차임벨)는 Android 외부, HAL 아래 또는 별도의 하드웨어에서 관리됩니다. 시스템의 구현자는 Android에서 1개 이상의 사운드 입력 스트림을 받아 차량에 필요한 외부 사운드 소스와 적절한 방법으로 결합하는 믹서를 제공해야 합니다.

HAL 구현과 외부 믹서는 안전에 중요한 외부 사운드가 들리게 하고, Android에서 제공하는 스트림을 믹싱해 적절한 스피커로 라우팅하는 작업을 담당합니다.

Android 사운드

앱에는 오디오 데이터의 논리적 스트림 하나 이상을 방출하기 위해 표준 Android API를 통해 상호작용하는 플레이어가 하나 이상 있을 수 있습니다(예: 포커스 제어를 위한 AudioManager 또는 스트리밍을 위한 MediaPlayer). 이 데이터는 단일 채널 모노 또는 7.1 서라운드일 수 있지만, 라우팅되어 단일 소스로 취급됩니다. 앱 스트림은 오디오가 어떻게 표현되어야 하는지에 관한 시스템 힌트를 제공하는 AudioAttributes와 연결되어 있습니다.

논리적 스트림은 AudioService를 통해 전송된 다음, 사용 가능한 물리적 출력 스트림 중 하나에만 라우팅됩니다. 이때 각 스트림은 AudioFlinger 내의 믹서 출력입니다. 오디오 속성이 물리적 스트림으로 믹스 다운되면 더 이상 사용할 수 없습니다.

그런 다음 하드웨어에서의 렌더링을 위해 각 물리적 스트림이 오디오 HAL로 전달됩니다. 자동차 앱에서 렌더링 하드웨어는 휴대기기와 유사한 로컬 코덱 또는 차량의 실제 네트워크에 있는 원격 프로세서일 수 있습니다. 어느 쪽이든 실제 샘플 데이터를 전달해 데이터를 청취 가능하게 만드는 일은 오디오 HAL 구현에서 담당합니다.

외부 스트림

인증 또는 타이밍상의 이유로 Android를 통해 라우팅하면 안 되는 사운드 스트림은 외부 믹서로 직접 전송할 수 있습니다. Android 11부터 이제 HAL은 이러한 외부 사운드를 위한 포커스를 요청하여 미디어를 일시중지하거나 다른 앱이 포커스를 획득하지 못하게 하는 등의 적절한 조치를 취할 수 있도록 Android에 알릴 수 있습니다.

외부 스트림이 Android가 생성하는 사운드 환경과 상호작용(예: 외부 튜너가 켜질 때 MP3 재생 중지)해야 하는 미디어 소스인 경우 그러한 외부 스트림은 Android 앱으로 표현되어야 합니다. 이러한 앱은 HAL이 아닌 미디어 소스를 대신해서 오디오 포커스를 요청하고, Android 포커스 정책에 맞게 필요에 따라 외부 소스를 시작/중지하여 포커스 알림에 응답합니다. 앱은 재생/일시중지와 같은 미디어 키 이벤트도 처리합니다. 이러한 외부 기기를 제어하기 위해 제안되는 한 가지 메커니즘이 HwAudioSource입니다.

출력 장치

오디오 HAL 수준에서 기기 유형 AUDIO_DEVICE_OUT_BUS는 차량 오디오 시스템에서 사용하기 위한 일반 출력 장치를 제공합니다. 버스 기기는 주소 지정 가능한 포트를 지원하며(여기서 각 포트는 물리적 스트림의 종료 지점임) 차량에서 유일하게 지원되는 출력 장치 유형입니다.

시스템 구현은 모든 Android 사운드에 하나의 버스 포트를 사용할 수 있는데, 이 경우 Android는 모든 사운드를 믹스하여 하나의 스트림으로 전달합니다. 또한 HAL이 각 CarAudioContext별로 버스 포트를 하나씩 제공하여 모든 사운드 유형을 동시에 전송할 수 있습니다. 이렇게 하면 HAL 구현이 여러 사운드를 원하는 대로 믹싱하거나 볼륨을 낮출 수 있습니다.

오디오 컨텍스트를 출력 장치에 할당하는 작업은 car_audio_configuration.xml을 통해 이루어집니다.

마이크 입력

오디오를 캡처할 때 오디오 HAL은 openInputStream 호출을 수신합니다. 이 호출에는 마이크 입력의 처리 방법을 나타내는 AudioSource 인수가 포함되어 있습니다.

VOICE_RECOGNITION 소스(특히 Google 어시스턴트)에는 에코 취소 효과가 있지만 다른 처리가 적용되지 않은 스테레오 마이크 스트림이 필요합니다(사용 가능한 경우). 빔포밍은 어시스턴트가 실행해야 합니다.

다중 채널 마이크 입력

채널이 3개 이상인 기기에서 오디오(스테레오)를 캡처하려면 위치 색인 마스크 대신 채널 색인 마스크(예: CHANNEL_IN_LEFT)를 사용합니다.

final AudioFormat audioFormat = new AudioFormat.Builder()
    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
    .setSampleRate(44100)
    .setChannelIndexMask(0xf /* 4 channels, 0..3 */)
    .build();
final AudioRecord audioRecord = new AudioRecord.Builder()
    .setAudioFormat(audioFormat)
    .build();
audioRecord.setPreferredDevice(someAudioDeviceInfo);

setChannelMasksetChannelIndexMask가 모두 설정된 경우 AudioRecordsetChannelMask에 의해 설정한 값만(최대 채널 2개) 사용합니다.

동시 캡처

Android 10부터 Android 프레임워크는 입력 동시 캡처를 지원하지만 사용자 개인 정보 보호를 위한 제한사항이 있습니다. 이러한 제한사항의 일환으로 AUDIO_SOURCE_FM_TUNER와 같은 가상 소스가 무시되며 이에 따라 일반 입력(예: 마이크)과 함께 동시에 캡처될 수 있습니다. 또한 HwAudioSources는 동시 캡처 제한사항의 일부로 간주되지 않습니다.

AUDIO_DEVICE_IN_BUS 기기 또는 보조 AUDIO_DEVICE_IN_FM_TUNER 기기에서 작동하도록 설계된 앱은 Android 기본 소스 선택 로직을 우회하려면 명시적으로 이러한 기기를 식별하고 AudioRecord.setPreferredDevice()를 사용해야 합니다.

오디오 용도

AAOS는 라우팅, 볼륨 조정, 포커스 관리에 주로 AudioAttributes.AttributeUsages를 사용합니다. 용도는 스트림이 재생되는 '이유'를 나타냅니다. 따라서 모든 스트림 및 오디오 포커스 요청에서 오디오 재생의 용도를 지정해야 합니다. AudioAttributes 객체를 빌드할 때 구체적으로 설정되지 않은 경우 기본값은 USAGE_UNKOWN입니다. 이 값은 현재 USAGE_MEDIA와 동일하게 취급되지만, 이 동작이 미디어 재생의 기반이 되어서는 안 됩니다.

시스템 사용

Android 11에는 시스템 사용이 도입되었습니다. 이러한 사용은 android.permission.MODIFY_AUDIO_ROUTING뿐만 아니라 시스템 API를 사용해야 사용한다는 점을 제외하고 이전에 설정된 사용과 유사하게 작동합니다. 새로운 시스템 사용은 다음과 같습니다.

  • USAGE_EMERGENCY
  • USAGE_SAFETY
  • USAGE_VEHICLE_STATUS
  • USAGE_ANNOUNCEMENT

시스템 사용을 포함하여 AudioAttributes를 구성하려면 setUsage 대신 AudioAttributes.Builder#setSystemUsage를 사용합니다. 비 시스템 사용을 포함하여 이 메서드를 호출하면 IllegalArgumentException이 발생합니다. 또한 빌더에서 시스템 사용과 용도가 모두 설정된 경우 빌드할 때 IllegalArgumentException이 발생합니다.

AudioAttributes 인스턴스와 관련된 용도를 확인하려면 AudioAttributes#getSystemUsage를 호출하세요. 그러면 연결된 용도나 시스템 사용이 반환됩니다.

오디오 컨텍스트

AAOS 오디오 구성을 단순화하기 위해 유사한 용도가 CarAudioContext로 그룹화되었습니다. 이러한 오디오 컨텍스트는 CarAudioService 전체에서 라우팅, 볼륨 그룹, 오디오 포커스 관리를 정의하는 데 사용됩니다.

Android 11의 오디오 컨텍스트는 다음과 같습니다.

CarAudioContext 연결된 AttributeUsages
MUSIC UNKNOWN, GAME, MEDIA
NAVIGATION ASSISTANCE_NAVIGATION_GUIDANCE
VOICE_COMMAND ASSISTANT, ASSISTANCE_ACCESSIBILITY
CALL_RING NOTIFICATION_RINGTONE
CALL VOICE_COMMUNICATION, VOICE_COMMUNICATION_SIGNALING
ALARM ALARM
NOTIFICATION NOTIFICATION, NOTIFICATION_*
SYSTEM_SOUND ASSISTANCE_SONIFICATION
EMERGENCY EMERGENCY
SAFETY SAFETY
VEHICLE_STATUS VEHICLE_STATUS
ANNOUNCEMENT ANNOUNCEMENT

오디오 컨텍스트와 용도를 매핑하여 보여줍니다. 강조표시된 행은 새로 도입된 시스템 사용입니다.

다중 영역 오디오

자동차에는 플랫폼과 상호작용하면서 별개의 미디어를 사용하려는 동시 사용자에 맞춘 새로운 사용 사례가 있기 마련입니다. 예를 들어 운전자는 앞좌석에서 음악을 재생하고 뒷좌석의 승객은 후면 디스플레이에서 YouTube 동영상을 시청할 수 있습니다. 다중 영역 오디오에서 이 작업이 가능한 이유는 서로 다른 오디오 소스를 차량의 다른 영역을 통해 동시에 재생할 수 있기 때문입니다.

Android 10부터 다중 영역 오디오에서는 OEM이 오디오를 별도의 영역에 구성할 수 있습니다. 각 영역은 차량 내에서 자체 볼륨 그룹과 컨텍스트를 위한 라우팅 구성 및 포커스 관리 기능을 가진 기기 모음입니다. 이 방식으로 기본 앞좌석을 하나의 오디오 영역으로 구성하면서 후면 디스플레이의 헤드폰 잭을 두 번째 영역으로 구성할 수 있습니다.

영역은 car_audio_configuration.xml의 일부로 정의됩니다. 그러면 CarAudioService가 구성을 읽어오고 연결된 영역을 토대로 오디오 스트림을 라우팅하도록 AudioService를 지원합니다. 각 영역은 계속해서 컨텍스트 및 애플리케이션 uid를 기준으로 라우팅 규칙을 정의합니다. 플레이어가 생성되면 CarAudioService는 플레이어가 연결된 영역을 확인한 다음 사용 방식에 따라 AudioFlinger가 오디오를 라우팅해야 하는 기기를 결정합니다.

또한 각 오디오 영역의 포커스는 개별적으로 관리됩니다. 이를 통해 서로 다른 영역에 있는 애플리케이션이 서로 간섭하지 않으며 개별적으로 오디오를 생성할 수 있고, 이와 동시에 애플리케이션이 영역 내 포커스의 변화를 계속해서 따를 수 있습니다. CarAudioServiceCarZonesAudioFocus가 각 영역의 포커스 관리를 담당합니다.

다중 영역 오디오 구성

그림 2. 다중 영역 오디오 구성

오디오 HAL

자동차 오디오 구현에서는 다음을 포함하는 표준 Android 오디오 HAL을 사용합니다.

  • IDevice.hal. 입력 및 출력 스트림을 만들고 마스터 볼륨 및 음소거를 처리하고 다음을 사용합니다.
    • createAudioPatch. 기기 간 외부-외부 패치를 만듭니다.
    • IDevice.setAudioPortConfig(). 각 물리적 스트림의 볼륨을 제공합니다.
  • IStream.hal. 입력 및 출력 변형과 함께 하드웨어로부터 또는 하드웨어로의 오디오 샘플 스트리밍을 관리합니다.

자동차 기기 유형

다음은 자동차 플랫폼과 관련된 기기 유형입니다.

기기 유형 설명
AUDIO_DEVICE_OUT_BUS Android의 기본 출력입니다. Android의 모든 오디오가 차량으로 전달되는 방법입니다. 각 컨텍스트의 스트림을 구별하기 위한 주소로 사용됩니다.
AUDIO_DEVICE_OUT_TELEPHONY_TX 전송을 위해 무선 라디오로 라우팅되는 오디오에 사용됩니다.
AUDIO_DEVICE_IN_BUS 달리 분류되지 않은 입력에 사용됩니다.
AUDIO_DEVICE_IN_FM_TUNER 라디오 입력 브로드캐스팅에만 사용됩니다.
AUDIO_DEVICE_IN_TV_TUNER TV 기기(있는 경우)에 사용됩니다.
AUDIO_DEVICE_IN_LINE AUX 입력 잭에 사용됩니다.
AUDIO_DEVICE_IN_BLUETOOTH_A2DP 블루투스를 통해 수신된 음악입니다.
AUDIO_DEVICE_IN_TELEPHONY_RX 전화 통화와 연결된 무선 라디오에서 수신한 오디오에 사용됩니다.

오디오 기기 구성

Android에 표시되는 오디오 기기는 다음 구성요소가 포함된 /audio_policy_configuration.xml에서 정의해야 합니다.

  • 모듈 이름. 'primary'(자동차 사용 사례에 사용됨), 'A2DP', 'remote_submix', 'USB'를 지원합니다. 모듈 이름과 이에 상응하는 오디오 드라이버는 audio.primary.$(variant).so로 컴파일해야 합니다.
  • devicePorts. 이 모듈에서 액세스할 수 있는 모든 입력 장치와 출력 장치(영구적으로 연결된 기기 및 이동식 기기 포함)의 기기 설명어 목록을 포함합니다.
    • 각 출력 장치의 경우 밀리벨 단위의 최소/최대/기본/단계 값으로 구성된 게인 컨트롤을 정의할 수 있습니다(1밀리벨 = 1/100dB = 1/1000벨).
    • AUDIO_DEVICE_OUT_BUS와 유형이 동일한 기기가 여러 개 있는 경우에도 devicePort 인스턴스의 주소 속성을 사용해 기기를 찾을 수 있습니다.
  • mixPorts. 오디오 HAL에서 노출한 모든 출력 스트림과 입력 스트림의 목록을 포함합니다. 각 mixPort 인스턴스는 Android AudioService의 물리적 스트림으로 간주될 수 있습니다.
  • 경로. 입력 장치와 출력 장치 사이 또는 스트림과 기기 사이에 가능한 연결 목록을 정의합니다.

다음 예는 모든 Android 오디오 스트림이 mixer_bus0_phone_out으로 혼합되는 출력 장치 bus0_phone_out을 정의합니다. 경로는 mixer_bus0_phone_out의 출력 스트림을 bus0_phone_out 기기로 가져옵니다.

<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
    <modules>
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>bus0_phone_out</item>
<defaultOutputDevice>bus0_phone_out</defaultOutputDevice>
            <mixPorts>
                <mixPort name="mixport_bus0_phone_out"
                         role="source"
                         flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                </mixPort>
            </mixPorts>
            <devicePorts>
                <devicePort tagName="bus0_phone_out"
                            role="sink"
                            type="AUDIO_DEVICE_OUT_BUS"
                            address="BUS00_PHONE">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    <gains>
                        <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                                minValueMB="-8400"
                                maxValueMB="4000"
                                defaultValueMB="0"
                                stepValueMB="100"/>
                    </gains>
                </devicePort>
            </devicePorts>
            <routes>
                <route type="mix" sink="bus0_phone_out"
                       sources="mixport_bus0_phone_out"/>
            </routes>
        </module>
    </modules>
</audioPolicyConfiguration>