nyancoder

WWDC 2021 - Create audio drivers with DriverKit 본문

WWDC/WWDC 2021

WWDC 2021 - Create audio drivers with DriverKit

nyancoder 2021. 8. 25. 02:34

원본 영상: https://developer.apple.com/videos/play/wwdc2021/10190/

 

 

  • 오디오 드라이버의 동작은 macOS Big Sur 이전에는 오디오 서버 플러그인과 커널 확장을 통해 하드웨어 장치와 통신해야 했습니다.

  • macOS Big Sur에서 CoreAudio HAL 은 DriverKit Extension 위에 빌드된 오디오 서버 플러그인을 제공했습니다.
  • 플러그인과 dext 사이의 계층은 kext와 동일 하지만 커널에서 사용자 공간으로 이동하여 보안이 향상되었습니다.

  • 하드웨어 오디오 드라이버를 구현하려면 오디오 서버 플러그인과 드라이버 확장이라는 두 가지 개별 구성 요소를 여전히 필요로 합니다.
  • 이는 개발을 복잡하게 만들고 리소스를 늘리며 오버헤드와 대기 시간을 증가시킬 수 있습니다.

  • macOS Monterey부터는 dext만 있으면 됩니다.

  • AudioDriverKit은 USBDriverKit, PCIDriverKit과 함께 오디오 드라이버 확장을 작성하는 데 사용되는 새로운 DriverKit 프레임워크입니다.
  • 이 프레임워크는 CoreAudio HAL에 대한 모든 프로세스 간 통신을 처리합니다.
  • 이제 오디오 서버 플러그인이 없으므로, dext와 오디오 서버 플러그인 간에 통신할 필요 없이 DriverKit 내에서 집중하면 됩니다.
  • AudioDriverKit 확장 프로그램은 Mac 앱에 번들로 제공되므로 별도의 설치 프로그램이 필요하지 않습니다.
    이제 드라이버가 즉시 로드되며 재부팅할 필요가 없습니다.

 

Architecture

  • 위의 다이어그램은 HAL 이 AudioDriverKit 프레임워크를 사용하여 드라이버 확장과 통신하는 방법을 보여줍니다.
  • AudioDriverKit 프레임워크는 CoreAudio와 오디오 dext 간의 모든 통신에 사용될 AudioDriverKit user client를 생성합니다.
  • 이 사용자 클라이언트는 직접 사용하기 위한 것이 아니며, dext에 노출되지도 않습니다.
  • 앱과 dext 간에 통신하기 위해서는 플러그인이나 사용자 지정 사용자 클라이언트가 필요하지 않습니다.
  • 앱은 필요한 경우 dext와 직접 통신하기 위해 사용자 지정 클라이언트를 열 수 있습니다.

 

Prerequisites

  • 모든 DriverKit 드라이버 확장 에는 DriverKit에 대한 자격 이 있어야 합니다.
  • AudioDriverKit dext에는 모든 사용자 클라이언트 접근을 허용할 수 있는 권한도 있어야 합니다.
  • 이는 모든 DriverKit 권한이 승인된 모든 개발자가 사용할 수 있습니다.
  • 또한 필요에 따라 전송 권한을 추가해야 합니다.
  • USB 또는 PCI전송 권한을 요청하지 않은 경우, Apple 개발자 사이트를 방문하여 요청을 제출해야 합니다.
  • 가상 오디오 드라이버 또는 장치만 필요한 경우 오디오 서버 플러그인 드라이버 모델을 사용해야 합니다. 

  • dext의 info.plist를 살펴보면 설정을 dext의 IOKitPersonalities에 추가해야 합니다.
  • AudioDriverKit은 HAL에 필요한 IOUserAudioDriverUserClient 생성을 처리합니다.
  • HAL에는 사용자 클라이언트를 연결하는데 필요한 권한이 있습니다.

  • 다음은 SimpleAudioDriverUserClient에 대한 사용자 지정 사용자 클라이언트의 예입니다.
  • 자세한 내용은 <AudioDriverKit/AudioDriverKitTypes.h> 헤더 파일을 참고할 수 있습니다.

 

