音视频5.1——MediaCodec 同步方式完成AAC硬解成PCM

音视频开发路线:

Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:

GitHub - wygsqsj/videoPath: 音视频学习路线demo

MediaCodec 

使用MediaCodec编解码实际是通过底层的硬件来对我们的音视频数据进行处理的,俗称硬编硬解,ffmpeg编解码是软解,效率不如MediaCodec,MediaCodec的主要实现是通过Native层去访问dsp芯片,让dsp芯片去编/解码流,整个流程按我的理解就是类似一个火腿肠加工厂,我给他一车猪,他拿走两头,一顿加工输入一筐火腿肠,我把这筐火腿肠取走,工厂收回筐,再抓两头猪进行加工,更详细的说明:MediaCodec API完成音频 AAC 硬编、硬解 - 简书

API说明

  • 构建MediaCodec
    1.  MediaCodec.createDecoderByType("要解码的类型")构建解码器;        
    2.  MediaCodec.createEncoderByType("要编码码的类型")构建编码码器;配置config
  • 配置config
configure(
            @Nullable MediaFormat format//media格式控制  ;参数3 ; 
            @Nullable Surface surface,//surface用于渲染编解码后的视频
            @Nullable MediaCrypto crypto,//加密用的对象,可根据自身需要定制
            @ConfigureFlag int flags)//标志位,解码为0,编码为1 
  • start开启编解码
  • dequeueInputBuffer() 可以理解为往工厂运猪的小推车,返回-1代表没有车子,返回大于-1代表有车子,获取到序号后就可以通过getInputBuffers()[序号]获取到小车子,再把猪放到车子里就可以了
  • queueInputBuffer  理解成把小推车里的猪运进工厂
void queueInputBuffer(
            int index,//小推车序号
            int offset, //偏移量,一般为0,表示从猪的那个地方开始加工
            int size, //猪的大小
            long presentationTimeUs, //时间戳,理解当前猪在一车猪中的顺序
            int flags//屠宰的标志,0进行加工 4就是没猪了
        )
  • dequeueOutputBuffer(“延时获取的微秒值”) 返回已处理好的数据buffer序号,理解成装火腿肠的筐序号,通过getOutputBuffers()[序号]获取装载数据的Buffer; 他除了序号的作用,还可以用作标志位,大于等于0表示序号,小于0表示当前codec返回的一些标志:
    • MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 输出的format已更改
    • MediaCodec.INFO_TRY_AGAIN_LATER 超时,没获取到
    • MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 输出缓冲区已更改        
  • releaseOutputBuffer(筐的序号, 标志位) 表示把筐还给工厂,第二个参数为true时将数据放到我们配置的surface里面
  • stop() release()释放资源

总之同步的方式就是配置完MediaCodec后开启while循环不断的dequeueInputBuffer -> queueInputBuffer填充数据 -> dequeueOutputBuffer -> releaseOutputBuffer

使用

  • input数据
//所有的猪都运进猪厂后不再添加
if (hasAudio) {
    //从猪肉工厂获取装猪的小推车,填充数据后发送到猪肉工厂进行处理
    ByteBuffer[] inputBuffers = decodeCodec.getInputBuffers();//所有的小推车
    int inputIndex = decodeCodec.dequeueInputBuffer(0);//返回当前可用的小推车标号
    if (inputIndex != -1) {
        Log.i(LOG_TAG, "找到了input 小推车" + inputIndex);
        //将MediaCodec数据取出来放到这个缓冲区里
        ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到小推车
        inputBuffer.clear();//扔出去里面旧的东西
        //将audioExtractor里面的猪装载到小推车里面
        int readSize = audioExtractor.readSampleData(inputBuffer, 0);
        //audioExtractor没猪了,也要告知一下
        if (readSize < 0) {
            Log.i(LOG_TAG, "当前音频已经读取完了");
            decodeCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            hasAudio = false;
        } else {//拿到猪
            Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + readSize);
            //告诉工厂这头猪的小推车序号、猪的大小、猪在这群猪里的排行、屠宰的标志
            decodeCodec.queueInputBuffer(inputIndex, 0, readSize, audioExtractor.getSampleTime(), 0);
            //读取音频的下一帧
            audioExtractor.advance();
        }
    } else {
        Log.i(LOG_TAG, "没有可用的input 小推车");
    }
}
  • output
int outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo, 0);//返回当前筐的标记
switch (outputIndex) {
    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
        Log.i(LOG_TAG, "输出的format已更改" + decodeCodec.getOutputFormat());
        break;
    case MediaCodec.INFO_TRY_AGAIN_LATER:
        Log.i(LOG_TAG, "超时,没获取到");
        break;
    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
        Log.i(LOG_TAG, "输出缓冲区已更改");
        break;
    default:
        Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + decodeBufferInfo.size);
        //获取所有的筐
        ByteBuffer[] outputBuffers = decodeCodec.getOutputBuffers();
        //拿到当前装满火腿肠的筐
        ByteBuffer outputBuffer;
        if (Build.VERSION.SDK_INT >= 21) {
            outputBuffer = decodeCodec.getOutputBuffer(outputIndex);
        } else {
            outputBuffer = outputBuffers[outputIndex];
        }
        //将火腿肠放到新的容器里,便于后期装车运走
        byte[] pcmData = new byte[decodeBufferInfo.size];
        outputBuffer.get(pcmData);//写入到字节数组中
        outputBuffer.clear();//清空当前筐
        //装车
        fos.write(pcmData);//数据写入文件中
        fos.flush();
        //把筐放回工厂里面
        decodeCodec.releaseOutputBuffer(outputIndex, false);
        break;
}
  • 完整代码
