音视频开发之Android录制音频

音视频开发之Android录制音频

由于FFmpeg一直都没有支持Android平台的音频输入设备API,所以无法使用FFmpeg在Android上录制音频。

Android平台音频录制API

Android上音频输入的API比较繁琐,有多套实现。有Java层的实现AudioRecord,也有native层实现OpenSLES,在 Android O之后又推出了新的native层实现AAudio,并且提供了Oboe库,对OpenSLES和AAudio进行了封装。

下面来详细的介绍下每套API的使用

AudioRecord

AudioRecord是Android平台提供的录制音频的Java层API,使用起来非常简单。通过设置采集参数就可以创建一个AudioRecord对象,调用了start方法后,就可以开始读取数据了,通过read方法将数据读取到指定的缓冲区中,调用stop后就停止采集。

创建AudioRecord
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
29
30
31
32
// 一个音频帧的字节大小 = 声道数 *(位数 /8)
final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8);
// BUFFERS_PER_SECOND = 100,预测一秒钟回调100次
// 那么44100的采样率,每次回调应该给的数据帧个数为4410个
final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND;
// 一次回调给的总数据字节大小 = 一次给的数据帧个数 * 一个数据帧的字节大小
// 创建一个buffer用来接收回调数据,使用堆外内存,在通过JNI传递的时候避免内存拷贝
byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer);

emptyBytes = new byte[byteBuffer.capacity()];
// 避免每次读取数据都需要传递buffer到JNI层,这里提前将buffer的地址保存在JNI层
nativeCacheDirectBufferAddress(byteBuffer, nativeAudioRecord);

final int channelConfig = channelCountToConfiguration(channels);
// 根据音频参数(采样率,声道数,位数)得到系统最小需要的缓冲区大小,创建AudioRecord时设置的缓冲区大小必须大于这个值
int minBufferSize =
AudioRecord.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);

// BUFFER_SIZE_FACTOR = 2
// 缓冲区一般要大一点,所以下面比较了两倍的最小缓冲区和我们自己计算的缓冲区大小
// 也就是说设置的缓冲区大小最小也要是最小缓冲区的两倍。
int bufferSizeInBytes = Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity());
try {
// 根据音频参数创建AudioRecord,audioSource是指音频采集的来源
audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig,
AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
} catch (IllegalArgumentException e) {
return -1;
}
if (audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
return -1;
}
读取数据
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
// 开始后才可以读取数据
audioRecord.startRecording();

// 循环读取数据
while (keepAlive) {
// 将数据读取到之前创建的buffer中,返回读取的字节大小
int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
if (bytesRead == byteBuffer.capacity()) {

// 将读取的字节大小通知给JNI层,因为前面在创建的时候已经将buffer的地址保存在JNI层中,所以这里传递读取的字节大小就行
if (keepAlive) {
nativeDataIsRecorded(bytesRead, nativeAudioRecord);
}
if (audioSamplesReadyCallback != null) {
// 拷贝整个数据回调出去
byte[] data = Arrays.copyOf(byteBuffer.array(), byteBuffer.capacity());
audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
new AudioSamples(audioRecord, data));
}
} else {
// 读取错误
}

}

// 停止后不能再读取数据
audioRecord.stop();
获取设备音频相关信息

通过AudioManager可以获取到当前设备默认的采样率以及最小缓冲区大小

1
2
3
AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String nativeParam = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
nativeParam = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);

AudioSource音频来源
  • DEFAULT:默认为MIC
  • MIC:麦克风
  • VOICE_UPLINK:电话录音上行线路,系统权限不允许第三方app使用
  • VOICE_DOWNLINK:电话录音下行线路,系统权限不允许第三方app使用
  • VOICE_CALL:电话录音上下线路,系统权限不允许第三方app使用
  • CAMCORDER:摄像头的麦克风
  • VOICE_RECOGNITION:语音识别
  • VOICE_COMMUNICATION:网络电话
  • REMOTE_SUBMIX:传输到远程的音频混合流,默认情况下如果用该项录音,本地扬声器或者耳机的声音将会被截走,系统权限不允许第三方app使用

OpenSL-ES

OpenSL-ES是Android平台上提供的高性能音频的API,相对Java层API有着一定的低延迟性。由于是C库,所以使用起来API相对繁琐一些。