Initialization

  • 오디오 dext를 구성하는 첫 번째 단계는 IOUserAudioDriver의 하위 클래스를 만들고 가상 함수를 재정의하는 것입니다.
  • IOUserAudioDriver는 IOService의 하위 클래스입니다.
  • IOUserAudio 클래스와 같이 사용자 지정 동작을 구현하는 데 필요한 객체를 하위 클래스로 만듭니다.
  • 그런 다음 해당 객체를 적절히 설정한 뒤, IOUserAudioDriver에 추가합니다.

  • IOUserAudio 개체의 개요는 위와 같습니다.
  • SimpleAudioDriver는 IOUserAudioDriver의 하위 클래스이며, dex의 진입점입니다.
  • SimpleAudioDriver는 IOUserAudioDevice의 하위 클래스인 SimpleAudioDevice를 생성합니다.
  • 오디오 장치는 모든 시작-중지 IO 관련 메시지, 타임스탬프, 구성 변경을 처리합니다.
  • SimpleAudioDevice는 다양한 IOUserAudioObject를 생성합니다.
  • 장치 개체는 또한 OSTimerDispatchSources, OSActions를 생성하고 하드웨어 인터럽트 및 IO를 시뮬레이션하기 위해 톤 생성기를 구현합니다.
  • IOUserAudioStream은 장치가 소유한 스트림으로, HAL에 매핑될 오디오 IO에 IOMemoryDescriptor를 사용합니다.
  • IOUserAudioVolumeLevelControl은 스칼라 또는 dB 값을 사용하는 제어 개체입니다.
  • 제어 값은 입력 오디오 버퍼에 게인을 적용하는 데 사용됩니다.
  • 모든 IOUserAudioObject는 IOUserAudioCustomProperties를 가질 수 있습니다.
  • SimpleAudioDevice는 한정자 및 데이터 값으로 사용자 정의 속성 및 문자열의 예를 생성합니다.

  • SimpleAudioDriver는 IOUserAudioDriver의 하위 클래스입니다.
  • Start, Stop, NewUserClient는 드라이버가 재정의해야 하는 IOService 클래스의 가상 함수입니다.
  • StartDevice 및 StopDevice는 IOUserAudioDriver의 IO 관련 가상 메서드이며, HAL이 오디오 장치에 대한 IO를 시작하거나 중지할 때 호출됩니다.

  • 이 예에서는 NewUserClient를 재정의하여 사용자 클라이언트 연결을 만드는 방법을 보여줍니다.
  • NewUserClient는 클라이언트 프로세스가 dext에 연결하려고 할 때 호출됩니다.
  • AudioDriverKit 프레임워크는 IOUserAudioDriver 기본 클래스에서 NewUserClient를 호출하여 HAL에 필요한 사용자 클라이언트 생성을 처리합니다.
  • CoreAudio HAL에 필요한 IOUserAudioDriverUserClient를 생성합니다.
  • 이전에 추가한 드라이버 확장 info.plist 항목에서 사용자 클라이언트 개체를 만드는 IOService Create를 호출하여 사용자 지정 사용자 클라이언트를 만들 수도 있습니다.

  • Start를 재정의 하고 사용자 지정 IOUserAudioDevice 개체를 만드는 방법은 위와 같습니다.
  • 부모 클래스에서 Start를 호출한 다음 SimpleAudioDevice를 할당하고 몇 가지 필수 매개변수로 초기화합니다.
  • 그런 다음 초기화된 장치를 AddObject를 호출하여 오디오 드라이버에 추가해야 합니다.
  • 마지막으로 서비스를 등록하면 드라이버가 준비됩니다.

 

