searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

[CoreAudio]如何通过虚拟音频设备录制系统声音

2023-05-25 03:26:35
71
0

使用AVCaptureSession录制

首先,我们想要录制声音的话,可以通过AVCaptureSession来实现,AVCaptureSession可以实现摄像头或者声音的录制,这取决于我们将什么作为输入设备(inputDevice),下面是一段使用AVCaptureSession进行录制的示例代码:

// 创建一个AVCaptureSession
AVCaptureSession *session = [AVCaptureSession new];
// 告诉Session要准备开始配置参数
[session beginConfiguration];
    
// 根据设备(mDevice)初始化输入(下文会介绍如何获取mDevice)
NSError *error  = nil;
AVCaptureDeviceInput* input = [[AVCaptureDeviceInput alloc] initWithDevice:mDevice error:&error];
if (!input) {
    NSLog(@"create capture device input: %s", [[error localizedDescription] UTF8String]);
    return;
}
if ([session canAddInput:input]) {
    [session addInput:input];
} else {
    NSLog("session can not add input");
    return;
}
    
// 创建一个输出(决定了使用什么样的产物格式)
AVCaptureAudioDataOutput *output = [AVCaptureAudioDataOutput new];
if ([session canAddOutput:output]) {
    [session addOutput:output];
} else {
    NSLog("session can not add output");
    return;
}

// 配置输出的代理回调(在代理回调里进行数据接收)
OSXAudioReceiver *receiver = [OSXAudioReceiver new];
dispatch_queue_t queue = dispatch_queue_create("osx-playback.receiver", NULL);
[output setSampleBufferDelegate:receiver queue:queue];

// 提交配置结果    
[session commitConfiguration];
// 告诉Session开始录制
[session startRunning];

前文里面的OSXAudioReceiver需要实现Delegate:AVCaptureAudioDataOutputSampleBufferDelegate,这个Delegate只有一个关键接口:

- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CMSampleBufferRef ref = (CMSampleBufferRef)CFRetain(sampleBuffer);
    if (g_async_queue_length(playback->frameQueue) < MAX_AUDIO_QUEUE_LENGTH) {
        g_async_queue_push(playback->frameQueue, ref);
    }
}

至此,就已经完成了一个简单声音录制功能的开发。

接下来说一下AVCaptureDevice,就是前面代码用到的mDevice参数。经常用到的两种CaptureDevice分辨是Video和Audio,前者用于摄像头录制,后者用于声音录制,可以通过如下接口获取:

 NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; // AVMediaTypeVideo

创建一个音频设备捕获系统声音

上面提到了可以使用音频录制设备来录制声音,比如麦克风。

系统的声音肯定无法通过麦克风来录制,因为默认情况下系统声音是直接通过音频输出设备播放的,也就是扬声器或耳机。

因此需要想办法把系统声音重定向到一个音频录制设备,这就是我们需要实现的虚拟音频设备。


// 将设备配置为既能够支持输入,也能够支持输出
static Boolean ClinkAudio_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress)
{
    ....
    switch(inObjectID)
    {
        // 同时支持输入和输出
        case kObjectID_Stream_Input:
        case kObjectID_Stream_Output:
              switch(inAddress->mSelector)
             {
                 ...
                case kAudioObjectPropertyBaseClass:
                    theAnswer = true;
                    break;
            };
            break;
         ...
    }
}