配置数据参数
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
SLDataFormat_PCM format;
// PCM数据格式
format.formatType = SL_DATAFORMAT_PCM;
// 声道数
format.numChannels = static_cast<SLuint32>(channels);
// 设置采样率
switch (sample_rate) {
case 8000:
format.samplesPerSec = SL_SAMPLINGRATE_8;
break;
case 16000:
format.samplesPerSec = SL_SAMPLINGRATE_16;
break;
case 22050:
format.samplesPerSec = SL_SAMPLINGRATE_22_05;
break;
case 32000:
format.samplesPerSec = SL_SAMPLINGRATE_32;
break;
case 44100:
format.samplesPerSec = SL_SAMPLINGRATE_44_1;
break;
case 48000:
format.samplesPerSec = SL_SAMPLINGRATE_48;
break;
case 64000:
format.samplesPerSec = SL_SAMPLINGRATE_64;
break;
case 88200:
format.samplesPerSec = SL_SAMPLINGRATE_88_2;
break;
case 96000:
format.samplesPerSec = SL_SAMPLINGRATE_96;
break;
default:
RTC_CHECK(false) << "Unsupported sample rate: " << sample_rate;
break;
}
// 设置位数
format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
// 小端
format.endianness = SL_BYTEORDER_LITTLEENDIAN;
if (format.numChannels == 1) {
format.channelMask = SL_SPEAKER_FRONT_CENTER;
} else if (format.numChannels == 2) {
format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
} else {
RTC_CHECK(false) << "Unsupported number of channels: "
<< format.numChannels;
}
创建引擎

在OpenSLES中,任何API接口对象都要由引擎来创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 设置线程安全模式
const SLEngineOption option[] = {
{SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}};
// 创建引擎接口
SLresult result =
slCreateEngine(engine_object_, 1, option, 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
return nullptr;
}
// 设置引擎实现模式为同步模式
result = engine_object_->Realize(engine_object_, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
return nullptr;
}

// 根据引擎接口获取到隐藏的引擎实现
if ((*engine_object)
->GetInterface(engine_object, SL_IID_ENGINE, &engine_)) {
return false;
}

创建录制器

在OpenSLES中,录制需要通过录制器对象来进行,而数据的获取是通过回调的方式来获取,但是并非将采集的数据放在回调函数的参数中进行传递,回调函数仅仅只是一个通知的意义,真正的数据是在Enqueue函数中传入的buffer中。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 设置IO设备
SLDataLocator_IODevice mic_locator = {SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
// 设置数据源
SLDataSource audio_source = {&mic_locator, NULL};

// 设置缓冲队列大小
SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
static_cast<SLuint32>(kNumOfOpenSLESBuffers)};
// 设置数据接收器的缓冲队列和数据参数
SLDataSink audio_sink = {&buffer_queue, &pcm_format_};

//
const SLInterfaceID interface_id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
SL_IID_ANDROIDCONFIGURATION};
const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
// 通过引擎实现创建录制器接口
if ((*engine_)->CreateAudioRecorder(
engine_, recorder_object_, &audio_source, &audio_sink,
arraysize(interface_id), interface_id, interface_required)) {
return false;
}

// 通过录制器接口创建录制器配置实现,用来设置相关录制配置参数
SLAndroidConfigurationItf recorder_config;
if ((recorder_object_->GetInterface(recorder_object_,
SL_IID_ANDROIDCONFIGURATION,
&recorder_config))) {
return false;
}
// 设置音频采集源
SLint32 stream_type = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
if (((*recorder_config)
->SetConfiguration(recorder_config,
SL_ANDROID_KEY_RECORDING_PRESET,
&stream_type, sizeof(SLint32)))) {
return false;
}

// 设置录制器实现模式为同步模式
if ((recorder_object_->Realize(recorder_object_,
SL_BOOLEAN_FALSE))) {
return false;
}

// 通过录制器接口创建录制器实现,用来采集数据
if ((recorder_object_->GetInterface(
recorder_object_, SL_IID_RECORD, &recorder_))) {
return false;
}

// 通过录制器接口创建缓冲队列实现,用来传递数据
if ((recorder_object_->GetInterface(
recorder_object_.Get(), SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&simple_buffer_queue_))) {
return false;
}

// 注册在缓冲队列上的回调,通过回调来获取到数据,SimpleBufferQueueCallback就是回调函数,this是回调参数的context参数
if (((*simple_buffer_queue_)
->RegisterCallback(simple_buffer_queue_,
SimpleBufferQueueCallback, this))) {
return false;
}

采集数据

每调用一次Enqueue函数都会触发一次数据回调,如果需要循环采集,则需要在回调函数中递归调用Enqueue函数。

1
2
3
4
5
6
7
8
9
10
11
// 每次调用Enqueue方法设置一个buffer入队,数据就会被采集到该buffer中,然后通过回调函数通知,在回调函数中才该buffer中获取对应数据
// 第一个参数就是缓冲队列实现对象,第二个参数就是buffer,第三个参数是采集的数据字节大小
SLresult err =
(*simple_buffer_queue_)
->Enqueue(
simple_buffer_queue_,
reinterpret_cast<SLint8*>(audio_buffers_[buffer_index_].get()),
audio_parameters_.GetBytesPerBuffer());
if (SL_RESULT_SUCCESS != err) {
return false;
}
1
2
3
4
5
6
7
8
9
void OpenSLESRecorder::SimpleBufferQueueCallback(
SLAndroidSimpleBufferQueueItf buffer_queue,
void* context) {
// 数据回调函数,context就是在设置回调时设置的this
OpenSLESRecorder* stream = static_cast<OpenSLESRecorder*>(context);
// 接收到数据采集成功回调,从缓冲队列中读取数据
stream->ReadBufferQueue();
// Enqueue()
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×