Create audio objects

  • 드라이버가 초기화되었으므로 장치, 스트림, 기타 몇 가지 오디오 개체를 생성할 수 있습니다.
  • 사용자 지정 동작을 지정하려면 IOUserAudioDevice를 상속받아 가상 함수를 구현해야 합니다.
  • 입력 스트림, 볼륨 컨트롤, 사용자 지정 속성 개체를 만들 수 있습니다.

  • SimpleAudioDevice의 init 함수에서 장치를 구성하고 다양한 오디오 개체를 만드는 방법은 위와 같습니다.
  • 디바이스의 샘플 레이트 관련 정보는 디바이스에서 SetAvailableSampleRates , SetSampleRate를 호출하여 설정할 수 있습니다.
  • IOUserAudioStream에 전달할 IOBufferMemoryDescriptor를 만듭니다.
  • 메모리는 CoreAudio HAL에 매핑되고 오디오 IO에 사용됩니다.
  • 메모리는 이상적으로는 DMA에서 하드웨어에 사용되는 것과 동일한 IO 메모리여야 합니다.
  • IOUserAudioStream은 스트림 방향을 Input으로 지정하고 생성합니다.

  • IOMemoryDescriptor 스트림이 작동하려면 몇 가지 추가 사항을 구성해야 합니다.
  • 스트림 형식은 IOUserAudioStreamBasicDescriptions의 format 배열을 전달하여 만들 수 있습니다.
  • 샘플 속도, format ID, 기타 필수 format 속성을 지정합니다.
  • 위에서 선언한 스트림 format 목록을 전달하여 사용 가능한 format을 설정합니다.
  • 그런 다음 스트림의 현재 format을 설정합니다.
  • 마지막으로 AddStream을 호출하여 구성된 스트림을 장치에 추가합니다.

  • 볼륨 레벨 컨트롤을 만드는 방법은 위와 같습니다.
  • 볼륨 제어 개체를 만들려면 IOUserAudioLevelControl::Create 함수를 호출합니다.
  • 컨트롤은 초기 레벨이 -6dB로 설정되고 범위가 96dB인 설정 가능한 볼륨 컨트롤입니다.
  • 컨트롤의 요소, 범위 및 클래스도 지정해야 합니다.
  • 마지막으로 제어 개체를 장치에 추가합니다.
  • 볼륨 제어 게인 값은 입력 스트림의 IO 버퍼에 게인을 적용하여 IO 경로에서 사용됩니다.

  • 장치에 대한 사용자 지정 속성 개체를 만드는 방법은 위와 같습니다.
  • 모든 사용자 정의 속성 개체에 대해 속성 주소를 제공해야 합니다.
  • 전역 범위와 기본 요소를 사용하여 사용자 지정 선택기 유형을 정의합니다.
  • 그다음 위에서 정의한 속성 주소를 제공하여 사용자 지정 속성 개체를 만듭니다.
  • 사용자 정의 속성은 설정 가능하며 한정자와 데이터 값 유형은 모두 문자열입니다.
  • 이제 한정자와 데이터 값에 대한 OSString을 만들고 사용자 정의 속성에 설정합니다.
  • 마지막으로 장치에 사용자 지정 속성을 추가합니다.

 

