在语音聊天室源码中,完整的流媒体传输涉及到的环节还是非常多的,不仅于此,每个环节都至关重要,今天我们主要来讲解一下在语音聊天室源码开发中,是如何实现音频解码的,主要用到的音频解码标准是AAC。
一、MediaCodec
MediaCodec不光能编码,同样也能解码。
之前语音聊天室源码编码,我们是将AudioRecord中读取数据送入编码器,接着再从编码器中获取输出的数据,这样循环,最终得到编码后的aac文件。
那么对于语音聊天室源码解码,我们就可以将编码后的aac文件传入解码器,接着再从解码器中获取输出的数据传入AudioTrack,这样循环,就可以完整的播放该aac文件。
此时产生了一个问题,我们如何将aac文件送入解码器呢,直接读文件是否可以,就像AudioTrack播放Pcm文件时,直接读文件数据,传入AudioTrack中播放。
当然,直接读文件是可以的,我们只需要在语音聊天室源码开发时设置好音频解码的相关参数,比如采样率、声道设置、编码格式等。
不过,Android提供了一种更为方便的组件,MediaExtractor,它可以从文件中获取编码的音频或视频,并一帧一帧解析出来
那么,下面我们将使用MediaExtractor来进行语音聊天室源码中aac音频文件的读取。
首先,介绍下MediaExtractor的使用步骤:
- 初始化实例
- 设置文件路径
- 遍历所有的track,根据需要,找到指定的track,并调用selectTrack()方法
- 调用readSampleData()方法读取当前帧数据
- 读取下一帧,调用advance()
- 读取结束,则释放资源
二、音频解码
通过上面的描述,我们对语音聊天室源码音频解码的步骤有了一定了解,我们先来回顾下AudioTrack的使用步骤:
- 开启子线程
- 构建实例
- 开始播放
- 循环读取文件数据,并写入AudioTrack
- 停止播放,释放资源
那么,对于语音聊天室源码音频解码播放,步骤如下:
- 开启子线程
- 构建实例
- 从MediaExtractor中找到指定的track
- 开始播放、解码
- 循环从MediaExtractor中读取数据,并送入解码器
- 解码完成后,数据写入AudioTrack中进行播放
- 停止播放、解码,释放资源
下面,就对上述步骤,一一讲解
2.1 开启子线程
语音聊天室源码解码的整个流程也是要在子线程中执行的
private static class DecodeThread extends Thread {
public DecodeThread(){}
}
2.2 配置必要参数
private static class DecodeThread extends Thread {
private static final long TIMEOUT_MS = 2000L;
private MediaExtractor mediaExtractor;
private MediaCodec mediaCodec;
private AudioTrack audioTrack;
private final String path;
/**
* 音频流格式(一般使用music)
*/
private final int streamType;
/**
* 采样率
*/
private final int sampleRateInHz;
/**
* 声道设置
*/
private final int channelConfig;
/**
* 编码格式
*/
private final int audioFormat;
/**
* 播放模式(一般使用流模式)
*/
private final int mod;
/**
* 音频缓存大小
*/
private int bufferSizeInBytes;
/**
* 音频格式
*/
private MediaFormat format;
private String mime;
/**
* 是否停止解码
*/
private boolean isStopDecode = false;
/**
* 构造方法(传入必要的参数)
*/
public DecodeThread(String path,
int streamType,
int sampleRateInHz,
int channelConfig,
int audioFormat,
int mod
) {
this.path = path;
this.streamType = streamType;
this.sampleRateInHz = sampleRateInHz;
this.channelConfig = channelConfig;
this.audioFormat = audioFormat;
this.mod = mod;
}
}
参数和AudioTrack播放Pcm时基本一致,只是多了两个组件MediaExtractor和MediaCodec
2.3 初始化
重写Thread的run方法
@Override
public void run() {
super.run();
initMediaExtractor();
initMediaCodec();
initAudioTrack();
decode();
}
run方法中,进行了一系列的初始化操作,最后一个方法decode()是真正开始解码
# initMediaExtractor()
private void initMediaExtractor() {
if (TextUtils.isEmpty(path)) {
return;
}
try {
mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(path);
int trackCount = mediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = mediaExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (!TextUtils.isEmpty(mime) && mime.startsWith("audio/")) {
mediaExtractor.selectTrack(i);
this.format = format;
this.mime = mime;
break;
}
}
} catch (IOException e) {
Log.e(TAG, "initMediaExtractor: ", e);
mediaExtractor = null;
format = null;
mime = null;
}
}
在该方法中,我们对MediaExtractor做了初始化操作,并找到了对应audio的track
# initMediaCodec
private void initMediaCodec() {
if (TextUtils.isEmpty(mime) || format == null) {
return;
}
try {
mediaCodec = MediaCodec.createDecoderByType(mime);
mediaCodec.configure(format, null, null, 0);
} catch (IOException e) {
e.printStackTrace();
mediaCodec = null;
}
}
可以看到,初始化MediaCodec的逻辑比较简单,这是因为我们利用了从MediaExtractor中获取的MediaFormat去配置MediaCodec,如果是从语音聊天室源码文件读取的话,MediaFormat还需要我们自己配置
# initAudioTrack()
private void initAudioTrack() {
bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mod);
}
2.4 播放
进入到decode()方法后,就正式开始播放
# decode()
private void decode() {
if (mediaExtractor == null || mediaCodec == null || audioTrack == null) {
return;
}
long startMs = System.currentTimeMillis();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
mediaCodec.start();
audioTrack.play();
for (; ; ) {
if (isStopDecode) {
release();
break;
}
int inputBufferId = mediaCodec.dequeueInputBuffer(TIMEOUT_MS);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId);
int readSize = -1;
if (inputBuffer != null) {
readSize = mediaExtractor.readSampleData(inputBuffer, 0);
}
if (readSize <= 0) {
mediaCodec.queueInputBuffer(
inputBufferId,
0,
0,
0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isStopDecode = true;
} else {
mediaCodec.queueInputBuffer(inputBufferId, 0, readSize, mediaExtractor.getSampleTime(), 0);
mediaExtractor.advance();
}
}
int outputBufferId = mediaCodec.dequeueOutputBuffer(info, TIMEOUT_MS);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);
if (outputBuffer != null && info.size > 0) {
while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
byte[] data = new byte[info.size];
outputBuffer.get(data);
outputBuffer.clear();
audioTrack.write(data, 0, info.size);
}
mediaCodec.releaseOutputBuffer(outputBufferId, false);
}
}
}
语音聊天室源码解码过程中,也是通过死循环不断的读取数据,并将解码后的数据传入AudioTrack播放的过程,不过,需要注意的一点是,在语音聊天室源码解码完成后,数据不能马上传入AudioTrack,而是要通过解码时间与当前时间做对比,进行延时解码,如果不进行这一操作,语音聊天室源码播放出的音频会很快。
以上就是“Android语音聊天室源码开发,如何实现音频解码?”的全部内容,希望对大家有帮助。