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" />
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值