安卓音频转码-新闻详情

安卓音频转码


发布时间:2017-11-28责任编辑:朱明 浏览:1471


一、概述

由于项目的需要,需要将mp3文件转码为aac音频文件,起初打算移植FFmpeg到项目中,无奈FFmpeg过于庞大,项目中的音频转码只是一个辅助util,并不是主要功能。所以打算用MediaCodec来实现这一需求。

二、转码实现原理

本篇文章以mp3转码成aac为例,转码实现原理:mp3->pcm->aac,首先将mp3解码成PCM,再将PCM编码成aac格式的音频文件。

PCM:可以将它理解为,未经过压缩的数字信号,mp3、aac等 理解为pcm压缩后的文件。播放器在播放mp3、aac等文件时要先将mp3等文件解码成PCM数据,然后再将PCM送到底层去处理播放

此处就好比 我要将rar压缩包内的文件改用zip压缩,->解压rar-->文件-->压缩zip

三、遇到问题

1、编解码过程中会卡主:此为参数设置引起的,下面代码中会提到

2、编码的aac音频不能播放:在编码过程中需要为aac音频添加ADTS  head,代码中有体现

3、最头痛的,转码速度太慢,转码一首歌长达5分钟。

      此问题究其原因,是由于MediaExtractor每次喂给MediaCodec的数据太少,每次只喂一帧的数据,通过打印的log发现size不到1k,严重影响效率,后来尝试不用MediaExtractor去读数据,直接开流 BufferInputStream设置200k ,每次循环喂给MediaCodec200k的数据 , 最终!!! 在三星手机上完美运行,一次转码由5分钟,直接降到10多秒,但是,注意但是!!!  此方法在其他测试机上全报错,泪奔。

  无奈,开线程,将解码和编码分别放到两个线程里面去执行,并且让MediaExtractor读取多次数据后再交给MediaCodec去处理,此方法转码一首歌大约1分钟左右

四、代码实现

1)初始化解码器

  MediaExtractor:可用于分离视频文件的音轨和视频轨道,如果你只想要视频,那么用selectTrack方法选中视频轨道,然后用readSampleData读出数据,这样你就得到了一个没有声音的视频。此处我们传入的是一个音频文件(mp3),所以也就只有一个轨道,音频轨道

 mime:用来表示媒体文件的格式 mp3为audio/mpeg;aac为audio/mp4a-latm;mp4为video/mp4v-es 此处注意前缀 音频前缀为audio,视频前缀为video 我们可用此区别区分媒体文件内的音频轨道和视频轨道

mime的各种类型定义在MediaFormat静态常量中

  MediaCodec.createDecoderByType(mime) 创建对应格式的解码器  要解码mp3 那么mime="audio/mpeg" 或者MediaFormat.MIMETYPE_AUDIO_MPEG其它同理

 

[java] view plain copy

1.      /** 

2.           * 初始化解码器 

3.           */  

4.          private void initMediaDecode() {  

5.              try {  

6.                  mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道  

7.                  mediaExtractor.setDataSource(srcPath);//媒体文件的位置  

8.                  for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道  

9.                      MediaFormat format = mediaExtractor.getTrackFormat(i);  

10.                  String mime = format.getString(MediaFormat.KEY_MIME);  

11.                  if (mime.startsWith("audio")) {//获取音频轨道  

12.  //                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);  

13.                      mediaExtractor.selectTrack(i);//选择此音频轨道  

14.                      mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器  

15.                      mediaDecode.configure(format, nullnull0);  

16.                      break;  

17.                  }  

18.              }  

19.          } catch (IOException e) {  

20.              e.printStackTrace();  

21.          }  

22.    

23.          if (mediaDecode == null) {  

24.              Log.e(TAG, "create mediaDecode failed");  

25.              return;  

26.          }  

27.          mediaDecode.start();//启动MediaCodec ,等待传入数据  

28.          decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据  

29.          decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据  

30.          decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息  

31.          showLog("buffers:" + decodeInputBuffers.length);  

32.      }  

 

2)初始化编码器 

编码器的创建于解码器的类似,只不过解码器的MediaFormat直接在音频文件内获取就可以了,编码器的MediaFormat需要自己来创建

[java] view plain copy

1.      /** 

2.       * 初始化AAC编码器 

3.       */  

4.      private void initAACMediaEncode() {  

5.          try {  

6.              MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 441002);//参数对应-> mime type、采样率、声道数  

7.              encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率  

8.              encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);  

9.              encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);//作用于inputBuffer的大小  

10.          mediaEncode = MediaCodec.createEncoderByType(encodeType);  

11.          mediaEncode.configure(encodeFormat, nullnull, MediaCodec.CONFIGURE_FLAG_ENCODE);  

12.      } catch (IOException e) {  

13.          e.printStackTrace();  

14.      }  

15.    

16.      if (mediaEncode == null) {  

17.          Log.e(TAG, "create mediaEncode failed");  

18.          return;  

19.      }  

20.      mediaEncode.start();  

21.      encodeInputBuffers=mediaEncode.getInputBuffers();  

22.      encodeOutputBuffers=mediaEncode.getOutputBuffers();  

23.      encodeBufferInfo=new MediaCodec.BufferInfo();  

24.  }  

 

3)解码的实现

 

[java] view plain copy

1.      /** 

2.           * 解码{@link #srcPath}音频文件 得到PCM数据块 

3.           * @return 是否解码完所有数据 

4.           */  

5.          private void srcAudioFormatToPCM() {  

6.              for (int i = 0; i < decodeInputBuffers.length-1; i++) {  

7.              int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可