Android MediaCodec硬解码AAC音频文件并播放

来源:互联网 发布:如何做好淘宝售后服务 编辑:程序博客网 时间:2024/06/10 22:58
 
在这里简单介绍一下,如何利用Android MediaCodec解码AAC音频文件或者实时AAC音频帧并通过AudioTrack来播放。主要的思路就是从文件或者网络获取一帧帧的AAC的数据,送入解码器解码后播放。

封装AudioTrack

AudioTrack主要是用来进行主要是用来播放声音的,但是只能播放PCM格式的音频流。这里主要是简单的对AudioTrack进行了封装,加入了一些异常判断:

/** * Created by ZhangHao on 2017/5/10. * 播放pcm数据 */public class MyAudioTrack {    private int mFrequency;// 采样率    private int mChannel;// 声道    private int mSampBit;// 采样精度    private AudioTrack mAudioTrack;    public MyAudioTrack(int frequency, int channel, int sampbit) {        this.mFrequency = frequency;        this.mChannel = channel;        this.mSampBit = sampbit;    }    /**     * 初始化     */    public void init() {        if (mAudioTrack != null) {            release();        }        // 获得构建对象的最小缓冲区大小        int minBufSize = getMinBufferSize();        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,                mFrequency, mChannel, mSampBit, minBufSize, AudioTrack.MODE_STREAM);        mAudioTrack.play();    }    /**     * 释放资源     */    public void release() {        if (mAudioTrack != null) {            mAudioTrack.stop();            mAudioTrack.release();        }    }    /**     * 将解码后的pcm数据写入audioTrack播放     *     * @param data   数据     * @param offset 偏移     * @param length 需要播放的长度     */    public void playAudioTrack(byte[] data, int offset, int length) {        if (data == null || data.length == 0) {            return;        }        try {            mAudioTrack.write(data, offset, length);        } catch (Exception e) {            Log.e("MyAudioTrack", "AudioTrack Exception : " + e.toString());        }    }    public int getMinBufferSize() {        return AudioTrack.getMinBufferSize(mFrequency,                mChannel, mSampBit);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

这里简单介绍一下,在AudioTrack构造方法AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)里几个变量的含义: 
1.streamType:指定流的类型,主要包括以下几种: 
- STREAM_ALARM:警告声 
- STREAM_MUSCI:音乐声 
- STREAM_RING:铃声 
- STREAM_SYSTEM:系统声音 
- STREAM_VOCIE_CALL:电话声音 
因为android系统对不同的声音的管理是分开的,所以这个参数的作用就是设置AudioTrack播放的声音类型。

2.sampleRateInHz : 采样率

3.channelConfig : 声道

4.audioFormat : 采样精度

5.bufferSizeInBytes :缓冲区大小,可以通过AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)来获取

6.mode : MODE_STATIC和MODE_STREAM: 
- MODE_STATIC : 直接把所有的数据加载到缓存区,不需要多次write,一般用于占用内存小,延时要求高的情况 
- MODE_STREAM : 需要多次write,一般用于像从网络获取数据或者实时解码的情况,本次的例子就是这种情况。

我这里只是简单的介绍,大家可以去网上找更为详细的介绍。

AAC解码器

这里主要对MediaCodec进行封装,实现一帧帧去解码AAC。

