Android 多媒体——MediaCodec编码AAC

Posted VNanyesheshou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 多媒体——MediaCodec编码AAC相关的知识,希望对你有一定的参考价值。

Android 音频开发——AudioRecord录音
Android 音频开发——AudioTrack播放
Android 多媒体——MediaCodec编码AAC
MeidaCodec 官方地址:https://developer.android.google.cn/reference/android/media/MediaCodec

通过AudioRecord录制的音频数据(pcm格式),占用空间较大,消耗内存和磁盘,更不适合用于网络传输。而AAC是比较通用的压缩格式,这里整理下MeidaCodec编码AAC。

1. MediaCodec

 MediaCodec类用于访问编码器、解码器组件。它是多媒体架构的一部分(通常与MediaExtractorMediaSyncMediaMuxerMediaCryptoMediaDrmImageSurfaceAudioTrackAndroidRecord一起使用)

简单说,编解码器处理输入数据以生成输出数据。MediaCodec异步处理数据,并使用一组输入和输出缓冲区。

流程:

  1. 请求(或接收)一个空的输入缓冲区;
  2. 将缓冲区填入数据;
  3. 将缓冲区加入队列;
  4. 编解码器处理输入数据,并将数据转换输出缓冲区;
  5. 请求(或接收)一个输出缓冲区;
  6. 获取输出缓冲区的数据;
  7. 释放输出缓冲区;

2. 处理方式

处理方式API版本<= 20 Jelly Bean / KitKatAPI版本> = 21 Lollipop及更高版本
使用缓冲区数组的同步API支持的不推荐使用
使用缓冲区的同步API无法使用支持的
使用缓冲区的异步API无法使用支持的

使用缓冲区的异步处理

 首选方法是通过在调用configure之前设置回调来异步处理数据。如下示例:

 MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() 
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) 
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId,);
  
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId,) 
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId,);
  
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) 
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  
 
  @Override
  void onError() 
 );
 codec.configure(format,);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

使用缓冲区的同步处理

MediaCodec通常在同步模式下按以下方式使用

 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format,);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) 
  int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
  if (inputBufferId >= 0) 
    ByteBuffer inputBuffer = codec.getInputBuffer();
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId,);
  
  int outputBufferId = codec.dequeueOutputBuffer();
  if (outputBufferId >= 0) 
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is identical to outputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId,);
   else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) 
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    outputFormat = codec.getOutputFormat(); // option B
  
 
 codec.stop();
 codec.release();

3. 流末处理

到达输入数据的末尾时,必须通过调用queueInputBufferBUFFER_FLAG_END_OF_STREAM标志发送给编解码器 。可以在最后一个有效的输入缓冲区上执行此操作,也可以通过提交附加的空输入缓冲区(设置了流结束标志)来执行此操作。如果使用空缓冲区,则时间戳将被忽略。

编解码器将继续返回输出缓冲区,直到在 dequeueOutputBufferonOutputBufferAvailable接收到相同流结束标志。这可以在最后一个有效的输出缓冲区上设置,也可以在最后一个有效的输出缓冲区后的空白缓冲区上设置。这种空缓冲区的时间戳应该被忽略。

在发出输入流结束信号后不要提交其他输入缓冲区,除非已 flushed, or stopped and restarted

4. 具体实现

音频录制pcm可以参考之前的文章,这里就不贴了。

1 获取指定 type(audio/mp4a-latm)对应的 编码信息;

编码器名称示例:c2.android.aac.encoder、OMX.google.aac.encoder

public static MediaCodecInfo getEncoderCodecInfo(String mimeType) 
    MediaCodecInfo[] mediaCodecInfos =
        new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos();
    MediaCodecInfo mediaCodecInfo = null;
    for (MediaCodecInfo codecInfo : mediaCodecInfos) 
        if (!codecInfo.isEncoder())
            continue;
        String[] types = codecInfo.getSupportedTypes();
        for (int j = 0; j < types.length; j++) 
            if (types[j].equalsIgnoreCase(mimeType)) 
                return codecInfo;
            
        
    
    return null;

2 创建MediaCodec

MediaCodec 有两种创建编码器的方式

  1. 通过编码器名称,createByCodecName(@NonNull String name)
  2. 通过编码类型,MediaCodec createEncoderByType(@NonNull String type)

这里使用的是第一种。

