Android播放音频

SDL是一个跨平台的音视频渲染库,是支持Android平台的,所以可以直接使用SDL库进行音频的播放。

但是SDL库在Android平台上的实现只有一种,就是通过JNI来调用Java层的AudioTrack来进行播放。所以如果你有其它的需求不想使用SDL库,那么可以直接使用Android平台原生API来播放音频。

Android平台音频播放API

前面录制的时候讲过,Android上音频输出的API比较繁琐,有多套实现。有Java层的实现AudioTrack,也有native层实现OpenSLES,在 Android O上又推出了新的native层实现AAudio,并且提供了Oboe库,对OpenSLES和AAudio进行了封装。

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

AudioTrack

AudioTrack是Android平台提供的播放音频的Java层API,使用起来非常简单。通过设置待播放音频参数就可以创建一个AudioTrack对象,调用了play方法后,就可以开始写入数据了,通过write方法将数据写入,调用stop后就停止播放。

需要注意的是,在创建AudioTrack的时候,对于待播放的音频数据格式都已经设定好了,也就是说这个创建的AudioTrack只能播放这种格式的音频数据。所以一般在获取到音频流解码后,都需要将PCM数据进行重采样,重采样成和AudioTrack一样的数据格式,才能交给AudioTrack播放。

创建AudioTrack

AudioTrack的构造函数有两套,新的一套是在Android L推出的,老的一套已经被标记为deprecated。

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
// 根据指定采样率、声道数、位数获取最小需要的缓冲区大小,后续再创建AudioTrack时设置的缓冲区大小必须大于这个值
// 如果想要将缓冲区设大一点,比如 bufferSizeFactor = 1.5
final int minBufferSizeInBytes = (int) (AudioTrack.getMinBufferSize(sampleRate, channelConfig,
AudioFormat.ENCODING_PCM_16BIT) * bufferSizeFactor);

// 一个音频帧的字节大小 = 声道数 *(位数 /8)
final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8);

// 创建buffer用来存放每一次要写入的数据,使用堆外内存,避免JNI内存拷贝
// BUFFERS_PER_SECOND = 100,预测一秒钟回调100次
// 那么44100的采样率,每次回调应该给的数据帧个数为4410个
byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * (sampleRate / BUFFERS_PER_SECOND));

AudioTrack audioTrack;
if (Build.VERSION.SDK_INT >= 21) {
// AudioFormat是对音频的格式进行封装,包括采样率、声道数、位数等。
// AudioAttributes是对播放内容的描述
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
// 音频的用途
.setUsage(usageAttribute)
// 音频内容的类型
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
new AudioFormat.Builder()
// 位数
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
// 采样率
.setSampleRate(sampleRateInHz)
// 声道数
.setChannelMask(channelConfig)
.build(),
bufferSizeInBytes,// 缓冲区大小
AudioTrack.MODE_STREAM,// 模式
// 生成新的会话ID
AudioManager.AUDIO_SESSION_ID_GENERATE );


} else {
// 构造参数:
// 音频流类型
// 采样率
// 声道数
// 位数
// 缓冲区大小
// 模式 : MODE_STATIC 预先将需要播放的音频数据读取到内存中,然后才开始播放。MODE_STREAM 边读边播,不会将数据直接加载到内存
audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, sampleRateInHz, channelConfig,
AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes, AudioTrack.MODE_STREAM);

}

if (audioTrack == null || audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
// 创建失败
}

播放模式

AudioTrack支持两种模式,MODE_STATIC和 MODE_STREAM

  • MODE_STREAM : 预先将需要播放的音频数据读取到内存中,所以如果使用这种模式,那么在创建AudioTrack的时候,传入的缓冲区大小就必须是整个播放文件的大小,AudioTrack只会播放这么多数据。
  • MODE_STREAM : 边读边播,不会将数据直接加载到内存,如果使用这种模式,需要注意每次写入的数据大小,需要确保小于或者等于创建时传入的缓冲区大小。一般使用这种模式。

音频流类型

  • AudioManager.STREAM_VOICE_CALL : 电话
  • AudioManager.STREAM_SYSTEM : 系统
  • AudioManager.STREAM_RING : 响铃和消息
  • AudioManager.STREAM_MUSIC : 音乐
  • AudioManager.STREAM_ALARM : 闹钟
  • AudioManager.STREAM_NOTIFICATION : 通知
  • AudioManager.STREAM_BLUETOOTH_SCO : 蓝牙
  • AudioManager.STREAM_SYSTEM_ENFORCED : 强制系统声音
  • AudioManager.STREAM_DTMF : 双音多频
  • AudioManager.STREAM_TTS : 语音

