• android MediaCodec 音频编解码的实现——转码


    原文地址:http://blog.csdn.net/tinsanmr/article/details/51049179

    从今天开始 每周不定期更新博客,把这一周在工作与学习中遇到的问题做个总结。俗话说:好记性不如写博客,善于总结的人才能走的更远。写博客这种利人利己的好处我就不一 一列举了,总之,谁做谁知道,哈哈。在文章中如果有什么问题或者错误,欢迎各位的讨论和指正。好了,步入正题,来看看我们今天要讲的MediaCodec

    一、概述

    由于项目的需要,需要将mp3文件转码为aac音频文件,起初打算移植FFmpeg到项目中,无奈FFmpeg过于庞大,项目中的音频转码只是一个辅助util,并不是主要功能。所以打算用MediaCodec来实现这一需求。网上关于MediaCodec的文章少的可怜,写demo的过程中踩到了无数的坑。不过,在http://blog.csdn.net/tn0521/article/details/44980183 这篇文章的指引下,终于在耳机中听到了那美妙的旋律,激动的我把这一首歌听了一星期,因为他出自我的手。哈哈,开个玩笑!在此对这片文章的原创表示感谢,这也是我决定写博客的原因之一,利人利己。

    二、转码实现原理

    本篇文章以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其它同理

     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, null, null, 0);
    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需要自己来创建

     1 /**
     2  * 初始化AAC编码器
     3  */
     4 private void initAACMediaEncode() {
     5     try {
     6         MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> 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, null, null, 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 }
    25  

    3)解码的实现

     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);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧
     8         if (inputIndex < 0) {
     9             codeOver =true;
    10             return;
    11         }
    12 
    13         ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer
    14         inputBuffer.clear();//清空之前传入inputBuffer内的数据
    15         int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
    16         if (sampleSize <0) {//小于0 代表所有数据已读取完成
    17                 codeOver=true;
    18             }else {
    19                 mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据
    20                 mediaExtractor.advance();//MediaExtractor移动到下一取样处
    21                 decodeSize+=sampleSize;
    22             }
    23         }
    24 
    25         //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒
    26         //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待
    27         int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);
    28 
    29 //        showLog("decodeOutIndex:" + outputIndex);
    30         ByteBuffer outputBuffer;
    31         byte[] chunkPCM;
    32         while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据
    33             outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer
    34             chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小
    35             outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中
    36             outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据
    37             putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码
    38             mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据
    39             outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束
    40         }
    41 
    42     }

    4)编码的实现

     1 /**
     2      * 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath}
     3      */
     4     private void dstAudioFormatFromPCM() {
     5 
     6         int inputIndex;
     7         ByteBuffer inputBuffer;
     8         int outputIndex;
     9         ByteBuffer outputBuffer;
    10         byte[] chunkAudio;
    11         int outBitSize;
    12         int outPacketSize;
    13         byte[] chunkPCM;
    14 
    15 //        showLog("doEncode");
    16         for (int i = 0; i < encodeInputBuffers.length-1; i++) {
    17             chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上
    18             if (chunkPCM == null) {
    19                 break;
    20             }
    21             inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器
    22             inputBuffer = encodeInputBuffers[inputIndex];//同解码器
    23             inputBuffer.clear();//同解码器
    24             inputBuffer.limit(chunkPCM.length);
    25             inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer
    26             mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码
    27         }
    28 
    29             outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
    30             while (outputIndex >= 0) {//同解码器
    31 
    32                 outBitSize=encodeBufferInfo.size;
    33                 outPacketSize=outBitSize+7;//7为ADTS头部的大小
    34                 outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
    35                 outputBuffer.position(encodeBufferInfo.offset);
    36                 outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
    37                 chunkAudio = new byte[outPacketSize];
    38                 addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上
    39                 outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
    40                 outputBuffer.position(encodeBufferInfo.offset);
    41 //                showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());
    42                 try {
    43                     bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac 
    44                 } catch (IOException e) {
    45                     e.printStackTrace();
    46                 }
    47 
    48                 mediaEncode.releaseOutputBuffer(outputIndex,false);
    49                 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
    50 
    51             }
    52 
    53     }
    54  
    55 
    56  
    57 
    58 /**
    59      * 添加ADTS头
    60      * @param packet
    61      * @param packetLen
    62      */
    63     private void addADTStoPacket(byte[] packet, int packetLen) {
    64         int profile = 2; // AAC LC
    65         int freqIdx = 4; // 44.1KHz
    66         int chanCfg = 2; // CPE
    67 
    68 
    69 // fill in ADTS data
    70         packet[0] = (byte) 0xFF;
    71         packet[1] = (byte) 0xF9;
    72         packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
    73         packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
    74         packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
    75         packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
    76         packet[6] = (byte) 0xFC;
    77     }
    78  

    5)完整代码

      1 package com.example.tinsan.mediaparser;
      2 
      3 
      4         import android.media.MediaCodec;
      5         import android.media.MediaCodecInfo;
      6         import android.media.MediaExtractor;
      7         import android.media.MediaFormat;
      8         import android.util.Log;
      9 
     10         import java.io.BufferedInputStream;
     11         import java.io.BufferedOutputStream;
     12         import java.io.File;
     13         import java.io.FileInputStream;
     14         import java.io.FileNotFoundException;
     15         import java.io.FileOutputStream;
     16         import java.io.IOException;
     17         import java.nio.ByteBuffer;
     18         import java.util.ArrayList;
     19 
     20 /**
     21  * Created by senshan_wang on 2016/3/31.
     22  */
     23 public class AudioCodec {
     24 
     25     private static final String TAG = "AudioCodec";
     26     private String encodeType;
     27     private String srcPath;
     28     private String dstPath;
     29     private MediaCodec mediaDecode;
     30     private MediaCodec mediaEncode;
     31     private MediaExtractor mediaExtractor;
     32     private ByteBuffer[] decodeInputBuffers;
     33     private ByteBuffer[] decodeOutputBuffers;
     34     private ByteBuffer[] encodeInputBuffers;
     35     private ByteBuffer[] encodeOutputBuffers;
     36     private MediaCodec.BufferInfo decodeBufferInfo;
     37     private MediaCodec.BufferInfo encodeBufferInfo;
     38     private FileOutputStream fos;
     39     private BufferedOutputStream bos;
     40     private FileInputStream fis;
     41     private BufferedInputStream bis;
     42     private ArrayList<byte[]> chunkPCMDataContainer;//PCM数据块容器
     43     private OnCompleteListener onCompleteListener;
     44     private OnProgressListener onProgressListener;
     45     private long fileTotalSize;
     46     private long decodeSize;
     47 
     48 
     49     public static AudioCodec newInstance() {
     50         return new AudioCodec();
     51     }
     52 
     53     /**
     54      * 设置编码器类型
     55      * @param encodeType
     56      */
     57     public void setEncodeType(String encodeType) {
     58         this.encodeType=encodeType;
     59     }
     60 
     61     /**
     62      * 设置输入输出文件位置
     63      * @param srcPath
     64      * @param dstPath
     65      */
     66     public void setIOPath(String srcPath, String dstPath) {
     67         this.srcPath=srcPath;
     68         this.dstPath=dstPath;
     69     }
     70 
     71     /**
     72      * 此类已经过封装
     73      * 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作
     74      */
     75     public void prepare() {
     76 
     77         if (encodeType == null) {
     78             throw new IllegalArgumentException("encodeType can't be null");
     79         }
     80 
     81         if (srcPath == null) {
     82             throw new IllegalArgumentException("srcPath can't be null");
     83         }
     84 
     85         if (dstPath == null) {
     86             throw new IllegalArgumentException("dstPath can't be null");
     87         }
     88 
     89         try {
     90             fos = new FileOutputStream(new File(dstPath));
     91             bos = new BufferedOutputStream(fos,200*1024);
     92             File file = new File(srcPath);
     93             fileTotalSize=file.length();
     94         } catch (IOException e) {
     95             e.printStackTrace();
     96         }
     97         chunkPCMDataContainer= new ArrayList<>();
     98         initMediaDecode();//解码器
     99 
    100         if (encodeType == MediaFormat.MIMETYPE_AUDIO_AAC) {
    101             initAACMediaEncode();//AAC编码器
    102         }else if (encodeType == MediaFormat.MIMETYPE_AUDIO_MPEG) {
    103             initMPEGMediaEncode();//mp3编码器
    104         }
    105 
    106     }
    107 
    108     /**
    109      * 初始化解码器
    110      */
    111     private void initMediaDecode() {
    112         try {
    113             mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
    114             mediaExtractor.setDataSource(srcPath);//媒体文件的位置
    115             for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道
    116                 MediaFormat format = mediaExtractor.getTrackFormat(i);
    117                 String mime = format.getString(MediaFormat.KEY_MIME);
    118                 if (mime.startsWith("audio")) {//获取音频轨道
    119 //                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);
    120                     mediaExtractor.selectTrack(i);//选择此音频轨道
    121                     mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器
    122                     mediaDecode.configure(format, null, null, 0);
    123                     break;
    124                 }
    125             }
    126         } catch (IOException e) {
    127             e.printStackTrace();
    128         }
    129 
    130         if (mediaDecode == null) {
    131             Log.e(TAG, "create mediaDecode failed");
    132             return;
    133         }
    134         mediaDecode.start();//启动MediaCodec ,等待传入数据
    135         decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据
    136         decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据
    137         decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
    138         showLog("buffers:" + decodeInputBuffers.length);
    139     }
    140 
    141 
    142     /**
    143      * 初始化AAC编码器
    144      */
    145     private void initAACMediaEncode() {
    146         try {
    147             MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数
    148             encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率
    149             encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    150             encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);
    151             mediaEncode = MediaCodec.createEncoderByType(encodeType);
    152             mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    153         } catch (IOException e) {
    154             e.printStackTrace();
    155         }
    156 
    157         if (mediaEncode == null) {
    158             Log.e(TAG, "create mediaEncode failed");
    159             return;
    160         }
    161         mediaEncode.start();
    162         encodeInputBuffers=mediaEncode.getInputBuffers();
    163         encodeOutputBuffers=mediaEncode.getOutputBuffers();
    164         encodeBufferInfo=new MediaCodec.BufferInfo();
    165     }
    166 
    167     /**
    168      * 初始化MPEG编码器
    169      */
    170     private void initMPEGMediaEncode() {
    171         
    172     }
    173 
    174     private boolean codeOver = false;
    175     /**
    176      * 开始转码
    177      * 音频数据{@link #srcPath}先解码成PCM  PCM数据在编码成想要得到的{@link #encodeType}音频格式
    178      * mp3->PCM->aac
    179      */
    180     public void startAsync() {
    181         showLog("start");
    182 
    183         new Thread(new DecodeRunnable()).start();
    184         new Thread(new EncodeRunnable()).start();
    185 
    186     }
    187 
    188     /**
    189      * 将PCM数据存入{@link #chunkPCMDataContainer}
    190      * @param pcmChunk PCM数据块
    191      */
    192     private void putPCMData(byte[] pcmChunk) {
    193         synchronized (AudioCodec.class) {//记得加锁
    194             chunkPCMDataContainer.add(pcmChunk);
    195         }
    196     }
    197 
    198     /**
    199      * 在Container中{@link #chunkPCMDataContainer}取出PCM数据
    200      * @return PCM数据块
    201      */
    202     private byte[] getPCMData() {
    203         synchronized (AudioCodec.class) {//记得加锁
    204             showLog("getPCM:"+chunkPCMDataContainer.size());
    205             if (chunkPCMDataContainer.isEmpty()) {
    206                 return null;
    207             }
    208 
    209             byte[] pcmChunk = chunkPCMDataContainer.get(0);//每次取出index 0 的数据
    210             chunkPCMDataContainer.remove(pcmChunk);//取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存
    211             return pcmChunk;
    212         }
    213     }
    214 
    215 
    216     /**
    217      * 解码{@link #srcPath}音频文件 得到PCM数据块
    218      * @return 是否解码完所有数据
    219      */
    220     private void srcAudioFormatToPCM() {
    221         for (int i = 0; i < decodeInputBuffers.length-1; i++) {
    222         int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧
    223         if (inputIndex < 0) {
    224             codeOver =true;
    225             return;
    226         }
    227 
    228         ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer
    229         inputBuffer.clear();//清空之前传入inputBuffer内的数据
    230         int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
    231         if (sampleSize <0) {//小于0 代表所有数据已读取完成
    232                 codeOver=true;
    233             }else {
    234                 mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据
    235                 mediaExtractor.advance();//MediaExtractor移动到下一取样处
    236                 decodeSize+=sampleSize;
    237             }
    238         }
    239 
    240         //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒
    241         //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待
    242         int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);
    243 
    244 //        showLog("decodeOutIndex:" + outputIndex);
    245         ByteBuffer outputBuffer;
    246         byte[] chunkPCM;
    247         while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据
    248             outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer
    249             chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小
    250             outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中
    251             outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据
    252             putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码
    253             mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据
    254             outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束
    255         }
    256 
    257     }
    258 
    259     /**
    260      * 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath}
    261      */
    262     private void dstAudioFormatFromPCM() {
    263 
    264         int inputIndex;
    265         ByteBuffer inputBuffer;
    266         int outputIndex;
    267         ByteBuffer outputBuffer;
    268         byte[] chunkAudio;
    269         int outBitSize;
    270         int outPacketSize;
    271         byte[] chunkPCM;
    272 
    273 //        showLog("doEncode");
    274         for (int i = 0; i < encodeInputBuffers.length-1; i++) {
    275             chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上
    276             if (chunkPCM == null) {
    277                 break;
    278             }
    279             inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器
    280             inputBuffer = encodeInputBuffers[inputIndex];//同解码器
    281             inputBuffer.clear();//同解码器
    282             inputBuffer.limit(chunkPCM.length);
    283             inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer
    284             mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码
    285         }
    286 
    287             outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
    288             while (outputIndex >= 0) {//同解码器
    289 
    290                 outBitSize=encodeBufferInfo.size;
    291                 outPacketSize=outBitSize+7;//7为ADTS头部的大小
    292                 outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
    293                 outputBuffer.position(encodeBufferInfo.offset);
    294                 outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
    295                 chunkAudio = new byte[outPacketSize];
    296                 addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上
    297                 outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
    298                 outputBuffer.position(encodeBufferInfo.offset);
    299 //                showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());
    300                 try {
    301                     bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac
    302                 } catch (IOException e) {
    303                     e.printStackTrace();
    304                 }
    305 
    306                 mediaEncode.releaseOutputBuffer(outputIndex,false);
    307                 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
    308 
    309             }
    310     }
    311 
    312     /**
    313      * 添加ADTS头
    314      * @param packet
    315      * @param packetLen
    316      */
    317     private void addADTStoPacket(byte[] packet, int packetLen) {
    318         int profile = 2; // AAC LC
    319         int freqIdx = 4; // 44.1KHz
    320         int chanCfg = 2; // CPE
    321 
    322 
    323 // fill in ADTS data
    324         packet[0] = (byte) 0xFF;
    325         packet[1] = (byte) 0xF9;
    326         packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
    327         packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
    328         packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
    329         packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
    330         packet[6] = (byte) 0xFC;
    331     }
    332 
    333     /**
    334      * 释放资源
    335      */
    336     public void release() {
    337         try {
    338             if (bos != null) {
    339                 bos.flush();
    340             }
    341         } catch (IOException e) {
    342             e.printStackTrace();
    343         }finally {
    344             if (bos != null) {
    345                 try {
    346                     bos.close();
    347                 } catch (IOException e) {
    348                     e.printStackTrace();
    349                 }finally {
    350                     bos=null;
    351                 }
    352             }
    353         }
    354 
    355         try {
    356             if (fos != null) {
    357                 fos.close();
    358             }
    359         } catch (IOException e) {
    360             e.printStackTrace();
    361         }finally {
    362             fos=null;
    363         }
    364 
    365         if (mediaEncode != null) {
    366             mediaEncode.stop();
    367             mediaEncode.release();
    368             mediaEncode=null;
    369         }
    370 
    371         if (mediaDecode != null) {
    372             mediaDecode.stop();
    373             mediaDecode.release();
    374             mediaDecode=null;
    375         }
    376 
    377         if (mediaExtractor != null) {
    378             mediaExtractor.release();
    379             mediaExtractor=null;
    380         }
    381 
    382         if (onCompleteListener != null) {
    383             onCompleteListener=null;
    384         }
    385 
    386         if (onProgressListener != null) {
    387             onProgressListener=null;
    388         }
    389         showLog("release");
    390     }
    391 
    392     /**
    393      * 解码线程
    394      */
    395     private class DecodeRunnable implements Runnable{
    396 
    397         @Override
    398         public void run() {
    399             while (!codeOver) {
    400                 srcAudioFormatToPCM();
    401             }
    402         }
    403     }
    404 
    405     /**
    406      * 编码线程
    407      */
    408     private class EncodeRunnable implements Runnable {
    409 
    410         @Override
    411         public void run() {
    412             long t=System.currentTimeMillis();
    413             while (!codeOver || !chunkPCMDataContainer.isEmpty()) {
    414                 dstAudioFormatFromPCM();
    415             }
    416             if (onCompleteListener != null) {
    417                 onCompleteListener.completed();
    418             }
    419             showLog("size:"+fileTotalSize+" decodeSize:"+decodeSize+"time:"+(System.currentTimeMillis()-t));
    420         }
    421     }
    422 
    423 
    424     /**
    425      * 转码完成回调接口
    426      */
    427     public interface OnCompleteListener{
    428         void completed();
    429     }
    430 
    431     /**
    432      * 转码进度监听器
    433      */
    434     public interface OnProgressListener{
    435         void progress();
    436     }
    437 
    438     /**
    439      * 设置转码完成监听器
    440      * @param onCompleteListener
    441      */
    442     public void setOnCompleteListener(OnCompleteListener onCompleteListener) {
    443         this.onCompleteListener=onCompleteListener;
    444     }
    445 
    446     public void setOnProgressListener(OnProgressListener onProgressListener) {
    447         this.onProgressListener = onProgressListener;
    448     }
    449 
    450     private void showLog(String msg) {
    451         Log.e("AudioCodec", msg);
    452     }
    453 }
    AudioCodec

    6)调用

    此类已经过封装,可通过下面的方法调用

    String path=Environment.getExternalStorageDirectory().getAbsolutePath();
    AudioCodec audioCodec=AudioCodec.newInstance();
    audioCodec.setEncodeType(MediaFormat.MIMETYPE_AUDIO_MPEG);
    audioCodec.setIOPath(path + "/codec.aac", path + "/encode.mp3");
    audioCodec.prepare();
    audioCodec.startAsync();
    audioCodec.setOnCompleteListener(new AudioCodec.OnCompleteListener() {
        @Override
        public void completed() {
            audioCodec.release();
        }
    });
  • 相关阅读:
    20145320 《信息安全系统设计基础》第2周学习总结
    20145320 《信息安全系统设计基础》第一周学习总结
    20145320 周岐浩《信息安全系统设计基础》第0周学习总结
    暑假CTF训练一
    #20145320课程总结
    20145320 《Java程序设计》第10周学习总结
    20145320《Java程序设计》第五次实验报告
    20145320《Java程序设计》第9周学习总结
    20145320《Java程序设计》第四次实验报告
    20145320《Java程序设计》第三次实验报告
  • 原文地址:https://www.cnblogs.com/Sharley/p/5964490.html
Copyright © 2020-2023  润新知