Android MediaCodec硬解碼AAC音訊檔案(實時AAC音訊幀)並播放
轉載請註明出處:http://blog.csdn.net/a512337862/article/details/72629755
今天在這裡簡單介紹一下,如何利用android MediaCodec解碼AAC音訊檔案或者實時AAC音訊幀並通過AudioTrack來播放。主要的思路就是從檔案或者網路獲取一幀幀的AAC的資料,送入解碼器解碼後播放。
封裝AudioTrack
AudioTrack主要是用來進行主要是用來播放聲音的,但是隻能播放PCM格式的音訊流。這裡主要是簡單的對AudioTrack進行了封裝,加入了一些異常判斷:
/**
* Created by ZhangHao on 2017/5/10.
* 播放pcm資料
*/
public class MyAudioTrack {
private int mFrequency;// 取樣率
private int mChannel;// 聲道
private int mSampBit;// 取樣精度
private AudioTrack mAudioTrack;
public MyAudioTrack(int frequency, int channel, int sampbit) {
this.mFrequency = frequency;
this.mChannel = channel;
this.mSampBit = sampbit;
}
/**
* 初始化
*/
public void init() {
if (mAudioTrack != null) {
release();
}
// 獲得構建物件的最小緩衝區大小
int minBufSize = getMinBufferSize();
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
mFrequency, mChannel, mSampBit, minBufSize, AudioTrack.MODE_STREAM);
mAudioTrack.play();
}
/**
* 釋放資源
*/
public void release() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
}
}
/**
* 將解碼後的pcm資料寫入audioTrack播放
*
* @param data 資料
* @param offset 偏移
* @param length 需要播放的長度
*/
public void playAudioTrack(byte[] data, int offset, int length) {
if (data == null || data.length == 0) {
return;
}
try {
mAudioTrack.write(data, offset, length);
} catch (Exception e) {
Log.e("MyAudioTrack", "AudioTrack Exception : " + e.toString());
}
}
public int getMinBufferSize() {
return AudioTrack.getMinBufferSize(mFrequency,
mChannel, mSampBit);
}
}
這裡簡單介紹一下,在AudioTrack構造方法AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)裡幾個變數的含義:
1.streamType:指定流的型別,主要包括以下幾種:
- STREAM_ALARM:警告聲
- STREAM_MUSCI:音樂聲
- STREAM_RING:鈴聲
- STREAM_SYSTEM:系統聲音
- STREAM_VOCIE_CALL:電話聲音
因為android系統對不同的聲音的管理是分開的,所以這個引數的作用就是設定AudioTrack播放的聲音型別。
2.sampleRateInHz : 取樣率
3.channelConfig : 聲道
4.audioFormat : 取樣精度
5.bufferSizeInBytes :緩衝區大小,可以通過AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)來獲取
6.mode : MODE_STATIC和MODE_STREAM:
- MODE_STATIC : 直接把所有的資料載入到快取區,不需要多次write,一般用於佔用記憶體小,延時要求高的情況
- MODE_STREAM : 需要多次write,一般用於像從網路獲取資料或者實時解碼的情況,本次的例子就是這種情況。
我這裡只是簡單的介紹,大家可以去網上找更為詳細的介紹。
AAC解碼器
這裡主要對MediaCodec進行封裝,實現一幀幀去解碼AAC。
/**
* Created by ZhangHao on 2017/5/17.
* 用於aac音訊解碼
*/
public class AACDecoderUtil {
private static final String TAG = "AACDecoderUtil";
//聲道數
private static final int KEY_CHANNEL_COUNT = 2;
//取樣率
private static final int KEY_SAMPLE_RATE = 48000;
//用於播放解碼後的pcm
private MyAudioTrack mPlayer;
//解碼器
private MediaCodec mDecoder;
//用來記錄解碼失敗的幀數
private int count = 0;
/**
* 初始化所有變數
*/
public void start() {
prepare();
}
/**
* 初始化解碼器
*
* @return 初始化失敗返回false,成功返回true
*/
public boolean prepare() {
// 初始化AudioTrack
mPlayer = new MyAudioTrack(KEY_SAMPLE_RATE, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
mPlayer.init();
try {
//需要解碼資料的型別
String mine = "audio/mp4a-latm";
//初始化解碼器
mDecoder = MediaCodec.createDecoderByType(mine);
//MediaFormat用於描述音視訊資料的相關引數
MediaFormat mediaFormat = new MediaFormat();
//資料型別
mediaFormat.setString(MediaFormat.KEY_MIME, mine);
//聲道個數
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, KEY_CHANNEL_COUNT);
//取樣率
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, KEY_SAMPLE_RATE);
//位元率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
//用來標記AAC是否有adts頭,1->有
mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
//用來標記aac的型別
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
//ByteBuffer key(暫時不瞭解該引數的含義,但必須設定)
byte[] data = new byte[]{(byte) 0x11, (byte) 0x90};
ByteBuffer csd_0 = ByteBuffer.wrap(data);
mediaFormat.setByteBuffer("csd-0", csd_0);
//解碼器配置
mDecoder.configure(mediaFormat, null, null, 0);
} catch (IOException e) {
e.printStackTrace();
return false;
}
if (mDecoder == null) {
return false;
}
mDecoder.start();
return true;
}
/**
* aac解碼+播放
*/
public void decode(byte[] buf, int offset, int length) {
//輸入ByteBuffer
ByteBuffer[] codecInputBuffers = mDecoder.getInputBuffers();
//輸出ByteBuffer
ByteBuffer[] codecOutputBuffers = mDecoder.getOutputBuffers();
//等待時間,0->不等待,-1->一直等待
long kTimeOutUs = 0;
try {
//返回一個包含有效資料的input buffer的index,-1->不存在
int inputBufIndex = mDecoder.dequeueInputBuffer(kTimeOutUs);
if (inputBufIndex >= 0) {
//獲取當前的ByteBuffer
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
//清空ByteBuffer
dstBuf.clear();
//填充資料
dstBuf.put(buf, offset, length);
//將指定index的input buffer提交給解碼器
mDecoder.queueInputBuffer(inputBufIndex, 0, length, 0, 0);
}
//編解碼器緩衝區
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
//返回一個output buffer的index,-1->不存在
int outputBufferIndex = mDecoder.dequeueOutputBuffer(info, kTimeOutUs);
if (outputBufferIndex < 0) {
//記錄解碼失敗的次數
count++;
}
ByteBuffer outputBuffer;
while (outputBufferIndex >= 0) {
//獲取解碼後的ByteBuffer
outputBuffer = codecOutputBuffers[outputBufferIndex];
//用來儲存解碼後的資料
byte[] outData = new byte[info.size];
outputBuffer.get(outData);
//清空快取
outputBuffer.clear();
//播放解碼後的資料
mPlayer.playAudioTrack(outData, 0, info.size);
//釋放已經解碼的buffer
mDecoder.releaseOutputBuffer(outputBufferIndex, false);
//解碼未解完的資料
outputBufferIndex = mDecoder.dequeueOutputBuffer(info, kTimeOutUs);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
//返回解碼失敗的次數
public int getCount() {
return count;
}
/**
* 釋放資源
*/
public void stop() {
try {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
if (mDecoder != null) {
mDecoder.stop();
mDecoder.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
其實這裡和我之前利用MediaCodec解碼H264很類似,主要就是在因為解碼資料型別不同,所以初始化時有區別。還有一點就是解碼H624時,直接將解碼後資料利用surface顯示,而解碼aac是將解碼後的資料取出來,再利用AudioTrack播放。
讀取aac檔案
這裡是利用執行緒讀aac檔案,獲得一幀幀的aac幀資料,然後送入解碼器播放。
/**
* Created by ZhangHao on 2017/4/18.
* 播放aac音訊檔案
*/
public class ReadAACFileThread extends Thread {
//音訊解碼器
private AACDecoderUtil audioUtil;
//檔案路徑
private String filePath;
//檔案讀取完成標識
private boolean isFinish = false;
//這個值用於找到第一個幀頭後,繼續尋找第二個幀頭,如果解碼失敗可以嘗試縮小這個值
private int FRAME_MIN_LEN = 50;
//一般AAC幀大小不超過200k,如果解碼失敗可以嘗試增大這個值
private static int FRAME_MAX_LEN = 100 * 1024;
//根據幀率獲取的解碼每幀需要休眠的時間,根據實際幀率進行操作
private int PRE_FRAME_TIME = 1000 / 50;
//記錄獲取的幀數
private int count = 0;
public ReadAACFileThread(String path) {
this.audioUtil = new AACDecoderUtil();
this.filePath = path;
this.audioUtil.start();
}
@Override
public void run() {
super.run();
File file = new File(filePath);
//判斷檔案是否存在
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
//儲存完整資料幀
byte[] frame = new byte[FRAME_MAX_LEN];
//當前幀長度
int frameLen = 0;
//每次從檔案讀取的資料
byte[] readData = new byte[10 * 1024];
//開始時間
long startTime = System.currentTimeMillis();
//迴圈讀取資料
while (!isFinish) {
if (fis.available() > 0) {
int readLen = fis.read(readData);
//當前長度小於最大值
if (frameLen + readLen < FRAME_MAX_LEN) {
//將readData拷貝到frame
System.arraycopy(readData, 0, frame, frameLen, readLen);
//修改frameLen
frameLen += readLen;
//尋找第一個幀頭
int headFirstIndex = findHead(frame, 0, frameLen);
while (headFirstIndex >= 0 && isHead(frame, headFirstIndex)) {
//尋找第二個幀頭
int headSecondIndex = findHead(frame, headFirstIndex + FRAME_MIN_LEN, frameLen);
//如果第二個幀頭存在,則兩個幀頭之間的就是一幀完整的資料
if (headSecondIndex > 0 && isHead(frame, headSecondIndex)) {
//視訊解碼
count++;
Log.e("ReadAACFileThread", "Length : " + (headSecondIndex - headFirstIndex));
audioUtil.decode(frame, headFirstIndex, headSecondIndex - headFirstIndex);
//擷取headSecondIndex之後到frame的有效資料,並放到frame最前面
byte[] temp = Arrays.copyOfRange(frame, headSecondIndex, frameLen);
System.arraycopy(temp, 0, frame, 0, temp.length);
//修改frameLen的值
frameLen = temp.length;
//執行緒休眠
sleepThread(startTime, System.currentTimeMillis());
//重置開始時間
startTime = System.currentTimeMillis();
//繼續尋找資料幀
headFirstIndex = findHead(frame, 0, frameLen);
} else {
//找不到第二個幀頭
headFirstIndex = -1;
}
}
} else {
//如果長度超過最大值,frameLen置0
frameLen = 0;
}
} else {
//檔案讀取結束
isFinish = true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
Log.e("ReadAACFileThread", "AllCount:" + count + "Error Count : " + audioUtil.getCount());
} else {
Log.e("ReadH264FileThread", "File not found");
}
audioUtil.stop();
}
/**
* 尋找指定buffer中AAC幀頭的開始位置
*
* @param startIndex 開始的位置
* @param data 資料
* @param max 需要檢測的最大值
* @return
*/
private int findHead(byte[] data, int startIndex, int max) {
int i;
for (i = startIndex; i <= max; i++) {
//發現幀頭
if (isHead(data, i))
break;
}
//檢測到最大值,未發現幀頭
if (i == max) {
i = -1;
}
return i;
}
/**
* 判斷aac幀頭
*/
private boolean isHead(byte[] data, int offset) {
boolean result = false;
if (data[offset] == (byte) 0xFF && data[offset + 1] == (byte) 0xF1
&& data[offset + 3] == (byte) 0x80) {
result = true;
}
return result;
}
//修眠
private void sleepThread(long startTime, long endTime) {
//根據讀檔案和解碼耗時,計算需要休眠的時間
long time = PRE_FRAME_TIME - (endTime - startTime);
if (time > 0) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
這裡沒有太多的東西,就是通過幀頭來判斷aac幀,並擷取每幀資料送入解碼器。我這裡只是取巧做了簡單的判斷,對幀頭的判斷並不一定滿足所有的aac幀頭,大家可以根據實際的情況自行修改。
結語
1.其實,實現分離音訊幀,利用MediaExtractor這個類就可以實現,但是因為我實際的資料來源是來自網路,所以才會demo才會複雜一點。
2.H264,AAC解碼Demo下載地址:http://download.csdn.net/detail/a512337862/9882200 。附帶aac以及H264檔案以及原始碼
相關文章
- Android音視訊(四)MediaCodec編解碼AACAndroid
- MediaCodec硬編碼pcm2aac
- Android:MediaCodeC硬編碼解碼視訊,並將視訊幀儲存為圖片檔案Android
- Android 音視訊 - MediaCodec 編解碼音視訊Android
- ffmpeg音訊編碼之pcm轉碼aac音訊
- 相親交友原始碼中,音訊AAC解碼的實現程式碼原始碼音訊
- 【秒懂音視訊開發】14_AAC編碼
- js對flv提取h264、aac音視訊流JS
- Android AudioRecord錄音 並websocket實時傳輸,AudioTrack 播放wav 音訊,Speex加密AndroidWeb音訊加密
- Android音視訊處理之MediaCodecAndroid
- Android 播放raw資料夾下音訊檔案Android音訊
- android 音訊播放 SoundPoolAndroid音訊
- FFmpeg開發筆記(八):ffmpeg解碼音訊並使用SDL同步音訊播放筆記音訊
- Android 音視訊錄製硬編碼實現Android
- MediaCodeC解碼視訊指定幀,迅捷、精確
- 萬彩動畫大師教程 | 輸出視訊音訊編碼器選擇(AAC和MP3)動畫音訊
- 音訊開發之錄製播放pcm檔案音訊
- Android 音視訊開發 - 使用AudioTrack播放音訊Android音訊
- android使用MediaCodec實現非同步視訊編解碼Android非同步
- Android 音視訊開發 視訊編碼,音訊編碼格式Android音訊
- 轉載:iOS音視訊實時採集硬體編碼iOS
- JavaCV FFmpeg AAC編碼Java
- iOS AVAudioPlayer(音訊播放)iOS音訊
- 使用 MediaCodec 在 Android 上進行硬解碼Android
- iOS 實時音訊採集與播放Audio Unit使用iOS音訊
- FFmpeg音訊解碼音訊
- iOS開發系列--音訊播放、錄音、視訊播放、拍照、視訊錄製(轉)iOS音訊
- 音視訊入門之音訊採集、編碼、播放音訊
- Android WebView 實現檔案選擇、拍照、錄製視訊、錄音AndroidWebView
- android音視訊指南-管理音訊焦點Android音訊
- Flutter(十) 音訊+影片播放Flutter音訊
- hqplayer pro 4,音訊播放音訊
- 帶你用AVPlayer實現音訊和視訊播放音訊
- Android 多媒體之 Silk 格式音訊解碼Android音訊
- 11┃音視訊直播系統之 WebRTC 進行文字聊天並實時傳輸檔案Web
- 騰訊互動白板+即時通訊+實時音視訊,Android學生端接入Android
- 針對Android訪問sd卡里面的音訊檔案AndroidSD卡音訊
- FFmpeg打造Android萬能音訊播放器總結Android音訊播放器
- 【AAC 系列一】Android 應用架構新時代來臨!Android應用架構