/** * Created by ZhangHao on 2017/5/17. * 用于aac音频解码 */public class AACDecoderUtil {    private static final String TAG = "AACDecoderUtil";    //声道数    private static final int KEY_CHANNEL_COUNT = 2;    //采样率    private static final int KEY_SAMPLE_RATE = 48000;    //用于播放解码后的pcm    private MyAudioTrack mPlayer;    //解码器    private MediaCodec mDecoder;    //用来记录解码失败的帧数    private int count = 0;    /**     * 初始化所有变量     */    public void start() {        prepare();    }    /**     * 初始化解码器     *     * @return 初始化失败返回false,成功返回true     */    public boolean prepare() {        // 初始化AudioTrack        mPlayer = new MyAudioTrack(KEY_SAMPLE_RATE, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);        mPlayer.init();        try {            //需要解码数据的类型            String mine = "audio/mp4a-latm";            //初始化解码器            mDecoder = MediaCodec.createDecoderByType(mine);            //MediaFormat用于描述音视频数据的相关参数            MediaFormat mediaFormat = new MediaFormat();            //数据类型            mediaFormat.setString(MediaFormat.KEY_MIME, mine);            //声道个数            mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, KEY_CHANNEL_COUNT);            //采样率            mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, KEY_SAMPLE_RATE);            //比特率            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);            //用来标记AAC是否有adts头,1->有            mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);            //用来标记aac的类型            mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);            //ByteBuffer key(暂时不了解该参数的含义,但必须设置)            byte[] data = new byte[]{(byte) 0x11, (byte) 0x90};            ByteBuffer csd_0 = ByteBuffer.wrap(data);            mediaFormat.setByteBuffer("csd-0", csd_0);            //解码器配置            mDecoder.configure(mediaFormat, null, null, 0);        } catch (IOException e) {            e.printStackTrace();            return false;        }        if (mDecoder == null) {            return false;        }        mDecoder.start();        return true;    }    /**     * aac解码+播放     */    public void decode(byte[] buf, int offset, int length) {        //输入ByteBuffer        ByteBuffer[] codecInputBuffers = mDecoder.getInputBuffers();        //输出ByteBuffer        ByteBuffer[] codecOutputBuffers = mDecoder.getOutputBuffers();        //等待时间,0->不等待,-1->一直等待        long kTimeOutUs = 0;        try {            //返回一个包含有效数据的input buffer的index,-1->不存在            int inputBufIndex = mDecoder.dequeueInputBuffer(kTimeOutUs);            if (inputBufIndex >= 0) {                //获取当前的ByteBuffer                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];                //清空ByteBuffer                dstBuf.clear();                //填充数据                dstBuf.put(buf, offset, length);                //将指定index的input buffer提交给解码器                mDecoder.queueInputBuffer(inputBufIndex, 0, length, 0, 0);            }            //编解码器缓冲区            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();            //返回一个output buffer的index,-1->不存在            int outputBufferIndex = mDecoder.dequeueOutputBuffer(info, kTimeOutUs);            if (outputBufferIndex < 0) {                //记录解码失败的次数                count++;            }            ByteBuffer outputBuffer;            while (outputBufferIndex >= 0) {                //获取解码后的ByteBuffer                outputBuffer = codecOutputBuffers[outputBufferIndex];                //用来保存解码后的数据                byte[] outData = new byte[info.size];                outputBuffer.get(outData);                //清空缓存                outputBuffer.clear();                //播放解码后的数据                mPlayer.playAudioTrack(outData, 0, info.size);                //释放已经解码的buffer                mDecoder.releaseOutputBuffer(outputBufferIndex, false);                //解码未解完的数据                outputBufferIndex = mDecoder.dequeueOutputBuffer(info, kTimeOutUs);            }        } catch (Exception e) {            Log.e(TAG, e.toString());            e.printStackTrace();        }    }    //返回解码失败的次数    public int getCount() {        return count;    }    /**     * 释放资源     */    public void stop() {        try {            if (mPlayer != null) {                mPlayer.release();                mPlayer = null;            }            if (mDecoder != null) {                mDecoder.stop();                mDecoder.release();            }        } catch (Exception e) {            e.printStackTrace();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147

其实这里和我之前利用MediaCodec解码H264很类似,主要就是在因为解码数据类型不同,所以初始化时有区别。还有一点就是解码H624时,直接将解码后数据利用surface显示,而解码aac是将解码后的数据取出来,再利用AudioTrack播放。

读取aac文件

这里是利用线程读aac文件,获得一帧帧的aac帧数据,然后送入解码器播放。

/** * Created by ZhangHao on 2017/4/18. * 播放aac音频文件 */public class ReadAACFileThread extends Thread {    //音频解码器    private AACDecoderUtil audioUtil;    //文件路径    private String filePath;    //文件读取完成标识    private boolean isFinish = false;    //这个值用于找到第一个帧头后,继续寻找第二个帧头,如果解码失败可以尝试缩小这个值    private int FRAME_MIN_LEN = 50;    //一般AAC帧大小不超过200k,如果解码失败可以尝试增大这个值    private static int FRAME_MAX_LEN = 100 * 1024;    //根据帧率获取的解码每帧需要休眠的时间,根据实际帧率进行操作    private int PRE_FRAME_TIME = 1000 / 50;    //记录获取的帧数    private int count = 0;    public ReadAACFileThread(String path) {        this.audioUtil = new AACDecoderUtil();        this.filePath = path;        this.audioUtil.start();    }    @Override    public void run() {        super.run();        File file = new File(filePath);        //判断文件是否存在        if (file.exists()) {            try {                FileInputStream fis = new FileInputStream(file);                //保存完整数据帧                byte[] frame = new byte[FRAME_MAX_LEN];                //当前帧长度                int frameLen = 0;                //每次从文件读取的数据                byte[] readData = new byte[10 * 1024];                //开始时间                long startTime = System.currentTimeMillis();                //循环读取数据                while (!isFinish) {                    if (fis.available() > 0) {                        int readLen = fis.read(readData);                        //当前长度小于最大值                        if (frameLen + readLen < FRAME_MAX_LEN) {                            //将readData拷贝到frame                            System.arraycopy(readData, 0, frame, frameLen, readLen);                            //修改frameLen                            frameLen += readLen;                            //寻找第一个帧头                            int headFirstIndex = findHead(frame, 0, frameLen);                            while (headFirstIndex >= 0 && isHead(frame, headFirstIndex)) {                                //寻找第二个帧头                                int headSecondIndex = findHead(frame, headFirstIndex + FRAME_MIN_LEN, frameLen);                                //如果第二个帧头存在,则两个帧头之间的就是一帧完整的数据                                if (headSecondIndex > 0 && isHead(frame, headSecondIndex)) {                                    //视频解码                                    count++;                                    Log.e("ReadAACFileThread", "Length : " + (headSecondIndex - headFirstIndex));                                    audioUtil.decode(frame, headFirstIndex, headSecondIndex - headFirstIndex);                                    //截取headSecondIndex之后到frame的有效数据,并放到frame最前面                                    byte[] temp = Arrays.copyOfRange(frame, headSecondIndex, frameLen);                                    System.arraycopy(temp, 0, frame, 0, temp.length);                                    //修改frameLen的值                                    frameLen = temp.length;                                    //线程休眠                                    sleepThread(startTime, System.currentTimeMillis());                                    //重置开始时间                                    startTime = System.currentTimeMillis();                                    //继续寻找数据帧                                    headFirstIndex = findHead(frame, 0, frameLen);                                } else {                                    //找不到第二个帧头                                    headFirstIndex = -1;                                }                            }                        } else {                            //如果长度超过最大值,frameLen置0                            frameLen = 0;                        }                    } else {                        //文件读取结束                        isFinish = true;                    }                }            } catch (Exception e) {                e.printStackTrace();            }            Log.e("ReadAACFileThread", "AllCount:" + count + "Error Count : " + audioUtil.getCount());        } else {            Log.e("ReadH264FileThread", "File not found");        }        audioUtil.stop();    }    /**     * 寻找指定buffer中AAC帧头的开始位置     *     * @param startIndex 开始的位置     * @param data       数据     * @param max        需要检测的最大值     * @return     */    private int findHead(byte[] data, int startIndex, int max) {        int i;        for (i = startIndex; i <= max; i++) {            //发现帧头            if (isHead(data, i))                break;        }        //检测到最大值,未发现帧头        if (i == max) {            i = -1;        }        return i;    }    /**     * 判断aac帧头     */    private boolean isHead(byte[] data, int offset) {        boolean result = false;        if (data[offset] == (byte) 0xFF && data[offset + 1] == (byte) 0xF1                && data[offset + 3] == (byte) 0x80) {            result = true;        }        return result;    }    //修眠    private void sleepThread(long startTime, long endTime) {        //根据读文件和解码耗时,计算需要休眠的时间        long time = PRE_FRAME_TIME - (endTime - startTime);        if (time > 0) {            try {                Thread.sleep(time);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147

这里没有太多的东西,就是通过帧头来判断aac帧,并截取每帧数据送入解码器。我这里只是取巧做了简单的判断,对帧头的判断并不一定满足所有的aac帧头,大家可以根据实际的情况自行修改。

结语

其实,实现分离音频帧,利用MediaExtractor这个类就可以实现,但是因为我实际的数据源是来自网络,所以才会demo才会复杂一点。

阅读全文
0 0