Android音頻AAC硬編碼

    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" />
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章