实时音视频的那些事儿(二)—— 音频采集


前言

实时音频采集作为音频存储、处理的基础,无论在直播、通讯、音乐制作等领域中都扮演着重要的角色。通过选择合适的麦克风、采样率以及压缩算法等技术手段,可以达到高质量音频采集的目标。今天就详细的介绍一下各个平台的音频采集。


一、iOS端的音频采集有两种方式,一种是使用AVFoundation提供的AVAudioRecorder类进行录音,另一种是使用Core Audio框架中的Audio Unit实现音频采集。

下面详细介绍一下基于Core Audio框架的音频采集实现。

// 建立Audio Session 
AVAudioSession *session = [AVAudioSession sharedInstance]; 
[session setCategory:AVAudioSessionCategoryPlayback error:nil]; 
[session setMode:AVAudioSessionModeMeasurement error:nil]; 
[session setActive:YES error:nil]; 
// 创建Audio Unit对象 
AudioComponentDescription desc; 
desc.componentType = kAudioUnitType_Output; 
desc.componentSubType = kAudioUnitSubType_RemoteIO; 
desc.componentManufacturer = kAudioUnitManufacturer_Apple; 
desc.componentFlags = 0; 
desc.componentFlagsMask = 0; 
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc); 
AudioUnit audioUnit; 
AudioComponentInstanceNew(inputComponent, &audioUnit); 
// 创建Audio Unit后需要对其进行配置,包括格式、采样率、音频输入和输出回调等参数的设置 
UInt32 flag = 1; 
AudioUnitSetProperty(
    audioUnit, 
    kAudioOutputUnitProperty_EnableIO, 
    kAudioUnitScope_Input, 
    1, 
    &flag, 
    sizeof(flag)
); 
AudioStreamBasicDescription audioFormat; 
audioFormat.mSampleRate = 44100; 
audioFormat.mFormatID = kAudioFormatLinearPCM; 
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
audioFormat.mFramesPerPacket = 1; 
audioFormat.mChannelsPerFrame = 2; 
audioFormat.mBytesPerFrame = 4; 
audioFormat.mBytesPerPacket = 4; 
audioFormat.mBitsPerChannel = 16; 
AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat)); 
 
AURenderCallbackStruct callback; 
callback.inputProc = audioInputCallback; 
callback.inputProcRefCon = (__bridge void *)(self); AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback)); 
// 启动Audio 
Unit AudioOutputUnitStart(audioUnit); 
// audioInputCallback函数为数据回调函数,在每次有新的音频数据时会被调用。可以在这个函数中获取音频数据,进行后续处理或传输。  
static OSStatus audioInputCallback(
    void *inRefCon, 
    AudioUnitRenderActionFlags *ioActionFlags, 
    const AudioTimeStamp *inTimeStamp, 
    UInt32 inBusNumber, 
    UInt32 inNumberFrames, 
    AudioBufferList *ioData) { 
        AudioBuffer buffer; 
        buffer.mNumberChannels = 2; 
        buffer.mDataByteSize = inNumberFrames * 4; 
        buffer.mData = malloc(inNumberFrames * 4); 
        AudioBufferList bufferList; bufferList.mNumberBuffers = 1; 
        bufferList.mBuffers[0] = buffer; 
        AudioUnitRender(audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList); // 获取采集到的音频数据,进行后续处理或传输 
        free(bufferList.mBuffers[0].mData); return noErr;
 })

总的来说,iOS端的音频采集可以基于AVFoundation提供的AVAudioRecorder类进行简单的录音操作,也可以使用Core Audio框架中的Audio Unit实现低延时、高质量的音频采集和处理,具体实现方式需要根据应用场景和需求进行选择。

二、Android平台上实现音频采集

大致可以分为以下几个步骤:

1. 获取音频设备 在Android的Audio系统中,音频设备是通过AudioManager来获取的。可以通过AudioManager的getDevices() 方法获取所有音频设备列表,或者通过getDevicesForStream() 方法获取指定音频流对应的音频设备列表。

