发布时间: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分钟左右
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, 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. }
编码器的创建于解码器的类似,只不过解码器的MediaFormat直接在音频文件内获取就可以了,编码器的MediaFormat需要自己来创建
[java] view plain copy
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. }
[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);//获取可