package com.wish.videopath.demo5;

import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Environment;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.wish.videopath.R;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import static com.wish.videopath.MainActivity.LOG_TAG;

/**
 * 类名称:EncodeAACThread
 * 类描述:将AAC通过MediaCodec接码成PCM文件
 */
class DecodeAACThread extends Thread {

    private Context context;
    private MediaFormat audioFormat;
    private File pcmFile;
    private boolean hasAudio = true;
    private FileOutputStream fos = null;


    public DecodeAACThread(Demo5Activity demo5Activity) {
        context = demo5Activity;
        pcmFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), "demo5.pcm");
        try {
            pcmFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void run() {
        super.run();
        //通过MediaExtractor获取音频通道
        MediaExtractor audioExtractor = new MediaExtractor();
        MediaCodec decodeCodec = null;
        //pcm文件输出
//        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(pcmFile.getAbsoluteFile());

            audioExtractor.setDataSource(context.getResources().openRawResourceFd(R.raw.demo5));
            int count = audioExtractor.getTrackCount();
            for (int i = 0; i < count; i++) {
                audioFormat = audioExtractor.getTrackFormat(i);
                if (audioFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
                    audioExtractor.selectTrack(i);
                    Log.i(LOG_TAG, "aac 找到了通道" + i);
                    break;
                }
            }

            //初始化MiediaCodec
            decodeCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
            //数据格式,surface用来渲染解析出来的数据;加密用的对象;标志 encode :1 decode:0
            decodeCodec.configure(audioFormat, null, null, 0);
            MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
            //启动解码
            decodeCodec.start();
            /*
             * 同步方式,流程是在while中
             * dequeueInputBuffer -> queueInputBuffer填充数据 -> dequeueOutputBuffer -> releaseOutputBuffer显示画面
             */
            boolean hasAudio = true;
            while (true) {
                //所有的猪都运进猪厂后不再添加
                if (hasAudio) {
                    //从猪肉工厂获取装猪的小推车,填充数据后发送到猪肉工厂进行处理
                    ByteBuffer[] inputBuffers = decodeCodec.getInputBuffers();//所有的小推车
                    int inputIndex = decodeCodec.dequeueInputBuffer(0);//返回当前可用的小推车标号
                    if (inputIndex != -1) {
                        Log.i(LOG_TAG, "找到了input 小推车" + inputIndex);
                        //将MediaCodec数据取出来放到这个缓冲区里
                        ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到小推车
                        inputBuffer.clear();//扔出去里面旧的东西
                        //将audioExtractor里面的猪装载到小推车里面
                        int readSize = audioExtractor.readSampleData(inputBuffer, 0);
                        //audioExtractor没猪了,也要告知一下
                        if (readSize < 0) {
                            Log.i(LOG_TAG, "当前音频已经读取完了");
                            decodeCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            hasAudio = false;
                        } else {//拿到猪
                            Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + readSize);
                            //告诉工厂这头猪的小推车序号、猪的大小、猪在这群猪里的排行、屠宰的标志
                            decodeCodec.queueInputBuffer(inputIndex, 0, readSize, audioExtractor.getSampleTime(), 0);
                            //读取音频的下一帧
                            audioExtractor.advance();
                        }
                    } else {
                        Log.i(LOG_TAG, "没有可用的input 小推车");
                    }
                }

                //工厂已经把猪运进去了,但是是否加工成火腿肠还是未知的,我们要通过装火腿肠的筐来判断是否已经加工完了
                int outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo, 0);//返回当前筐的标记
                switch (outputIndex) {
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        Log.i(LOG_TAG, "输出的format已更改" + decodeCodec.getOutputFormat());
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        Log.i(LOG_TAG, "超时,没获取到");
                        break;
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        Log.i(LOG_TAG, "输出缓冲区已更改");
                        break;
                    default:
                        Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + decodeBufferInfo.size);
                        //获取所有的筐
                        ByteBuffer[] outputBuffers = decodeCodec.getOutputBuffers();
                        //拿到当前装满火腿肠的筐
                        ByteBuffer outputBuffer;
                        if (Build.VERSION.SDK_INT >= 21) {
                            outputBuffer = decodeCodec.getOutputBuffer(outputIndex);
                        } else {
                            outputBuffer = outputBuffers[outputIndex];
                        }
                        //将火腿肠放到新的容器里,便于后期装车运走
                        byte[] pcmData = new byte[decodeBufferInfo.size];
                        outputBuffer.get(pcmData);//写入到字节数组中
                        outputBuffer.clear();//清空当前筐
                        //装车
                        fos.write(pcmData);//数据写入文件中
                        fos.flush();
                        //把筐放回工厂里面
                        decodeCodec.releaseOutputBuffer(outputIndex, false);
                        break;
                }
                if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Log.i(LOG_TAG, "表示当前编解码已经完事了");
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (audioExtractor != null) {
                audioExtractor.release();
            }
            if (decodeCodec != null) {
                decodeCodec.stop();
                decodeCodec.release();
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值