IO path and timestamps

  • GetIOMemoryDescriptor함수는 IOUserAudioStream에서 사용하는 IOMemoryDescriptor를 반환합니다.
  • IOMemoryDescriptor는 스트림을 생성할 때 init 메서드로 전달되며, 스트림은 새로운 메모리 디스크립터로도 업데이트될 수 있습니다.
  • 메모리는 HAL에 매핑되고 오디오 IO에 사용됩니다.
  • 스트림에서 사용하는 MemoryDescriptor는 이상적으로는 하드웨어 장치의 DMA에 사용되는 것과 동일해야 합니다.
  • IOUserAudioClockDevice는 IOUserAudioDevice의 기본 클래스입니다.
  • UpdateCurrentZeroTimestamp 및 GetCurrentZeroTimestamp는 하드웨어 장치의 타임스탬프를 처리하는 데 사용해야 합니다.
  • 타임스탬프는 원자적으로 처리되며 HAL은 샘플 시간-호스트 시간 쌍을 사용하여 IO를 실행하고 동기화합니다.
  • 하드웨어 시계의 타임스탬프를 최대한 가깝게 추적하는 것이 중요합니다.

  • SimpleAudioDevice 클래스의 IO 관련은 위와 같습니다.
  • HAL이 IO를 실행하려고 할 때 StartIO 및 StopIO가 드라이버에서 호출됩니다.
  • IOTimerDispatchSource 및 OSAction을 사용하여 하드웨어 인터럽트를 시뮬레이션하는 함수들도 있습니다.
  • 하드웨어 인터럽트는 입력 IO 버퍼에서 제로 타임스탬프와 오디오 데이터를 생성하는 데 사용됩니다.
  • 이 예제는 하드웨어 장치에 대해 실행되지 않으므로 하드웨어 인터럽트 및 DMA 대신 타이머와 작업이 사용됩니다.

  • HAL이 장치에서 IO를 시작하려고 할 때 장치 개체에서 StartIO가 호출됩니다.
  • 하드웨어에서 IO를 시작하는 데 필요한 모든 호출은 여기에서 수행해야 합니다.
  • 그다음 기본 클래스에서 StartIO를 호출해야 합니다.
  • 다음으로, CreateMapping을 호출하여 IOMemoryMap을 생성할 수 있도록 입력 스트림의 IOMemoryDescriptor를 가져옵니다.
  • IO 버퍼에 톤을 생성하기 위한 액션 핸들러에서 버퍼 주소, 길이, 오프셋을 사용합니다.
  • 타임 소스들과 입력 오디오 버퍼를 채우고 타임스탬프를 생성하기 위한 액션을 구성하고 활성화하기 위해서 StartTimers를 호출합니다.

  • UpdateCurrentZeroTimestamp는 IOUserAudioDevice에 대한 샘플 시간-호스트 시간 쌍을 원자적으로 업데이트하기 위해 호출됩니다.
  • 장치에서 구성된 mach_absolute_time 및 호스트 틱을 기반으로 wake-up 시간이 설정한 다음, 타이머 소스를 활성화합니다.

  • ZtsTimerOccurred 작업은 장치에서 새 타임스탬프를 업데이트할 수 있도록 wake-up 시간을 기반으로 호출됩니다.
  • 제로 타임스탬프 작업이 실행되면 GetCurrentZeroTimestamp를 호출하여 장치에서 마지막 제로 타임스탬프 값을 가져옵니다.
  • 이것이 첫 번째 타임스탬프인 경우 타이머에 전달된 mach_absolute_time을 앵커 시간으로 사용합니다.
  • 다른 경우에는 타임스탬프는 GetZeroTimestampPeriod로 설정되고 호스트 시간은 버퍼당 호스트 틱으로 업데이트됩니다.
  • UpdateCurrentZeroTimestamp를 호출하면 HAL이 새 값을 사용할 수 있도록 기기의 타임스탬프가 업데이트됩니다.
  • 다음 제로 타임스탬프에 대해 깨어나도록 ZTS 타이머를 설정합니다.

  • DMA를 시뮬레이션하기 위해 타이머 작업이 실행될 때 오디오 데이터가 입력 IO 버퍼에 기록됩니다.
  • 먼저 start IO가 호출될 때 할당된 입력 메모리 맵 이 유효한지 확인합니다.
  • 메모리 맵 버퍼 길이와 스트림 format을 사용하여 IO 버퍼의 샘플 길이를 가져옵니다.
  • 스트림은 부호 있는 16비트 pcm 샘플 형식만 지원하므로 버퍼 주소와 오프셋을 가져와 int16_t 버퍼 포인터로 할당합니다.

  • 이제 사인 톤을 생성하여 입력 IO 버퍼를 채울 수 있습니다.
  • 먼저 입력 볼륨 제어 게인을 스칼라 값으로 가져옵니다.
  • 그런 다음 필요한 샘플 수만큼 반복하면서 볼륨 제어 게인을 적용하여 사인 톤을 생성합니다.
  • 다음으로 버퍼를 순환하고 채널 수를 기반으로 IO 버퍼에 사인 톤 샘플을 채우고 랩어라운드도 고려합니다.

 

