香雨站

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 98|回复: 1

MediaCodec 同步方式完成AAC硬解成PCM

[复制链接]

2

主题

4

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 2022-9-21 21:43:54 | 显示全部楼层 |阅读模式
MediaCodec

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

构建MediaCodec
MediaCodec.createDecoderByType("要解码的类型")构建解码器;        
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
【相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】
音视频免费学习地址:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】免费领取C++音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击994289133加群领取哦~


使用


  • 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();
                }
            }
        }
    }
}原文地址:MediaCodec 同步方式完成AAC硬解成PCM - 资料 - 音视频开发中文网 - 构建全国最权威的音视频技术交流分享论坛:
回复

使用道具 举报

2

主题

6

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 前天 08:39 | 显示全部楼层
求沙发
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|香雨站

GMT+8, 2025-3-15 01:22 , Processed in 0.077046 second(s), 19 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.. 技术支持 by 巅峰设计

快速回复 返回顶部 返回列表