2. 配置音频采集参数 根据需求配置音频采集参数,包括采样频率、采样位数、声道数等。可以通过AudioRecord类来实现配置。AudioRecord是Android平台上的录音类,它可以通过构造函数来传入采集参数,例如:

int audioSource = MediaRecorder.AudioSource.MIC; 
int sampleRateInHz = 44100; 
int channelConfig = AudioFormat.CHANNEL_IN_MONO; 
int audioFormat = AudioFormat.ENCODING_PCM_16BIT; 
int bufferSizeInBytes = AudioRecord.getMinBufferSize(
                                                         sampleRateInHz, 
                                                         channelConfig, 
                                                         audioFormat
                                                      ); 
AudioRecord audioRecord = new AudioRecord(
                                                audioSource, 
                                                sampleRateInHz, 
                                                channelConfig, 
                                                audioFormat, 
                                                bufferSizeInBytes
                                            ); 

3. 采集音频数据 调用AudioRecord的 read()方法来采集音频数据。read()方法有多个重载,在本文中我们使用其中一个比较简单的重载,每次读取一定长度的数据,并将其存储到一个字节数组中。例如:

byte[] audioData = new byte[blockSizeInBytes]; 
audioRecord.read(audioData, 0, blockSizeInBytes); 

需要注意的是,这里采集到的是PCM格式的音频数据,需要根据采集参数进行相应的解析。

4. 停止音频采集 在不需要采集音频数据时,需要调用AudioRecord的stop() 方法及release() 方法来停止音频采集并释放相应的资源。例如:

audioRecord.stop(); 
audioRecord.release();

以上就是Android平台上实现音频采集的简单介绍。需要注意的是,音频采集过程中需要申请权限,并且考虑到设备的不同,配置参数和实现方式会有所不同。

三、Windows端音频采集

在Windows平台上进行音频采集需要使用Windows Multimedia API (MME)或者Windows Core Audio API。这里我们以Windows Core Audio API为例,介绍一下Windows端音频采集的代码实现。

1. 初始化设备 初始化设备需要使用MMDevice API,首先需要创建一个IMMDeviceEnumerator接口,并使用GetDefaultAudioEndpoint()方法获取默认音频输入设备。

IMMDeviceEnumerator* deviceEnumerator = nullptr; 
IMMDevice* audioDevice = nullptr; 
HRESULT hr = CoCreateInstance( __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&deviceEnumerator) ); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: CoCreateInstance failed.\n"); 
    return EXIT_FAILURE; 
} 
hr = deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &audioDevice); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: GetDefaultAudioEndpoint failed.\n"); 
    deviceEnumerator->Release(); 
    return EXIT_FAILURE; 
} 
deviceEnumerator->Release();

2. 获取音频格式 接着需要获取音频输入设备当前支持的音频格式。我们可以使用 IAudioClient 接口来获取它所支持的格式,包括采样率、位深、通道数等。

IAudioClient* audioClient = nullptr; 
IAudioCaptureClient* captureClient = nullptr; 
WAVEFORMATEX* pwfx = nullptr; 
hr = audioDevice->Activate( __uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&audioClient) ); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: Activate failed.\n"); 
    audioDevice->Release(); 
    return EXIT_FAILURE; 
} 
// Get the default device format. 
hr = audioClient->GetMixFormat(&pwfx); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: GetMixFormat failed.\n"); 
    audioClient->Release(); 
    audioDevice->Release(); 
    return EXIT_FAILURE; 
} 
// Ensure the format support. 
hr = audioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, REFTIMES_PER_SEC, 0, pwfx, nullptr ); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: Initialize failed.\n"); 
    audioClient->Release(); 
    audioDevice->Release(); 
    CoTaskMemFree(pwfx); 
    return EXIT_FAILURE; 
} 
UINT32 bufferFrameCount = 0; 
hr = audioClient->GetBufferSize(&bufferFrameCount); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: GetBufferSize failed.\n"); 
    audioClient->Release(); 
    audioDevice->Release(); 
    CoTaskMemFree(pwfx); 
    return EXIT_FAILURE; 
}