写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 开始播放
audioTrack.play();

// 写入数据,使用阻塞模式WRITE_BLOCKING,如果缓冲区满了,则阻塞等待
if (Build.VERSION.SDK_INT >= 21) {
// 使用Buffer来传递到Native层,buffer创建时分配堆外内存,这样在JNI传递时避免了内存拷贝
audioTrack.write(byteBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING);
} else {
// 使用byte array来传递到Native层,需要内存拷贝
audioTrack.write(byteBuffer.array(), byteBuffer.arrayOffset(), sizeInBytes);
}

// 停止播放
audioTrack.stop();

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
52
53
54
  
// 创建播放数据格式
SLDataFormat_PCM format;
// 数据格式
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
21
22
// 设置线程安全模式
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中,播放需要通过播放器对象来进行,

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
// 通过引擎实现创建混音器接口
(*engine_)->CreateOutputMix(engine_, output_mix_, 0, nullptr, nullptr);

// 设置混音器实现模式为同步模式
output_mix_->Realize(output_mix_.Get(), SL_BOOLEAN_FALSE);

// 设置缓冲队列大小
SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
static_cast<SLuint32>(kNumOfOpenSLESBuffers)};
// 设置数据源的缓冲队列和数据格式
SLDataSource audio_source = {&simple_buffer_queue, &pcm_format_};

// 设置输出混音器
SLDataLocator_OutputMix locator_output_mix = {SL_DATALOCATOR_OUTPUTMIX,
output_mix_};
// 设置数据接收器的混音器
SLDataSink audio_sink = {&locator_output_mix, nullptr};

//
const SLInterfaceID interface_ids[] = {SL_IID_ANDROIDCONFIGURATION,
SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE};

// 通过引擎实现创建播放器接口
(*engine_)->CreateAudioPlayer(
engine_, player_object_, &audio_source, &audio_sink,
arraysize(interface_ids), interface_ids, interface_required);

// 通过播放器接口创建播放器配置实现,用来设置相关播放配置参数
SLAndroidConfigurationItf player_config;
player_object_->GetInterface(player_object_,
SL_IID_ANDROIDCONFIGURATION, &player_config);
// 设置音频流类型
SLint32 stream_type = SL_ANDROID_STREAM_VOICE;
// 设置参数
(*player_config)->SetConfiguration(player_config, SL_ANDROID_KEY_STREAM_TYPE,
&stream_type, sizeof(SLint32));

// 设置播放器实现模式为同步模式
player_object_->Realize(player_object_, SL_BOOLEAN_FALSE);

// 通过播放器接口创建播放器实现,用来播放数据
player_object_->GetInterface(player_object_, SL_IID_PLAY, &player_);

// 通过播放器接口创建缓冲队列实现,用来传递数据
player_object_->GetInterface(player_object_, SL_IID_BUFFERQUEUE,
&simple_buffer_queue_);

// 注册在缓冲队列上的回调,通过回调来通知写入数据,SimpleBufferQueueCallback就是回调函数,this是回调参数的context参数
// 当回调触发,则说明播放器需要数据,将数据写入到缓冲队列中即可
(*simple_buffer_queue_)->RegisterCallback(simple_buffer_queue_,
SimpleBufferQueueCallback, this);

// 通过播放器接口创建音量实现,用来控制音量
player_object_->GetInterface(player_object_, SL_IID_VOLUME, &volume_);

写入数据

每次回调函数被触发,则说明播放器需要数据,通过调用Enqueue函数将数据写入到缓冲队列中

1
2
3
4
5
6
7
8
9
void OpenSLESPlayer::SimpleBufferQueueCallback(
SLAndroidSimpleBufferQueueItf caller,
void* context) {
// 数据回调函数,context就是在设置回调时设置的this
OpenSLESPlayer* stream = reinterpret_cast<OpenSLESPlayer*>(context);
// 写入数据,Enqueue(缓冲队列,buffer数据,数据size)
SLresult err = (*simple_buffer_queue_)->Enqueue(simple_buffer_queue_, audio_ptr8,
audio_parameters_.GetBytesPerBuffer());
}