private void initMediaCodec(int sampleRate,
							int channelCount) 
	MediaCodecInfo info = MediaUtil.getEncoderCodecInfo(AAC_TYPE);
	if (info != null) 
		try 
			mCodec = MediaCodec.createByCodecName(info.getName());
			mediaFormat = MediaFormat.createAudioFormat(AAC_TYPE
					, sampleRate,
					channelCount);
			mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
					aacLevel);
			mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
			mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
			mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING,
					AudioFormat.ENCODING_PCM_16BIT);
			mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
		 catch (IOException e) 
			e.printStackTrace();
			if (mCodec == null) 
				mCodec.release();
				mCodec = null;
			
		
	

3 配置mediaformat

代码如上所示;
mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

4 开始编码

mCodec.start();

5 处理数据

编码pcm数据,并将编码后的aac 数据添加adts 头信息,写入文件。

private void encodeAudioDataToFile() 
	Log.i(TAG, "encodeAudioDataToFile start");
	FileOutputStream out;
	try 
		out = new FileOutputStream(audioFile);
	 catch (FileNotFoundException e) 
		e.printStackTrace();
		out = null;
	
	long totalSize = 0;
	if (out != null) 
		try 
			//应该够保存编码后的aac数据
			byte[] aacData = new byte[bufferSize / 5];
			byte[] aacHeader = new byte[AAC_HEADER_SIZE];
			//首次主要设置header的1~3、7字节,后续只需要修改4~6字节即可。
			AacUtil.addADTStoPacket(aacLevel, sampleRate, channelCount, aacHeader, 0);
			MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
			while (!isEncodeEnd) 
				int inputBufferId = mCodec.dequeueInputBuffer(TIME_OUT);
				if (inputBufferId >= 0) 
					BufferPool.AudioData audioData = mBufferPool.pollDataBuffer(TIME_OUT,
							TimeUnit.MILLISECONDS);

					if (audioData != null) 
						ByteBuffer inputBuffer = mCodec.getInputBuffer(inputBufferId);
						byte[] data = audioData.data;
						inputBuffer.put(data, 0, audioData.readSize);
						mCodec.queueInputBuffer(inputBufferId, 0, audioData.readSize, 0,
								0);
						mBufferPool.deque(audioData);
					 else if (audioData == null && isRecording) 
						mCodec.queueInputBuffer(inputBufferId, 0, 0, 0,
								0);
					 else 
						mCodec.queueInputBuffer(inputBufferId, 0, 0, 0,
								MediaCodec.BUFFER_FLAG_END_OF_STREAM);
					
				

				int outputBufferId = mCodec.dequeueOutputBuffer(bufferInfo, TIME_OUT);
				if (outputBufferId >= 0) 
					ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufferId);
					if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) 
						Log.i(TAG, "BUFFER_FLAG_END_OF_STREAM");
						isEncodeEnd = true;
					 else 
						if (outputBuffer != null && bufferInfo.size > 0) 
							int len = bufferInfo.size;
							outputBuffer.get(aacData, 0, bufferInfo.size);
							//设置header
							AacUtil.setADTSPacketLen(channelCount, aacHeader,
									len + AAC_HEADER_SIZE);
							totalSize += len;
							out.write(aacHeader);
							out.write(aacData, 0, len);
						
					
					// bufferFormat is identical to outputFormat
					// outputBuffer is ready to be processed or rendered.
					mCodec.releaseOutputBuffer(outputBufferId, false);
				 else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) 
					// Subsequent data will conform to new format.
					// Can ignore if using getOutputFormat(outputBufferId)
					mediaFormat = mCodec.getOutputFormat(); // option B
				
			
		 catch (IOException | InterruptedException e) 
			e.printStackTrace();
		 finally 
			try 
				out.close();
			 catch (IOException e) 
				e.printStackTrace();
			
		
		mBufferPool.clear();
		mBufferPool = null;
		mCodec.stop();
		mCodec.release();
		mCodec = null;
	

6 停止编码,释放

mCodec.stop();
mCodec.release();
mCodec = null;

其中上述的 mBufferPool用来管理pcm数据,如果嫌麻烦可以在AudioRecord读取后,直接进行操作。

源代码路径https://github.com/pyzhangfan/audio-sample-app

以上是关于Android 多媒体——MediaCodec编码AAC的主要内容,如果未能解决你的问题,请参考以下文章

Android媒体解码MediaCodec MediaExtractor学习

Android MediaCodec

android硬编解码MediaCodec

Android 媒体编解码器(转)

Android 音视频 - MediaCodec 编解码音视频

简单高效易用Windows/Linux/ARM/Android/iOS平台实现RTMP推送组件EasyRTMP-Android MediaCodec硬编码流程介绍