Android音視頻編碼分爲軟編和硬編兩種。所謂的硬編是用設備GPU去實現編解碼,從而減輕CPU的壓力,讓程序更加的健壯,自然而然你就知道了軟編其實就是讓CPU編碼(其實是在c層通過c/c++進行編碼,之所以這樣是因爲c/c++平臺上已經有很多比較好的音視頻編解碼庫。比如著名ffmpeg,搞過音視頻的相信對這個庫絕對不會陌生)。那麼或許你心目中有一個小小的疑問?爲什麼要編解碼了?原因就是讓數據更小便於傳輸。編解碼就好比是壓縮與解壓!本文是把PCM數據硬編成ACC格式數據。如果對音頻的採集不熟悉,請查閱Android 音頻採集。
讀取原始數據:
public class AudioData {
public ByteBuffer buffer; //存儲原始音頻數據的buffer
public int size; //buffer大小
}
//讀取音頻數據(原始音頻數據)
private int readData(AudioData data){
if(mAudioRecord==null){//檢查是否初始化
return -1;
}
if(data==null){
return -1;
}
//開闢大小爲640字節的byteBuffer
if(data.buffer==null){
data.buffer=ByteBuffer.allocateDirect(640);
}else{
if(data.buffer.capacity()<640) {
data.buffer=ByteBuffer.allocate(640);
}
}
//把音頻讀取到data.buffer中,期望讀取640個byte,返回值表示實際讀取多個byte
data.size=mAudioRecord.read(data.buffer,640);
if(data.size==AudioRecord.ERROR_BAD_VALUE){//AudioRecord對象參數不可用
return -1;
}
return 0;
}
把原始的音頻數據讀取到ByteBuffer中,ByteBuffer是nio包中引用的,相對傳統的io包要快的多,如果對ByteBuffer不熟悉請查閱圖解ByteBuffer。 android平臺上的音視頻硬編碼主要就是通過MediaCodec進行實現的。
//創建編碼器
@SuppressLint("NewApi")
private int createEncoder(){
//防止重複創建編碼器
if(mediaCodec!=null){
return 0;
}
try {
mediaCodec=MediaCodec.createEncoderByType("audio/mp4a-latm");
} catch (Exception e) {
e.printStackTrace();
return -1;
}
// AAC 硬編碼器
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,1); //聲道數(這裏是數字)
format.setInteger(MediaFormat.KEY_SAMPLE_RATE,mSampleRateInHz); //採樣率
format.setInteger(MediaFormat.KEY_BIT_RATE,9600); //碼率
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
bufferInfo=new MediaCodec.BufferInfo();//記錄編碼完成的buffer的信息
mediaCodec.configure(format, null, null,MediaCodec.CONFIGURE_FLAG_ENCODE);// MediaCodec.CONFIGURE_FLAG_ENCODE 標識爲編碼器
mediaCodec.start();
return 0;
}
//停止編碼
@SuppressLint("NewApi")
private int stopEncoder(){
if(mediaCodec==null){
return -1;
}
mediaCodec.stop();
mediaCodec.release();
return 0;
}
或許你在煩惱配置MediaCodec時用到的MediaFormat 我怎麼知道其中應該配置哪些屬性了? 放心答案就在下圖:(也可直接訪問官網查,當然需要翻牆 編解碼所需要的MediaFormat屬性)
//編碼
@SuppressLint("NewApi")
private int encode(AudioData result){
if(mediaCodec==null){
return -1;
}
//把數據拷貝到byte數組中
byte[] data=new byte[result.size];
result.buffer.get(data);
result.buffer.flip();
inputBuffers=mediaCodec.getInputBuffers();
outputBuffers=mediaCodec.getOutputBuffers();
// <0一直等待可用的byteBuffer 索引;=0 馬上返回索引 ;>0 等待相應的毫秒數返回索引
inputBufferIndex=mediaCodec.dequeueInputBuffer(-1); //一直等待(阻塞)
if(inputBufferIndex>=0){ //拿到可用的buffer索引了
inputBuffer=inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(data);
mediaCodec.queueInputBuffer(inputBufferIndex,0,result.size,0,0); //投放到編碼隊列裏去
}
//獲取已經編碼成的buffer的索引 0表示馬上獲取 ,>0表示最多等待多少毫秒獲取
outputBufferIndex=mediaCodec.dequeueOutputBuffer(bufferInfo,0);
while(outputBufferIndex>=0){
//------------添加頭信息--------------
int outBitsSize = bufferInfo.size;
int outPacketSize = outBitsSize + 7; // 7 is ADTS size
byte[]outData= new byte[outPacketSize];
outputBuffer = outputBuffers[outputBufferIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
addADTStoPacket(outData,outPacketSize,mSampleRateInHz,1);//添加頭
outputBuffer.get(outData,7,outBitsSize);
try {
ou.write(outData);
} catch (IOException e) {
e.printStackTrace();
}
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
}
return 0;
}
/**
* 添加頭部信息
* Add ADTS header at the beginning of each and every AAC packet. This is
* needed as MediaCodec encoder generates a packet of raw AAC data.
* Note the packetLen must count in the ADTS header itself.
* packet 數據
* packetLen 數據長度
* sampleInHz 採樣率
* chanCfgCounts 通道數
**/
private void addADTStoPacket(byte[] packet, int packetLen,int sampleInHz,int chanCfgCounts) {
int profile = 2; // AAC LC
int freqIdx = 8; // 16KHz 39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
switch (sampleInHz){
case 8000:{
freqIdx = 11;
break;
}
case 16000:{
freqIdx = 8;
break;
}
default:
break;
}
int chanCfg = chanCfgCounts; // CPE
// fill in ADTS data
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 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;
}
整片文章中最重要的就是編碼這部分 ,編碼AAC文件格式的音頻時需要添加頭的,要不然是沒有辦法進行正常播放的。添加頭部信息,詳細可以查閱該文:ADTS格式解析
最後是錄音以及編碼的調用封裝方法:(全部完整代碼,請在文章最後下載AAC音頻硬編可播放Demo查閱)
//錄音以及編碼
private void Recording(){
isStart=true;
File file=null;
int result=startRecord();//開始錄音
if(result==0){
file=new File(parent,String.valueOf(SystemClock.elapsedRealtime())+".aac");
final String a=file.getAbsolutePath();
try {
file.createNewFile();
runOnUiThread(new Runnable() {
@Override
public void run() {
path.setText("文件存目路徑:"+a);
}
});
} catch (IOException e) {
e.printStackTrace();
Log.e("ZL","創建文件出錯");
}
}
if(file!=null){
try {
ou=new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.e("ZL","創建輸出流出錯");
}
}
int result1=createEncoder(); //創建編碼器
if(result1==0){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"創建編碼器成功",Toast.LENGTH_SHORT).show();
}
});
}
AudioData data=new AudioData();
while(isStart)
{
int result2=readData(data);
if(result2==0){
encode(data);
Log.e("ZL","錄音成功");
}
}
stopRecord(); //停止錄音
stopEncoder(); //停止編碼
if(ou!=null){
try {
ou.close();
}
catch (IOException e) {
e.printStackTrace();
Log.e("ZL","關閉輸出流出錯");
}
}
}
截止至2016/10/10爲止,目前android平臺支持的音視頻硬編碼格式(當然大家也可訪問這個網址android平臺支持的音視頻硬編碼格式進行查看。ps:要想打開這個網址需要翻牆,中國長城防火牆實在是太厚太高啦,翻牆的方法大家百度下就知道啦。),如下圖所示:
轉載請申明出處 http://blog.csdn.net/java_android_c/article/details/52775769
備註:
AAC音頻硬編可播放Demo 用手機上支持aac格式的播放器就可以播放
注意添加相應的權限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />