Android MediaCodec硬解码AAC音频文件(实时AAC音频帧)并播放

来源:互联网 发布:单例模式 java代码 编辑:程序博客网 时间:2024/05/16 05:32
今天在这里简单介绍一下,如何利用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,一般用于像从网络获取数据或者实时解码的情况,本次的例子就是这种情况。

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

public final void setDataSource(@NonNull String path) throws IOException {    nativeSetDataSource(            MediaHTTPService.createHttpServiceBinderIfNecessary(path),            path,            null,            null);}
AAC解码器

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

若要解码AAC-ELD:

1、初始化修改为mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, 
MediaCodecInfo.CodecProfileLevel.AACObjectELD);
2、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
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 老公把老婆微信屏蔽了老婆该怎么办 魅族手机有质量问题不给退款怎么办 屏幕点不动锁屏密码无法点怎么办 在嘴巴和鼻翼周围长痘该怎么办 小孩嘴巴被蚊虫咬的红肿怎么办 每次洗头都掉好多头发该怎么办 关于宝宝脾不好胃口却很好怎么办 高中生掉发严重怎么办该看什么科 脱头发如何治疗 掉头发厉害怎么办 头发上突然秃了一小块怎么办 全秃过后长出来白色头发怎么办 高三学生喜欢打球影响学习怎么办 高三学生受同学搔挠学习怎么办 烫完头发掉头发很厉害怎么办 烫头发后掉头发很厉害怎么办 十八岁了下门牙活动疼痛怎么办 烫完头发后掉头发很厉害怎么办 16岁的孩孑得了肺炎怎么办 我的头发掉的很厉害怎么办 八个月宝宝头后面没头发怎么办 生完孩子掉头发很厉害怎么办 生完孩子后掉头发很厉害怎么办 头发又细又软又卷怎么办 后颈部没有头发掉光了想植发怎么办 头发掉的厉害怎么办怎么拯救掉头发 头发可以种植吗 如果是秃顶怎么办 染头发把手指甲染黑了怎么办 怀孕两个月下体流褐色分泌物怎么办 头发总是大把大把的得掉 怎么办 严重脱发怎么办去问南宁肤康 脱发严重怎么办去看南宁肤康 前额头发少怎么办 如何使头发增多 生完宝宝头发一把一把的掉怎么办 生完宝宝后头发掉的厉害怎么办 生完宝宝头发掉的厉害怎么办 生了小孩后头发掉很多怎么办 生了孩子头发掉的很厉害怎么办 母乳期头发掉的很厉害怎么办 宝宝吃母乳头发掉的厉害怎么办 头发油腻头皮屑多还掉头发怎么办 头发剪了中分刘海弯了怎么办