Configuration changes

  • 위의 함수들을 사용하여 장치 구성 변경을 수행할 수 있습니다.
  • IO 또는 해당 구조에 영향을 주는 오디오 장치 상태 변경의 경우 드라이버는 RequestDeviceConfigurationChange를 호출하여 구성 변경을 요청해야 합니다.
  • HAL은 실행 중인 IO를 중지하고 드라이버에서 PerformDeviceConfigurationChange를 호출합니다.
  • 그래야만 오디오 장치가 IO 관련 상태를 업데이트할 수 있습니다.
  • 이에 대한 일반적인 시나리오는 오디오 장치의 현재 샘플 속도를 업데이트하거나 하드웨어 장치의 변경 사항에 대응하도록 현재 스트림 형식을 변경하는 것입니다.

  • 다이어그램은 장치 구성 변경에 대한 이벤트 순서를 보여줍니다.
  • 드라이버는 먼저 구성 변경을 요청해야 합니다.
  • HAL은 모든 리스너에게 기기에 대한 구성 변경이 시작될 것임을 알립니다.
  • 장치가 현재 실행 중인 경우 IO가 중지됩니다.
  • 그다음 장치의 현재 상태가 캡처됩니다.
  • PerformDeviceConfigurationChange는 드라이버에서 호출됩니다.
  • 이것은 드라이버가 허용되는 경우 장치 및 하드웨어의 상태를 변경합니다.
  • 구성 변경이 수행되면 장치의 새 상태가 캡처되고 IO 버퍼 또는 샘플 속도와 같은 모든 IO 관련 상태가 업데이트됩니다.
  • 그러면 장치 상태에 대한 모든 변경 사항이 모든 클라이언트 수신기에 통보됩니다.
  • 구성 변경 이전에 IO가 이전에 실행 중이었다면 장치에서 IO가 다시 시작됩니다.
  • 마지막으로 HAL은 구성 변경이 이제 종료되었음을 모든 리스너에게 알립니다.

  • 하드웨어 상향식 구성 변경 요청을 시뮬레이션하기 위해 사용자 지정 클라이언트 명령을 사용하여 dext에서 샘플 속도 변경을 트리거합니다.
  • RequestDeviceConfigurationChange가 HAL에 알립니다
  • 모든 종류의 OSObject가 오디오 장치에 대한 구성 변경 요청 정보가 될 수 있습니다.
  • 이 예에서는 사용자 지정 구성 변경 작업과 변경 정보를 OSString으로 제공합니다.
  • 구성 변경 수행을 처리하려면 SimpleAudioDevice 클래스가 PerformDeviceConfigurationChange 메서드를 재정의 해야 합니다.

  • PerformDeviceConfigurationChange에서 switch 문의 구성 변경 작업을 처리합니다.
  • 구성 변경이 요청되었을 때 변경 정보로 제공된 동일한 OSString 개체를 기록합니다.
  • 다음으로 현재 샘플 속도를 가져오고 장치에서 새 속도를 설정합니다.
  • 오디오 스트림 이 현재 스트림 형식을 업데이트하는지 확인해야 합니다.
  • 스트림 개체에서 DeviceSampleRateChanged를 호출하여 샘플 속도 변경을 처리합니다.
  • 장치가 직접 처리하지 않는 다른 구성 변경 작업은 기본 클래스로 전달할 수 있습니다.

  • SimpleAudio는 드라이버 예제 앱에서는 드라이버 설치 버튼을 누르면 보안 기본 설정이 나타납니다.

  • 허용을 누르면 오디오 드라이버 확장이 동적으로 로드됩니다.
  • 이전 에는 재부팅이 필요했기 때문에 kext에서는 이것이 불가능했습니다.

  • SimpleAudioDevice에는 샘플 속도 형식과 톤 선택 데이터 소스가 있습니다.
  • 그리고 우리가 샘플 코드에서 추가한 볼륨 컨트롤도 볼 수 있습니다.
  • 이제 QuickTime을 열고 오디오 장치에서 오디오 녹음을 수행할 수 있습니다.

  • 그리고 상향식 구성 변경을 테스트하기 위해 dext와 직접 통신하여 톤 주파수 또는 샘플 속도를 토글 할 수 있으며 변경 사항은 오디오 MIDI 설정에도 반영되어야 합니다.

  • 드라이버 확장을 제거하려면 응용 프로그램을 삭제하기만 하면 됩니다.

  • 그러면 오디오 MIDI 설정에서 더 이상 사용할 수 없다는 것을 볼 수 있습니다.

 

목차: https://nyancoder.tistory.com/2

Comments