29、音频驱动开发全解析
音频驱动开发全解析
1. 虚拟音频设备架构
虚拟音频设备
MyAudioDevice
是
IOAudioDevice
的子类,它会分配一个
MyAudioEngine
类的实例,该类继承自
IOAudioEngine
。同时,
MyAudioDevice
还会分配多个
IOAudioControl
实例,用于调整左右声道的输入输出音量、静音输入输出等。由于没有实际的硬件设备,这些控制目前不会执行实际操作,仅用于演示。
MyAudioEngine
类将代替实际硬件来表示I/O引擎,它会分配两个
IOAudioStream
实例,分别用于输出和输入样本缓冲区。当数据进入输出缓冲区时,会直接复制到输入缓冲区。
2. 驱动和硬件初始化
IOAudioEngine
主要负责硬件初始化,其实现通常较为简约,因为音频驱动的大部分复杂性会在
IOAudioEngine
的子类中实现。该类内部会执行一些重要任务,例如提供一个中央
IOWorkLoop
和
IOCommandGate
,供
IOAudioEngine
等从属类共享,用于序列化对驱动和硬件的访问。此外,它还提供共享的定时器服务,对象可以通过
addTimerEvent()
函数注册接收定时器事件:
virtual IOReturn addTimerEvent(OSObject *target, TimerEvent event, AbsoluteTime interval);
参数说明:
-
target
:指向将接收定时器事件通知的对象的指针。
-
interval
:以
AbsoluteTime
(纳秒)为单位指定定时器事件的频率。
-
event
:指定回调函数。
音频驱动的
IOAudioDevice
子类通常会执行以下步骤:
1. 配置硬件设备的提供者并枚举所需资源。对于PCI或Thunderbolt设备,需要映射设备内存或I/O区域;对于USB设备,需要枚举接口和/或管道。
2. 配置设备以进行操作,例如通过访问设备寄存器或发送控制请求将设备从复位/睡眠模式中唤醒。
3. 如果驱动支持多个音频芯片或具有不同数量的DMA通道、输入或输出的芯片,需要查询设备以确定其确切功能。
4. 设置音频设备的名称和描述,以便Core Audio和用户空间应用程序识别。
5. 根据从设备提取的信息,创建适当数量的
IOAudioEngine
实例,这些实例将依次分配一个或多个
IOAudioStream
实例以及相关的样本缓冲区。
以下是
MyAudioDevice
类的头文件:
#ifndef _MYAUDIODEVICE_H__
#define _MYAUDIODEVICE_H__
#include <IOKit/audio/IOAudioDevice.h>
#define MyAudioDevice com_osxkernel_MyAudioDevice
class MyAudioDevice : public IOAudioDevice
{
OSDeclareDefaultStructors(MyAudioDevice);
virtual bool initHardware(IOService *provider);
bool createAudioEngine();
// Control callbacks
static IOReturn volumeChangeHandler(OSObject* target, IOAudioControl *volumeControl,
SInt32 oldValue, SInt32 newValue);
virtual IOReturn volumeChanged(IOAudioControl *volumeControl, SInt32 oldValue, SInt32
newValue);
static IOReturn outputMuteChangeHandler(OSObject* target, IOAudioControl *muteControl,
SInt32 oldValue, SInt32 newValue);
virtual IOReturn outputMuteChanged(IOAudioControl* muteControl, SInt32 oldValue, SInt32
newValue);
static IOReturn gainChangeHandler(OSObject* target, IOAudioControl* gainControl, SInt32
oldValue, SInt32 newValue);
virtual IOReturn gainChanged(IOAudioControl* gainControl, SInt32 oldValue, SInt32
newValue);
static IOReturn inputMuteChangeHandler(OSObject* target, IOAudioControl *muteControl,
SInt32 oldValue, SInt32 newValue);
virtual IOReturn inputMuteChanged(IOAudioControl* muteControl, SInt32 oldValue, SInt32
newValue);
};
#endif
MyAudioDevice
类缺少一些常见的I/O Kit生命周期方法,如
start()
和
stop()
,因为这些方法由父类
IOAudioDevice
实现。
start()
方法会处理电源管理注册,然后调用
initHardware()
方法,驱动开发者应实现该方法。
initHardware()
是执行与硬件相关初始化的首选方法,在返回之前,应创建至少一个
IOAudioEngine
实例并通过调用
activateAudioEngine()
方法激活它。以下是
MyAudioDevice
的
initHardware()
方法实现:
bool MyAudioDevice::initHardware(IOService *provider)
{
bool result = false;
IOLog("MyAudioDevice[%p]::initHardware(%p)\n", this, provider);
if (!super::initHardware(provider))
goto done;
setDeviceName("My Audio Device");
setDeviceShortName("MyAudioDevice");
setManufacturerName("osxkernel.com");
if (!createAudioEngine())
goto done;
result = true;
done:
return result;
}
由于
MyAudioDevice
没有实际的硬件设备支持,这里只设置了设备名称、简称和制造商名称,这些信息将被Core Audio用于各种目的,设备名称将在OS X系统偏好设置中可见。最后调用
createAudioEngine()
方法来初始化并创建
MyAudioEngine
实例。
3. 注册音频控制
音频设备通常具有一个或多个可控制的属性,如调整音量、静音等。为了让用户空间客户端能够看到这些控制,需要使用
IOAudioControl
来描述每个属性。
IOAudioFamily
提供了三个
IOAudioControl
的子类,其中
IOAudioLevelControl
用于控制音量。以下是创建和注册左声道音量控制的示例:
control = IOAudioLevelControl::createVolumeControl
(65535, // Initial value
0, // min value
65535, // max value
(-22 << 16) + (32768), // -22.5 in IOFixed (16.16)
0, // max 0.0 in IOFixed
kIOAudioControlChannelIDDefaultLeft,
kIOAudioControlChannelNameLeft,
0, // control ID - driver-defined
kIOAudioControlUsageOutput);
if (!control) {
goto Done;
}
control->setValueChangeHandler(volumeChangeHandler, this);
audioEngine->addDefaultAudioControl(control);
control->release();
创建音量控制使用了特殊的工厂方法
createVolumeControl()
,前三个参数分别表示初始音量值、最小值和最大值,可以根据硬件寄存器规范指定不同的值,也可以在回调中转换这些值以匹配硬件音量控制寄存器的预期范围。接下来的两个参数设置最小值和最大值对应的dB值,音量通常从0.0 dB(表示全音量)到某个负dB值。dB值以定点值形式存储。
kIOAudioControlChannelIDDefaultLeft
指定该控制是用于左声道,
IOAudioFamily
还为其他声道定义了常量名称。最后一个参数
kIOAudioControlUsageOutput
表示这是一个输出音量控制。
当控制值发生变化时,会调用回调函数:
IOReturn SampleAudioDevice::volumeChangeHandler(IOService *target, IOAudioControl *volumeControl, SInt32 oldValue, SInt32 newValue)
{
IOReturn result = kIOReturnBadArgument;
SampleAudioDevice *audioDevice;
audioDevice = (SampleAudioDevice *)target;
if (audioDevice) {
result = audioDevice->volumeChanged(volumeControl, oldValue, newValue);
}
return result;
}
IOReturn SampleAudioDevice::volumeChanged(IOAudioControl *volumeControl, SInt32 oldValue, SInt32 newValue)
{
IOLog("SampleAudioDevice[%p]::volumeChanged(%p, %ld, %ld)\n",
this, volumeControl, oldValue, newValue);
if (volumeControl) {
IOLog("\t-> Channel %ld\n", volumeControl->getChannelID());
}
// Add hardware volume code change
return kIOReturnSuccess;
}
回调函数会提供指向值已更改的控制的指针,并传递旧值和新值。对于大多数硬件驱动,该方法会将新值写入硬件寄存器,以实现音量的增减或其他操作。
静音控制的实现与音量控制类似,但使用
createMuteControl()
工厂方法:
// Create an input mute control
control = IOAudioToggleControl::createMuteControl(false, // initial state - unmuted
AudioControlChannelIDAll, // Affects all channels
kIOAudioControlChannelNameAll,
0, // control ID - driver-defined
kIOAudioControlUsageInput);
这里的静音控制指定影响所有声道。
4. 音频引擎实现流程
graph TD;
A[开始] --> B[重写initHardware方法];
B --> C[分配样本缓冲区和IOAudioStream实例];
C --> D[实现performAudioEngineStart和performAudioEngineStop方法];
D --> E[实现free方法];
E --> F[实现getCurrentSampleFrame方法];
F --> G[实现performFormatChange方法];
G --> H[实现通知机制];
H --> I[实现clipOutputSamples和convertInputSamples方法];
I --> J[结束];
5. 注册音频控制操作步骤
| 步骤 | 操作 |
|---|---|
| 1 |
使用
IOAudioLevelControl::createVolumeControl
创建音量控制
|
| 2 | 设置音量控制的回调函数 |
| 3 |
使用
addDefaultAudioControl
方法将控制添加到父对象
|
| 4 | 释放控制的引用 |
| 5 |
使用
IOAudioToggleControl::createMuteControl
创建静音控制
|
| 6 | 重复步骤2 - 4添加静音控制 |
6. 实现音频引擎
音频引擎在音频驱动中负责实际的I/O操作,它是抽象类
IOAudioEngine
的子类,控制I/O行为并处理一个或多个相关样本缓冲区的传输。对于能同时驱动多个独立输入和输出的音频设备,建议为每个I/O通道创建一个
IOAudioEngine
实例。实现
IOAudioEngine
子类需要完成以下步骤:
1. 重写
initHardware()
方法,执行额外的硬件初始化并分配和初始化其他所需资源。
2. 分配样本缓冲区和相关的
IOAudioStream
实例。
3. 实现
performAudioEngineStart()
和
performAudioEngineStop()
方法,用于启动和停止I/O。
4. 实现
free()
方法,清理使用的资源。
5. 实现
getCurrentSampleFrame()
方法。
6. 实现
performFormatChange()
方法,响应Core Audio的格式更改请求。
7. 实现一种机制,通知父类样本缓冲区何时回到起始位置的时间戳。
8. 为输出流实现
clipOutputSamples()
方法,为输入流实现
convertInputSamples()
方法。
以下是
MyAudioDevice
的
IOAudioEngine
子类的头文件:
#ifndef _MYAUDIOENGINE_H_
#define _MYAUDIOENGINE_H_
#include <IOKit/audio/IOAudioEngine.h>
#include "MyAudioDevice.h"
#define MyAudioEngine com_osxkernel_MyAudioEngine
class MyAudioEngine : public IOAudioEngine
{
OSDeclareDefaultStructors(MyAudioEngine)
public:
virtual void free();
virtual bool initHardware(IOService* provider);
virtual void stop(IOService *provider);
virtual IOAudioStream *createNewAudioStream(IOAudioStreamDirection direction,
void* sampleBuffer, UInt32 sampleBufferSize);
virtual IOReturn performAudioEngineStart();
virtual IOReturn performAudioEngineStop();
virtual UInt32 getCurrentSampleFrame();
virtual IOReturn performFormatChange(IOAudioStream* audioStream, const IOAudioStreamFormat*
newFormat, const IOAudioSampleRate* newSampleRate);
virtual IOReturn clipOutputSamples(const void* mixBuf, void* sampleBuf, UInt32
firstSampleFrame, UInt32 numSampleFrames,
const IOAudioStreamFormat* streamFormat,
IOAudioStream* audioStream);
virtual IOReturn convertInputSamples(const void* sampleBuf, void* destBuf, UInt32
firstSampleFrame, UInt32 numSampleFrames,
const IOAudioStreamFormat* streamFormat,
IOAudioStream* audioStream);
private:
IOTimerEventSource* fAudioInterruptSource;
SInt16* fOutputBuffer;
SInt16* fInputBuffer;
UInt32 fInterruptCount;
SInt64 fNextTimeout;
static void interruptOccured(OSObject* owner, IOTimerEventSource* sender);
void handleAudioInterrupt();
};
#endif
7. I/O引擎初始化
IOAudioEngine
有自己的
initHardware()
方法,需要重写该方法以执行特定于I/O引擎的硬件初始化,以及分配和初始化其他所需资源。方法返回后,引擎应准备好启动I/O,然后可以调用
performAudioEngineStart()
开始实际的I/O操作。在本例中,
initHardware()
方法由
IOAudioDevice::activateAudioEngine()
调用。虽然
IOAudioEngine
继承自
IOService
,但这里不重写或调用
start()
方法,因为该类由
MyAudioDevice
分配和初始化,而不是由I/O Kit。不过,声明了
stop()
方法,可用于撤销
initHardware()
中执行的操作。以下是
MyAudioDevice
驱动的
initHardware()
方法实现:
#define kAudioSampleRate 48000
#define kAudioNumChannels 2
#define kAudioSampleDepth 16
#define kAudioSampleWidth 16
#define kAudioBufferSampleFrames kAudioSampleRate/2
// Buffer holds half second's worth of audio.
#define kAudioSampleBufferSize (kAudioBufferSampleFrames * kAudioNumChannels *
(kAudioSampleDepth / 8))
#define kAudioInterruptInterval 10000000 // nanoseconds (1000 ms / 100 hz = 10ms).
#define kAudioInterruptHZ 100
bool MyAudioEngine::initHardware(IOService *provider)
{
bool result = false;
IOAudioSampleRate initialSampleRate;
IOAudioStream* audioStream;
IOWorkLoop* workLoop = NULL;
IOLog("MyAudioEngine[%p]::initHardware(%p)\n", this, provider);
if (!super::initHardware(provider))
goto done;
fAudioInterruptSource = IOTimerEventSource::timerEventSource(this, interruptOccured);
if (!fAudioInterruptSource)
return false;
workLoop = getWorkLoop();
if (!workLoop)
return false;
if (workLoop->addEventSource(fAudioInterruptSource) != kIOReturnSuccess)
return false;
// Setup the initial sample rate for the audio engine
initialSampleRate.whole = kAudioSampleRate;
initialSampleRate.fraction = 0;
setDescription("My Audio Device");
setSampleRate(&initialSampleRate);
// Set the number of sample frames in each buffer
setNumSampleFramesPerBuffer(kAudioBufferSampleFrames);
setInputSampleLatency(kAudioSampleRate / kAudioInterruptHZ);
setOutputSampleOffset(kAudioSampleRate / kAudioInterruptHZ);
fOutputBuffer = (SInt16 *)IOMalloc(kAudioSampleBufferSize);
if (!fOutputBuffer)
goto done;
fInputBuffer = (SInt16 *)IOMalloc(kAudioSampleBufferSize);
if (!fInputBuffer)
goto done;
// Create an IOAudioStream for each buffer and add it to this audio engine
audioStream = createNewAudioStream(kIOAudioStreamDirectionOutput,
fOutputBuffer, kAudioSampleBufferSize);
if (!audioStream)
goto done;
addAudioStream(audioStream);
audioStream->release();
audioStream = createNewAudioStream(kIOAudioStreamDirectionInput,
fInputBuffer, kAudioSampleBufferSize);
if (!audioStream)
goto done;
addAudioStream(audioStream);
audioStream->release();
result = true;
done:
return result;
}
该方法首先分配一个
IOTimerEventSource
,用于模拟硬件中断,并使用
setDescription()
方法设置描述信息,该信息将在系统偏好设置的声音面板中显示。接着设置引擎的采样率,采样率是
IOAudioEngine
的属性,如果引擎管理多个流,它们必须具有相同的采样率。还需要定义样本缓冲区包含的样本数量,在
MyAudioEngine
中使用两个流(一个输入流和一个输出流),缓冲区大小通过
setNumSampleFramesPerBuffer()
方法设置为采样率的一半,即24000个样本,对应半秒的音频。通过公式
24000 samples * 2 channels * (16 bits / 8 bits = 2 bytes) = 96000 bytes
计算出样本缓冲区的大小。
setInputSampleLatency()
和
setOutputSampleLatency()
方法用于向Core Audio指示从样本计划播放到实际在硬件中开始播放所需的时间。在本例中,由于没有硬件延迟,将延迟设置为一个中断周期(10毫秒),以给Core Audio在读写样本时留出一些空间。
还需要为样本缓冲区分配内存,在
MyAudioDevice
中,由于不执行到硬件设备的DMA操作,直接使用
IOMalloc()
分配输入和输出缓冲区。对于基于硬件的驱动,需要分配
IOBufferMemoryDescriptor
或为缓冲区创建单独的
IOMemoryDescriptor
,并为DMA或I/O传输准备缓冲区。每个缓冲区需要与一个
IOAudioStream
关联,通过
createNewAudioStream()
方法分配
IOAudioStream
实例,并使用
addAudioStream()
方法将其添加到引擎中,添加后可以释放引用,父类会处理最终的释放。
8. 创建和初始化音频流
IOAudioEngine
至少需要一个
IOAudioStream
才能正常工作,一个流与一个样本缓冲区关联,并描述该缓冲区支持的格式和采样率。流可以是输出流或输入流,
IOAudioStream
负责处理数据进出样本缓冲区的机制。它内部维护一个混合缓冲区,将多个客户端的音频数据混合成一个流,然后存储到最终的样本缓冲区中。对于大多数情况,
IOAudioStream
的默认行为就足够了,但如果驱动需要更高级的功能,可以重写
IOAudioStream
的大多数方法以提供自定义行为。以下是
MyAudioEngine
的
createNewAudioStream()
方法,用于创建输入和输出流:
IOAudioStream *MyAudioEngine::createNewAudioStream(IOAudioStreamDirection direction,
void* sampleBuffer, UInt32 sampleBufferSize)
{
IOAudioStream* audioStream;
audioStream = new IOAudioStream;
if (audioStream) {
if (!audioStream->initWithAudioEngine(this, direction, 1)) {
audioStream->release();
} else {
IOAudioSampleRate rate;
IOAudioStreamFormat format = {
2, // num channels
9. I/O引擎初始化操作步骤
| 步骤 | 操作 |
|---|---|
| 1 |
重写
initHardware
方法,调用父类的
initHardware
方法
|
| 2 |
分配
IOTimerEventSource
|
| 3 | 获取工作循环并添加事件源 |
| 4 | 设置音频引擎的初始采样率 |
| 5 | 设置设备描述、采样率、每个缓冲区的样本帧数、输入和输出样本延迟 |
| 6 |
使用
IOMalloc
分配输入和输出缓冲区
|
| 7 |
使用
createNewAudioStream
方法创建输入和输出流
|
| 8 |
使用
addAudioStream
方法将流添加到引擎中并释放引用
|
10. 创建和初始化音频流操作步骤
graph TD;
A[开始] --> B[创建IOAudioStream实例];
B --> C{初始化是否成功};
C -- 是 --> D[设置采样率和流格式];
C -- 否 --> E[释放IOAudioStream实例];
D --> F[结束];
E --> F;
综上所述,开发音频驱动需要对虚拟音频设备架构、硬件初始化、音频控制注册、音频引擎实现等方面有深入的理解,并按照相应的步骤进行操作,才能实现一个完整的音频驱动程序。
更多推荐
所有评论(0)