// 在IO接口,将输入和输出调用进行数据交换,这样就实现了输入和输出数据的打通
static OSStatus ClinkAudio_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer)
{
    OSStatus theAnswer = 0;
    ...
    // 表现为输入设备,应用从设备读取声音数据
    if(inOperationID == kAudioServerPlugInIOOperationReadInput)
    {
         ...
         memcpy(ioMainBuffer, gRingBuffer + ringBufferFrameLocationStart * kNumber_Of_Channels, firstPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
         memcpy((Float32*)ioMainBuffer + firstPartFrameSize * kNumber_Of_Channels, gRingBuffer, secondPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
    }
    
    // 表现为输出设备,应用(比如系统)将声音数据输出到设备
    if(inOperationID == kAudioServerPlugInIOOperationWriteMix)
    {
        ...
        memcpy(gRingBuffer + ringBufferFrameLocationStart * kNumber_Of_Channels, ioMainBuffer, firstPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
        memcpy(gRingBuffer, (Float32*)ioMainBuffer + firstPartFrameSize * kNumber_Of_Channels, secondPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
    }

    return theAnswer;
}

待完成该设备的创建之后,编译并放到Drivers目录执行,然后把该设备通过MIDI设置为系统默认输出设备。

其次通过AVCaptureDevice读取该设备并将其作为AVCaptureSession的声音输入设备。

AVCaptureDevice *mDevice = NULL;
NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
for (AVCaptureDevice *device in devices) {
    // VIRTUAL_PLAYBACK_DEVICE 是我们设置的虚拟音频设备的名字
    if ([[device localizedName] isEqualToString:@VIRTUAL_PLAYBACK_DEVICE]) {
        mDevice = device;
        break;
    }
}
if (!mDevice) {
    NSLog("virtual audio device(%s) not found", VIRTUAL_PLAYBACK_DEVICE);
    return;
}

至此,我们就可以顺利完成系统声音的录制了。

0条评论
0 / 1000
l****n
14文章数
1粉丝数
l****n
14 文章 | 1 粉丝
原创

[CoreAudio]如何通过虚拟音频设备录制系统声音

2023-05-25 03:26:35
71
0

使用AVCaptureSession录制

首先,我们想要录制声音的话,可以通过AVCaptureSession来实现,AVCaptureSession可以实现摄像头或者声音的录制,这取决于我们将什么作为输入设备(inputDevice),下面是一段使用AVCaptureSession进行录制的示例代码:

// 创建一个AVCaptureSession
AVCaptureSession *session = [AVCaptureSession new];
// 告诉Session要准备开始配置参数
[session beginConfiguration];
    
// 根据设备(mDevice)初始化输入(下文会介绍如何获取mDevice)
NSError *error  = nil;
AVCaptureDeviceInput* input = [[AVCaptureDeviceInput alloc] initWithDevice:mDevice error:&error];
if (!input) {
    NSLog(@"create capture device input: %s", [[error localizedDescription] UTF8String]);
    return;
}
if ([session canAddInput:input]) {
    [session addInput:input];
} else {
    NSLog("session can not add input");
    return;
}
    
// 创建一个输出(决定了使用什么样的产物格式)
AVCaptureAudioDataOutput *output = [AVCaptureAudioDataOutput new];
if ([session canAddOutput:output]) {
    [session addOutput:output];
} else {
    NSLog("session can not add output");
    return;
}

// 配置输出的代理回调(在代理回调里进行数据接收)
OSXAudioReceiver *receiver = [OSXAudioReceiver new];
dispatch_queue_t queue = dispatch_queue_create("osx-playback.receiver", NULL);
[output setSampleBufferDelegate:receiver queue:queue];

// 提交配置结果    
[session commitConfiguration];
// 告诉Session开始录制
[session startRunning];

前文里面的OSXAudioReceiver需要实现Delegate:AVCaptureAudioDataOutputSampleBufferDelegate,这个Delegate只有一个关键接口:

- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CMSampleBufferRef ref = (CMSampleBufferRef)CFRetain(sampleBuffer);
    if (g_async_queue_length(playback->frameQueue) < MAX_AUDIO_QUEUE_LENGTH) {
        g_async_queue_push(playback->frameQueue, ref);
    }
}

至此,就已经完成了一个简单声音录制功能的开发。

接下来说一下AVCaptureDevice,就是前面代码用到的mDevice参数。经常用到的两种CaptureDevice分辨是Video和Audio,前者用于摄像头录制,后者用于声音录制,可以通过如下接口获取:

 NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; // AVMediaTypeVideo

创建一个音频设备捕获系统声音

上面提到了可以使用音频录制设备来录制声音,比如麦克风。

系统的声音肯定无法通过麦克风来录制,因为默认情况下系统声音是直接通过音频输出设备播放的,也就是扬声器或耳机。

因此需要想办法把系统声音重定向到一个音频录制设备,这就是我们需要实现的虚拟音频设备。


// 将设备配置为既能够支持输入,也能够支持输出
static Boolean ClinkAudio_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress)
{
    ....
    switch(inObjectID)
    {
        // 同时支持输入和输出
        case kObjectID_Stream_Input:
        case kObjectID_Stream_Output:
              switch(inAddress->mSelector)
             {
                 ...
                case kAudioObjectPropertyBaseClass:
                    theAnswer = true;
                    break;
            };
            break;
         ...
    }
}

// 在IO接口,将输入和输出调用进行数据交换,这样就实现了输入和输出数据的打通
static OSStatus ClinkAudio_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer)
{
    OSStatus theAnswer = 0;
    ...
    // 表现为输入设备,应用从设备读取声音数据
    if(inOperationID == kAudioServerPlugInIOOperationReadInput)
    {
         ...
         memcpy(ioMainBuffer, gRingBuffer + ringBufferFrameLocationStart * kNumber_Of_Channels, firstPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
         memcpy((Float32*)ioMainBuffer + firstPartFrameSize * kNumber_Of_Channels, gRingBuffer, secondPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
    }
    
    // 表现为输出设备,应用(比如系统)将声音数据输出到设备
    if(inOperationID == kAudioServerPlugInIOOperationWriteMix)
    {
        ...
        memcpy(gRingBuffer + ringBufferFrameLocationStart * kNumber_Of_Channels, ioMainBuffer, firstPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
        memcpy(gRingBuffer, (Float32*)ioMainBuffer + firstPartFrameSize * kNumber_Of_Channels, secondPartFrameSize * kNumber_Of_Channels * sizeof(Float32));
    }

    return theAnswer;
}

待完成该设备的创建之后,编译并放到Drivers目录执行,然后把该设备通过MIDI设置为系统默认输出设备。

其次通过AVCaptureDevice读取该设备并将其作为AVCaptureSession的声音输入设备。

AVCaptureDevice *mDevice = NULL;
NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
for (AVCaptureDevice *device in devices) {
    // VIRTUAL_PLAYBACK_DEVICE 是我们设置的虚拟音频设备的名字
    if ([[device localizedName] isEqualToString:@VIRTUAL_PLAYBACK_DEVICE]) {
        mDevice = device;
        break;
    }
}
if (!mDevice) {
    NSLog("virtual audio device(%s) not found", VIRTUAL_PLAYBACK_DEVICE);
    return;
}

至此,我们就可以顺利完成系统声音的录制了。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0