AAC 音频编码保存和解码播放
2020-07-31 本文已影响0人
海盗的帽子
一. 编码器 MediaCodec
MediaCodec 是 Android 提供的用于对音频进行编解码的类,属于硬编解。MediaCodec 在编解码的过程中使用了一组缓冲区来处理数据。如下图所示:
image.png
基本使用流程如下:
// 1 创建编解码器
MediaCodec.createByCodecName() // createEncoderByType , createDecoderByType
// 2 配置编解码器
configure(@Nullable MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, int flags)
// 3 开始编解码
start
// 4 循环处理数据
while(true) {
dequeueInputBuffer// 获取可用的输入缓存区 buffer 的下标 inputIndex
getInputBuffers// 根据 inputIndex 获取可用的输入缓冲区 bytebuffer
bytebuffer.put // 放入数据
queueInputBuffer // 将数据放入输入缓冲区
dequeueOutputBuffer // 获取可用的输出缓存区 buffer 的下标 outputIndex
getOutPutBuffers // 根据 outputIndex 获取可用的输出缓冲区 bytebuffer
outputBuffer.get() // 获取数据
releaseOutputBuffer // 处理完成,释放 buffer
}
// 5 终止
stop
// 6 释放编码器使用的资源
release
二. MediaExtractor 媒体数据提取器
通过 MediaExtractor 可以将媒体文件的视频和音频数据分离,也可以获取对应的音频格式或者视频格式。主要 API 如下:
- setDataSource : 设置数据源
- getTrackCount:获取文件的通道数,音频通道和视频通道
- getTrackFormat : 获取指定通道的格式,比如音频的格式或者是视频的格式
- getSampleTime: 获取当前帧的时间戳
- readSampleData :将当前帧数据写入 byteBuffer
- advance : 读取下一帧
- release : 释放资源
基本使用流程如下:
// 1 设置数据源
setDataSource
// 2 获取对应的视频或者音频格式
getTrackFormat
// 3 定位到某条轨道
selectTrack
// 4 读取数据
while(true) {
readSampleData
advance
}
// 5 释放
release
三. 编码保存
特别指出,由于 aac 的格式问题,如果保存到本地需要对每一帧添加 ADTS ,见 addADTStoPacket 方法
public class AacAudioRecord {
private static final String TAG = "AacAudioRecord";
private AudioRecord mAudioRecord;
private MediaCodec mAudioEncoder;
private volatile boolean mIsRecording = false;
private ExecutorService mExecutorService;
private int mAudioSource;
private int mSampleRateInHz;
private int mChannelConfig;
private int mAudioFormat;
private int mBufferSizeInBytes;
private MediaFormat mMediaFormat;
private String mFilePath;
private File mFile;
private FileOutputStream mFileOutputStream;
private BufferedOutputStream mBufferedOutputStream;
private BlockingQueue<byte[]> mDataQueue;
public AacAudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) {
mAudioSource = audioSource;
mSampleRateInHz = sampleRateInHz;
mChannelConfig = channelConfig;
mAudioFormat = audioFormat;
mBufferSizeInBytes = bufferSizeInBytes;
mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
try {
mAudioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
} catch (IOException e) {
e.printStackTrace();
}
try {
mAudioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
mMediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRateInHz, channelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
//声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标
mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//64000, 96000, 128000
mMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSizeInBytes);
mMediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
mAudioEncoder.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException e) {
Log.e(TAG, "" + e.getMessage());
}
mDataQueue = new ArrayBlockingQueue<>(10);
mExecutorService = Executors.newFixedThreadPool(2);
}
public void start(String filePath) {
mFilePath = filePath;
mExecutorService.execute(new Runnable() {
@Override
public void run() {
startRecord();
}
});
mExecutorService.execute(new Runnable() {
@Override
public void run() {
startEncode();
}
});
}
public void stop() {
mIsRecording = false;
}
private void startRecord() {
mAudioRecord.startRecording();
mIsRecording = true;
byte[] buffer = new byte[2048];
while (mIsRecording) {
int len = mAudioRecord.read(buffer, 0, 2048);
if (len > 0) {
byte[] data = new byte[len];
System.arraycopy(buffer, 0, data, 0, len);
queueData(data);
}
}
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
private void startEncode() {
if (TextUtils.isEmpty(mFilePath)) {
return;
}
mAudioEncoder.start();
mFile = new File(mFilePath);
if (mFile.exists()) {
mFile.delete();
}
try {
mFile.createNewFile();
mFileOutputStream = new FileOutputStream(mFile);
mBufferedOutputStream = new BufferedOutputStream(mFileOutputStream, 2048);
} catch (IOException e) {
e.printStackTrace();
}
byte[] pcmData;
int inputIndex;
ByteBuffer inputBuffer;
ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();
int outputIndex;
ByteBuffer outputBuffer;
ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
byte[] aacChunk;
while (mIsRecording || !mDataQueue.isEmpty()) {
pcmData = dequeueData();
if (pcmData == null) {
continue;
}
inputIndex = mAudioEncoder.dequeueInputBuffer(10_000);
if (inputIndex >= 0) {
inputBuffer = inputBuffers[inputIndex];
inputBuffer.clear();
inputBuffer.limit(pcmData.length);
inputBuffer.put(pcmData);
mAudioEncoder.queueInputBuffer(inputIndex, 0, pcmData.length, 0, 0);
}
outputIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 10_000);
while (outputIndex >= 0) {
outputBuffer = outputBuffers[outputIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
aacChunk = new byte[bufferInfo.size + 7];
addADTStoPacket(mSampleRateInHz, aacChunk, aacChunk.length);
outputBuffer.get(aacChunk, 7, bufferInfo.size);
try {
mBufferedOutputStream.write(aacChunk);
} catch (IOException e) {
e.printStackTrace();
}
mAudioEncoder.releaseOutputBuffer(outputIndex, false);
outputIndex = mAudioEncoder.dequeueOutputBuffer(bufferInfo, 10_000);
}
}
try {
mBufferedOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
IOUtil.close(mBufferedOutputStream);
IOUtil.close(mFileOutputStream);
mAudioEncoder.stop();
mAudioEncoder.release();
mAudioEncoder = null;
}
private byte[] dequeueData() {
if (mDataQueue.isEmpty()) {
return null;
}
try {
return mDataQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
private void queueData(byte[] data) {
try {
mDataQueue.put(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void addADTStoPacket(int sampleRateType, byte[] packet, int packetLen) {
int profile = 2;
int chanCfg = 2;
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (sampleRateType << 2) + (chanCfg >> 2));
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
四. 解码播放
public class AacAudioPlayer {
private AudioTrack mAudioTrack;
private MediaCodec mAudioDecoder;
private MediaExtractor mMediaExtractor;
private int mStreamType = AudioManager.STREAM_MUSIC;
private int mSampleRate = 44100;
private int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO;
private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int mMode = AudioTrack.MODE_STREAM;
private int mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRate, mChannelConfig, mAudioFormat);
private volatile boolean mIsPlaying = false;
private ExecutorService mExecutorService;
public AacAudioPlayer() {
mAudioTrack = new AudioTrack(mStreamType, mSampleRate, mChannelConfig, mAudioFormat, mMinBufferSize, mMode);
mMediaExtractor = new MediaExtractor();
mExecutorService = Executors.newFixedThreadPool(1);
}
public void play(String filePath) {
try {
mMediaExtractor.setDataSource(filePath);
} catch (IOException e) {
e.printStackTrace();
}
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(0);
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
if (TextUtils.isEmpty(mimeType)) {
return;
}
assert mimeType != null;
if (mimeType.startsWith("audio")) {
mMediaExtractor.selectTrack(0);
mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 0);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, 0);
try {
mAudioDecoder = MediaCodec.createDecoderByType(mimeType);
} catch (IOException e) {
e.printStackTrace();
}
mAudioDecoder.configure(mediaFormat, null, null, 0);
}
mAudioDecoder.start();
mAudioTrack.play();
mExecutorService.execute(new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
decode();
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void decode() {
MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();
ByteBuffer inputBuffer;
mIsPlaying = true;
while (mIsPlaying) {
int inputIndex = mAudioDecoder.dequeueInputBuffer(10_000);
if (inputIndex < 0) {
mIsPlaying = false;
continue;
}
inputBuffer = mAudioDecoder.getInputBuffer(inputIndex);
inputBuffer.clear();
int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);
if (sampleSize > 0) {
mAudioDecoder.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);
mMediaExtractor.advance();
} else {
mIsPlaying = false;
}
int outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 0);
ByteBuffer outputBuffer;
byte[] buffer;
while (outputIndex >= 0) {
outputBuffer = mAudioDecoder.getOutputBuffer(outputIndex);
if (outputBuffer == null) {
break;
}
buffer = new byte[decodeBufferInfo.size];
outputBuffer.get(buffer);
outputBuffer.clear();
mAudioTrack.write(buffer, 0, decodeBufferInfo.size);
mAudioDecoder.releaseOutputBuffer(outputIndex, false);
outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 0);
}
}
mIsPlaying = false;
mAudioDecoder.stop();
mAudioDecoder.release();
mAudioDecoder = null;
}
}