Android 音频框架 --- APPLICATION->JNI
摘要
在该章节,主要解释 Android 官方提供的的音频框架图,以及针对 APPLICATION 的应用逻辑进行讲解--- 以 AudioTrack 为例。
在官网资料中, 有提供 Android 的音频框架图,这里先解读一下图中的含义。

1. APPLICATION
这里提供一个播放(track)的demo 来展示应用播放的流程,来帮助更好的理解这个流程框图。(完整的代码放到文件的末尾, 只是用来演示播放流程,不一定能跑通哦).
1.1 提取音轨, 解码
AudioTrack 只能够播放原始数据 也就是 pcm 数据. 文件的后缀通常为 .pcm.
而我们平常遇到的 .mp3 通常是经过封装的数据格式。因此,如果要将数据送入 AudioTrack 进行播放,那么就必须对数据进行提取解码。所以一般在播放的第一步为 提取音轨, 解码。
java
// 提取音频轨道
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(filePath);
// 创建解码器
codec = MediaCodec.createDecoderByType(mime);
codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); // 解码器输入待解码数据
int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, timeoutUs); // 获取解码后的输出缓冲区
在提取完音频数据之后需要对数据进行解码, 解析成 AudioTrack 能够播放的音频数据类型。
1.2 创建 AudioTrack.java
该组件为一个通用的应用层播放组件, 与其具有相似功能的还有MediaPlayer, 但他们最终都会调用到 framework 中的 AudioTrack.cpp.
java
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // StreamType
sampleRate, // 采样率
channelConfig, // 通道配置
AudioFormat.ENCODING_PCM_16BIT, // 数据格式, 位深
minBufferSize, // 最小buffeSize
AudioTrack.MODE_STREAM); // MODE_STREAM (一般用这个其他暂不做详细解释, 见下面的解释).
- MODE_STATIC 预先将需要播放的音频数据读取到内存中,然后才开始播放。
- MODE_STREAM 边读边播,不会将数据直接加载到内存
1.3 播放
目前我们做了几件事情, 提取文件的音频轨道、对音轨的数据进行解码为原始数据格式。那么下一步就是播放。播放的流程很简单,就是向 Track 中不断的写数据.
java
audioTrack.play(); // 启动AudioTrack,开始播放音频, 其中会触发一系列的 AudioFramework 框架的流程,这里暂不细究.
// 当文件没有播放完一直向其中写数据。
while (!isEOS) {
audioTrack.write(buffer, 0, buffer.length);
}
1.4 停止
java
codec.stop(); // 停止解码器
codec.release(); // 释放解码器资源
extractor.release(); // 释放MediaExtractor资源
audioTrack.stop(); // 停止AudioTrack
audioTrack.release(); // 释放AudioTrack资源
2. 调用关系
在上面的代码中,我们看到了 APPLICATION 的代码, 以及看到了 APPLICATION 是调用 JNI 的代码,但是对比 Android 官网的架构图中,它具体调用的是哪些 JNI 的方法呢?这里也将其贴出来。这里只贴和AudioTrack 相关的逻辑,提取音频轨道和解码不在该音频框架中
java
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
AudioFormat.ENCODING_PCM_16BIT,
minBufferSize,
AudioTrack.MODE_STREAM); // 创建AudioTrack对象
| |
| | 调用 JNI
| |
\ /
cpp
...
{"native_setup",
"(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[ILandroid/os/Parcel;"
"JZILjava/lang/Object;Ljava/lang/String;)I",
(void *)android_media_AudioTrack_setup},
...
也就是上层在创建 AudioTrack 的时候会调用到 JNI 的 android_media_AudioTrack_setup 方法,具体的调用逻辑这里不做赘述。感兴趣可自行先看,具体的逻辑放到后面的章节说明。
小结
这里简单介绍了 Android 音频框架图,以AudioTrack 为例,举例说明 APPLICATION 是如何调用 JNI 方法。
下一章,将说明 JNI 到 libmeida 的具体调用。
demo 代码
java
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class AudioTrackDemo {
private static final String TAG = "AudioTrackDemo";
public void playMp3(String filePath) {
MediaExtractor extractor = new MediaExtractor(); // 创建MediaExtractor,用于提取媒体数据
try {
extractor.setDataSource(filePath); // 设置数据源为要播放的MP3文件路径
} catch (IOException e) {
Log.e(TAG, "Error setting data source", e); // 如果发生错误,打印错误信息
return; // 出现异常时,直接返回
}
int audioTrackIndex = -1; // 初始化音频轨道索引
MediaFormat format = null; // 初始化媒体格式
// 遍历媒体文件中的轨道,找到音频轨道
for (int i = 0; i < extractor.getTrackCount(); i++) {
format = extractor.getTrackFormat(i); // 获取轨道格式
String mime = format.getString(MediaFormat.KEY_MIME); // 获取MIME类型
if (mime.startsWith("audio/")) { // 如果MIME类型以"audio/"开头,表示这是音频轨道
audioTrackIndex = i; // 记录音频轨道的索引
break; // 找到音频轨道后跳出循环
}
}
if (audioTrackIndex == -1) {
Log.e(TAG, "No audio track found in file."); // 如果没有找到音频轨道,打印错误信息
return; // 没有音频轨道时,直接返回
}
extractor.selectTrack(audioTrackIndex); // 选择音频轨道进行解码
String mime = format.getString(MediaFormat.KEY_MIME); // 获取音频轨道的MIME类型
MediaCodec codec;
try {
codec = MediaCodec.createDecoderByType(mime); // 创建解码器,用于解码音频数据
} catch (IOException e) {
Log.e(TAG, "Error creating codec", e); // 如果解码器创建失败,打印错误信息
return; // 发生异常时,直接返回
}
codec.configure(format, null, null, 0); // 配置解码器,设置解码格式
codec.start(); // 启动解码器
ByteBuffer[] inputBuffers = codec.getInputBuffers(); // 获取输入缓冲区,用于存放解码前的数据
ByteBuffer[] outputBuffers = codec.getOutputBuffers(); // 获取输出缓冲区,用于存放解码后的PCM数据
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // 创建BufferInfo对象,用于描述解码后的数据
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); // 获取音频的采样率
int channelConfig = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ?
AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO; // 根据声道数量设置音频输出格式(单声道或立体声)
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,
channelConfig, AudioFormat.ENCODING_PCM_16BIT); // 计算AudioTrack所需的最小缓冲区大小
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
AudioFormat.ENCODING_PCM_16BIT,
minBufferSize,
AudioTrack.MODE_STREAM); // 创建AudioTrack对象
audioTrack.play(); // 启动AudioTrack,开始播放音频
boolean isEOS = false; // 标记是否到达文件末尾
long timeoutUs = 10000; // 解码超时时间,单位为微秒
while (!isEOS) {
int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs); // 从解码器获取可用的输入缓冲区
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; // 获取对应的输入缓冲区
int sampleSize = extractor.readSampleData(inputBuffer, 0); // 从媒体文件中读取一帧数据到输入缓冲区
if (sampleSize < 0) {
// 如果没有更多数据,表示文件结束
codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); // 向解码器发送结束标志
isEOS = true; // 设置结束标志为true
} else {
codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0); // 将读取的数据送入解码器
extractor.advance(); // 提取下一帧数据
}
}
int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, timeoutUs); // 获取解码后的输出缓冲区
if (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; // 获取对应的输出缓冲区
byte[] chunk = new byte[bufferInfo.size]; // 创建字节数组存储解码后的PCM数据
outputBuffer.get(chunk); // 将输出缓冲区的数据读入字节数组
outputBuffer.clear(); // 清空输出缓冲区
audioTrack.write(chunk, 0, chunk.length); // 将PCM数据写入AudioTrack进行播放
codec.releaseOutputBuffer(outputBufferIndex, false); // 释放输出缓冲区
}
}
codec.stop(); // 停止解码器
codec.release(); // 释放解码器资源
extractor.release(); // 释放MediaExtractor资源
audioTrack.stop(); // 停止AudioTrack
audioTrack.release(); // 释放AudioTrack资源
}
}