3. 创建采样线程 在初始化完设备后,我们需要创建一个子线程并在其中不断地循环读取数据。这里我们使用IAudioCaptureClient接口的GetBuffer() 方法来获取音频数据,并使用ReleaseBuffer() 方法通知系统该缓冲区已满。

DWORD taskThreadId; 
HANDLE taskThreadHandle = CreateThread( nullptr, 0, AudioCaptureThread, reinterpret_cast<void*>(audioClient), 0, &taskThreadId ); 
if (taskThreadHandle == nullptr) { 
    fprintf(stderr, "ERROR: CreateThread failed.\n"); 
    audioClient->Release(); 
    audioDevice->Release(); 
    CoTaskMemFree(pwfx); 
    return EXIT_FAILURE; 
}

在子线程中,我们循环读取采样缓冲区中的音频数据:

DWORD WINAPI AudioCaptureThread(LPVOID lpParameter) { 
    IAudioClient* audioClient = reinterpret_cast<IAudioClient*>(lpParameter); 
    IAudioCaptureClient* captureClient = nullptr; 
    WAVEFORMATEX* pwfx = nullptr; 
    UINT32 bufferFrameCount = 0; 
    DWORD flags = 0; 
    BYTE* pData = nullptr; 
    DWORD n = 0; 
    // Get the capture client. HRESULT 
    hr = audioClient->GetService( __uuidof(IAudioCaptureClient), reinterpret_cast<void**>(&captureClient) ); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ERROR: GetService failed.\n"); 
        return EXIT_FAILURE; 
    } 
    // Get the buffer size. 
    hr = audioClient->GetBufferSize(&bufferFrameCount); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ERROR: GetBufferSize failed.\n"); 
        captureClient->Release(); 
        return EXIT_FAILURE; 
    } 
    // Get the default device format. 
    hr = audioClient->GetMixFormat(&pwfx); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ERROR: GetMixFormat failed.\n"); 
        captureClient->Release(); 
        return EXIT_FAILURE; 
    } 
    // Start capturing. 
    hr = audioClient->Start(); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ERROR: Start failed.\n"); 
        captureClient->Release(); 
        return EXIT_FAILURE; 
     } 
    // Capture loop. 
    while (g_audioCapturing) { 
        // Get the next packet. 
        hr = captureClient->GetBuffer(&pData, &n, &flags, nullptr, nullptr); 
        if (FAILED(hr)) { 
            fprintf(stderr, "ERROR: GetBuffer failed.\n"); 
            captureClient->Release(); 
            return EXIT_FAILURE; 
        } 
        if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { 
            memset(pData, 0, n); 
        } 
        // Do something with the audio data. 
        // ... 
        // Release the packet. 
        hr = captureClient->ReleaseBuffer(n); 
        if (FAILED(hr)) { 
            fprintf(stderr, "ERROR: ReleaseBuffer failed.\n"); 
            captureClient->Release(); 
            return EXIT_FAILURE; 
        } 
    } // Stop capturing. 
    hr = audioClient->Stop(); 
    if (FAILED(hr)) { 
        fprintf(stderr, "ERROR: Stop failed.\n"); 
        captureClient->Release(); 
        return EXIT_FAILURE; 
    } 
    captureClient->Release(); 
    return EXIT_SUCCESS; 
}

4. 停止采集 最后,在程序退出时需要调用IAudioClient接口的 Stop() 方法来停止采集,并释放资源。

// Stop capturing. 
g_audioCapturing = false; 
WaitForSingleObject(taskThreadHandle, INFINITE); 
HRESULT hr = audioClient->Stop(); 
if (FAILED(hr)) { 
    fprintf(stderr, "ERROR: Stop failed.\n");
} 
audioClient->Release(); 
audioDevice->Release(); 
CoTaskMemFree(pwfx); 

实现window端的音频采集,除了 Win32 API 之外,还可以使用第三方库进行音频采集。目前比较流行的音频采集库包括 OpenAL、ASIO、PortAudio 等。

至此,我们分别在iOS、Android、Windows平台实现是了音频采集,有关音频相关的常用参数可以查看 实时音视频的那些事儿(一)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值