最近协助处理一个OpenSL的问题。问题背景和描述如下:
我司的安卓电视需要使用OpenSL从语音遥控器上采集音频,而语音遥控器有两种协议:2.4G和蓝牙。
在2.4G遥控上功能正常,但是使用蓝牙遥控器,没有任何报错,流程也正常跑通,但是读取到的音频数据全部是空数据。
其实OpenSL我之前也只是简单入门而已(笔记在这里 ),首先想到的是去修改它的设备类型
1 2 3 pHelper->device.locatorType = SL_DATALOCATOR_IODEVICE; pHelper->device.deviceType = SL_IODEVICE_AUDIOINPUT; pHelper->device.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
但是查看了下locatorType、deviceType、deviceID可以选择的选项,发现并没有适合的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** Data locator */ #define SL_DATALOCATOR_URI ((SLuint32) 0x00000001) #define SL_DATALOCATOR_ADDRESS ((SLuint32) 0x00000002) #define SL_DATALOCATOR_IODEVICE ((SLuint32) 0x00000003) #define SL_DATALOCATOR_OUTPUTMIX ((SLuint32) 0x00000004) #define SL_DATALOCATOR_RESERVED5 ((SLuint32) 0x00000005) #define SL_DATALOCATOR_BUFFERQUEUE ((SLuint32) 0x00000006) #define SL_DATALOCATOR_MIDIBUFFERQUEUE ((SLuint32) 0x00000007) #define SL_DATALOCATOR_RESERVED8 ((SLuint32) 0x00000008) /** IODevice-types */ #define SL_IODEVICE_AUDIOINPUT ((SLuint32) 0x00000001) #define SL_IODEVICE_LEDARRAY ((SLuint32) 0x00000002) #define SL_IODEVICE_VIBRA ((SLuint32) 0x00000003) #define SL_IODEVICE_RESERVED4 ((SLuint32) 0x00000004) #define SL_IODEVICE_RESERVED5 ((SLuint32) 0x00000005) /** Device ids */ #define SL_DEFAULTDEVICEID_AUDIOINPUT ((SLuint32) 0xFFFFFFFF) #define SL_DEFAULTDEVICEID_AUDIOOUTPUT ((SLuint32) 0xFFFFFFFE) #define SL_DEFAULTDEVICEID_LED ((SLuint32) 0xFFFFFFFD) #define SL_DEFAULTDEVICEID_VIBRA ((SLuint32) 0xFFFFFFFC) #define SL_DEFAULTDEVICEID_RESERVED1 ((SLuint32) 0xFFFFFFFB)
其实以前在用java层的AudioRecorder的时候就遇到过获取不到蓝牙语音遥控器音频的问题,当时是通过将audioSource设置成MediaRecorder.AudioSource.CAMCORDER解决的.
所以猜测OpenSL里面大概也是这样处理,在OpenSLES_AndroidConfiguration.h下找到类似的宏定义:
1 2 3 /** uses the microphone audio source with the same orientation as the camera * if available, the main device microphone otherwise */ #define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002)
这个宏用于指定音频设备的预设值。也就是说需要改变的不是音频设备的类型,依然使用SL_DEFAULTDEVICEID_AUDIOINPUT这个设备去读取音频,只不过将它的配置改成SL_ANDROID_RECORDING_PRESET_CAMCORDER让这个AUDIOINPUT设备读取的是摄像机方向的麦克风数据。
那要怎么修改这个预设值呢?
从安卓开发者文档 找到了播放器的设置方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 // CreateAudioPlayer and specify SL_IID_ANDROIDCONFIGURATION // in the required interface ID array. Do not realize player yet. // ... SLAndroidConfigurationItf playerConfig; result = (*playerObject)->GetInterface(playerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig); assert(SL_RESULT_SUCCESS == result); SLint32 streamType = SL_ANDROID_STREAM_ALARM; result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); assert(SL_RESULT_SUCCESS == result); // ... // Now realize the player here.
其实录音的方法也是类似的,通过SLAndroidConfigurationItf的SetConfiguration方法去配置。不过文档其实默认读者都是了解OpenSL的使用方法的了,所以还有些必要的代码其实没有展示出来。
要从playerObject里面用GetInterface获取到SLAndroidConfigurationItf,必须在CreateAudioPlayer的时候就将SLAndroidConfigurationItf的id传入,这样playerObject才会有SLAndroidConfigurationItf这个接口。要不然GetInterface会获取失败。
这里我就直接将录音代码展示出来了,注意看我注释的那两行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 SLObjectItf recorderObject; SLInterfaceID id[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION // 需要指定AudioRecorder包含SLAndroidConfigurationItf }; SLboolean required[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE // 指定SL_IID_ANDROIDCONFIGURATION是必须包含的 }; (*engineInterface)->CreateAudioRecorder( engineInterface, &(recorderObject), &(source), &(sink), 2, id, required ); SLAndroidConfigurationItf configItf; (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDCONFIGURATION, (void*)&configItf); SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_CAMCORDER; (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_RECORDING_PRESET, &presetValue, sizeof(SLuint32)); (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
顺嘴说一句,如果我们不设置SL_ANDROID_RECORDING_PRESET_CAMCORDER,它默认是用SL_ANDROID_RECORDING_PRESET_GENERIC。
所有的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** 配置中“无”不能设置,它是用来表示当前设置不匹配任何配置 */ #define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000) /** 平台上通用的录像配置 */ #define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001) /** 如果有的话使用具有相同的方向与摄像机的麦克风的音频源,否则使用过主麦克风 */ #define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002) /** 使用为语音识别优化过的主要麦克风 */ #define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003) /** 使用为音频通信优化过的主要麦克风 */ #define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) /** 使用未经处理的主麦克风 */ #define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005)
其它坑 然后除了上面的SL_IID_ANDROIDCONFIGURATION之外,其实我这边还遇到了几个坑,这里也记录一下
由于代码里面一直用16000Hz的采样率,在2.4G语音遥控器上也工作的好好的,于是适配蓝牙语音遥控之后就没有修改它,然后发现读取直接阻塞住了,读取音频数据的回调一直没有被调用(如果不设置SL_ANDROID_RECORDING_PRESET_CAMCORDER的时候虽然也拿不到音频数据,但是回调是会调用的,只不过拿到的数据都是空数据)。之后改成了和硬件一致的44100Hz之后就正常了
SetConfiguration必须在Realize前面调用
其实这块并不是我司代码的问题,我们的项目接入了亚马逊的Alexa sdk,它对OpenSL进行了一层封装,它在创建AudioRecorder之后顺手就Realize了,但是它原本也是有提供封装好的SetConfiguration的配置的,于是我们就直接在上面改了。
于是就一直没有效果。最后还是在分析log的时候发现调用SetConfiguration的时候底层输出了“Realize已经调用”的打印才找到问题所在。
这里忍不住吐槽一下,亚马逊写这块的人明显对OpenSL也不是很熟悉,除了这个问题之外我们还发现了不少其他的低级问题。看来亚马逊这种大公司的代码审查也做的不怎么样嘛……