Mediacodec

基本原理和流程
Mediacodec用于硬件编解码,其存在3种形态Stopped[Uninitialized,Confirured,Error]Executing[Flushed,Running,End of Stream],Released。构造一个Mediacodec对象(eg:MediaCodec.createDecoderByType)时处于Uninitialized态,执行configure会进入Configured态,调用start会立即进入Executing的Flushed态(通过flush方法在Executing下可回到Flushed态),当第一个输出缓冲出队(dequeueInputBuffer)则进入Running态(这个状态处理编解码工作,可以执行stop回到Uninitialized态),当放入EOS标记(queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM))时则会转入
End of Stream态(不接受输入,但可能会继续输出,直到EOS),不再使用时调用release进入Released态
可以在任何时候执行reset使回到Uninitialized态
这里写图片描述

采用2个环形队列作为输入输出缓冲区,input client在空闲区放入数据,codec取出数据进行编解码然后在输出的空闲区放入数据(清空归还使用的区),output client取出编解码后的数据 (归还)。
这里写图片描述
这里写图片描述

[Uninitialized]
MediaCodec.createDecoderByType
[Configured]
mediaCodec.configure
[Flushed]
mediaCodec.start
[Running]
mediaCodec.dequeueInputBuffer
mediaCodec.queueInputBuffer
mediaCodec.dequeueOutputBuffer
mediaCodec.releaseOutputBuffer

注意点
1.如果是解码视频不想直接显示,则configure不要指定surface,然后在输出中获取数据。
2.如果是自己定义设置format,一定要记得设置csd-0,csd-1等,不然会报错。对于H264涉及的sps和pps参考地址
对于aac涉及的adts如图http://stackoverflow.com/questions/18862715/how-to-generate-the-aac-adts-elementary-stream-with-android-mediacodec这里写图片描述
也可以利用queueInputBuffer方法使用BUFFER_FLAG_CODEC_CONFIG进行标记
这里写图片描述

示例代码
旧api同步处理方式

public class DecodeVideoThread extends Thread {

    //媒体信息提取器
    private MediaExtractor mediaExtractor;
    private MediaCodec mediaCodec;
    private boolean running=true;

    @RequiresApi(api = Build.VERSION_CODES.N)
    public DecodeVideoThread(Surface surface, AssetFileDescriptor assetFileDescriptor){
        mediaExtractor=new MediaExtractor();
        try {
            mediaExtractor.setDataSource(assetFileDescriptor);
        } catch (IOException e) {
            e.printStackTrace();
        }
        init(surface);
    }

    public DecodeVideoThread(Surface surface,String filePath){
        mediaExtractor=new MediaExtractor();
        try {
            mediaExtractor.setDataSource(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        init(surface);
    }

    //利用的mediaextractor
    private void init(Surface surface){
        int trackCount = mediaExtractor.getTrackCount();
        for (int i = 0; i < trackCount; i++) {
            MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
            String mime = trackFormat.getString(MediaFormat.KEY_MIME);
            if(mime.startsWith("video/")){
                Log.i("tag",mime);
                mediaExtractor.selectTrack(i);
                try {
                    mediaCodec = MediaCodec.createDecoderByType(mime);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mediaCodec.configure(trackFormat,surface,null,0);
                mediaCodec.start();
                break;
            }
        }
    }



    @Override
    public void run() {
        super.run();
        MediaCodec.BufferInfo bufferInfo=new MediaCodec.BufferInfo();
        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
        ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();

        boolean needInput=true;
        boolean first=true;
        long startWhen=0;
        while (running){
            if(needInput){
                int inputIndex = mediaCodec.dequeueInputBuffer(10 * 1000);
                if(inputIndex>=0){
                     ByteBuffer inputBuffer;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        //新版api,测试
                        Log.i("tag","aaaaaaaaa");
                         inputBuffer = mediaCodec.getInputBuffer(inputIndex);
                    }else{
                         inputBuffer = inputBuffers[inputIndex];
                    }
                    int sampleDataSize = mediaExtractor.readSampleData(inputBuffer, 0);
                    if(mediaExtractor.advance() && sampleDataSize>0){
                        mediaCodec.queueInputBuffer(inputIndex,0,sampleDataSize,mediaExtractor.getSampleTime(),0);
                    }else {
                        mediaCodec.queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        needInput=false;
                    }
                }
            }

            int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10 * 1000);
            switch (outputIndex){
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    Log.i("tag","output format"+mediaCodec.getOutputFormat());
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    Log.i("tag","如果前面指定了超时值,这个则表明超时");
                    break;
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    outputBuffers=mediaCodec.getOutputBuffers();
                    Log.i("tag","输出缓冲区更改");
                    break;
                default:
                    if(first){
                        first=false;
                        startWhen=System.currentTimeMillis();
                    }
                    //presentationTimeUs表示的是展示的时间,单位是微妙
                    long sleepTime=bufferInfo.presentationTimeUs/1000-(System.currentTimeMillis()-startWhen);
                    //这样处理只能保证播放不变快,没有处理播放太慢的情况
                    if(sleepTime>0){
                        try {
                            Thread.sleep(sleepTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    mediaCodec.releaseOutputBuffer(outputIndex,true);


            }

            if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){
                //结束循环
                break;
            }
        }
        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec=null;
        mediaExtractor.release();
    }

    public void close(){
        running=false;
    }
}

新api异步处理方式

public class DecodeVideoThread2 extends Thread {

    //媒体信息提取器
    private MediaExtractor mediaExtractor;
    private MediaCodec mediaCodec;
    private boolean running=true;

    @RequiresApi(api = Build.VERSION_CODES.N)
    public DecodeVideoThread2(Surface surface, AssetFileDescriptor assetFileDescriptor){
        mediaExtractor=new MediaExtractor();
        try {
            mediaExtractor.setDataSource(assetFileDescriptor);
        } catch (IOException e) {
            e.printStackTrace();
        }
        init(surface);
    }

    public DecodeVideoThread2(Surface surface, String filePath){
        mediaExtractor=new MediaExtractor();
        try {
            mediaExtractor.setDataSource(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        init(surface);
    }

    //利用的mediaextractor
    private void init(Surface surface){
        int trackCount = mediaExtractor.getTrackCount();
        for (int i = 0; i < trackCount; i++) {
            MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
            String mime = trackFormat.getString(MediaFormat.KEY_MIME);
            if(mime.startsWith("video/")){
                Log.i("tag",mime);
                mediaExtractor.selectTrack(i);
                try {
                    mediaCodec = MediaCodec.createDecoderByType(mime);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    mediaCodec.setCallback(new MediaCodec.Callback() {
                        boolean first=true;
                        long startWhen;

                        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
                        @Override
                        public void onInputBufferAvailable(MediaCodec codec, int index) {

                            ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
                            int sampleDataSize = mediaExtractor.readSampleData(inputBuffer, 0);
                            if(sampleDataSize>0){
                                mediaCodec.queueInputBuffer(index,0,sampleDataSize,mediaExtractor.getSampleTime(),0);
                                mediaExtractor.advance();
                            }else{
                                mediaCodec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            }


                        }

                        @Override
                        public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
                            if(first){
                                first=false;
                                startWhen=System.currentTimeMillis();
                            }
                            long sleepTime=info.presentationTimeUs/1000-(System.currentTimeMillis()-startWhen);
                            if(sleepTime>0){
                                try {
                                    Thread.sleep(sleepTime);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            mediaCodec.releaseOutputBuffer(index,true);

//                            //下面这样设置并没有什么卵用,姿势不对?
//                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//                                mediaCodec.releaseOutputBuffer(index,info.presentationTimeUs*1000);
//                            }
                        }

                        @Override
                        public void onError(MediaCodec codec, MediaCodec.CodecException e) {

                        }

                        @Override
                        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {

                        }
                    });
                }
                mediaCodec.configure(trackFormat,surface,null,0);
                mediaCodec.start();
                break;
            }
        }
    }


    public void close(){
        running=false;

        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec=null;
        mediaExtractor.release();
    }
}

编码音频(播放速度不对不知道为啥)

public class EncodeAudioThread extends Thread {
    boolean running=true;
    MediaCodec mediaCodec;
    MediaMuxer mediaMuxer;
    int trackIndex;
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    public EncodeAudioThread(){
        try {
            mediaCodec=MediaCodec.createEncoderByType("audio/mp4a-latm");
        } catch (IOException e) {
            e.printStackTrace();
        }
        MediaFormat mediaFormat=makeAACCodecSpecificData(AACObjectLC,44100,2);
        mediaCodec.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();
        try {
            mediaMuxer=new MediaMuxer(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        } catch (IOException e) {
            e.printStackTrace();
        }
        trackIndex=mediaMuxer.addTrack(mediaFormat);
        mediaMuxer.start();
    }

    private MediaFormat makeAACCodecSpecificData(int audioProfile, int sampleRate, int channelConfig) {
//        MediaFormat format = new MediaFormat();
//        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
//        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
//        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig);
        MediaFormat format=MediaFormat.createAudioFormat("audio/mp4a-latm",sampleRate,channelConfig);
        //编码不设置比特率会报错 android.media.MediaCodec$CodecException: Error 0x80001001
        format.setInteger(MediaFormat.KEY_BIT_RATE,56*1000);
        format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        int samplingFreq[] = {
                96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
                16000, 12000, 11025, 8000
        };

        // Search the Sampling Frequencies9
        int sampleIndex = -1;
        for (int i = 0; i < samplingFreq.length; ++i) {
            if (samplingFreq[i] == sampleRate) {
                Log.d("TAG", "kSamplingFreq " + samplingFreq[i] + " i : " + i);
                sampleIndex = i;
            }
        }

        if (sampleIndex == -1) {
            return null;
        }

        //没搞懂这段的计算原理
        ByteBuffer csd = ByteBuffer.allocate(2);
        csd.put((byte) ((audioProfile << 3) | (sampleIndex >> 1)));
        csd.position(1);
        csd.put((byte) ((byte) ((sampleIndex << 7) & 0x80) | (channelConfig << 3)));
        csd.flip();
        format.setByteBuffer("csd-0", csd); // add csd-0
        System.out.println(Arrays.toString(csd.array())+"===++");

        return format;
    }

    private void addADTStoPacket(byte[] packet, int packetLen) {
        int profile = 2;  //AAC LC
        //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
        int freqIdx = 4;  //44.1KHz
        int chanCfg = 2;  //CPE

        // fill in ADTS data
        packet[0] = (byte)0xFF;
        packet[1] = (byte)0xF9;
        packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
        packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
        packet[4] = (byte)((packetLen&0x7FF) >> 3);
        packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
        packet[6] = (byte)0xFC;
    }


    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    @Override
    public void run() {
        super.run();
        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
        ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
        MediaCodec.BufferInfo bufferInfo=new MediaCodec.BufferInfo();

        //================

        FileOutputStream fileOutputStream = null;
        FileOutputStream fileOutputStream2 = null;
        try {
             fileOutputStream=new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record.pcm");
             fileOutputStream2=new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record2");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        AudioRecord audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC,44100, AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT,minBufferSize);
        audioRecord.startRecording();
        byte[] buffer=new byte[minBufferSize];
        while(running){
            int readSize = audioRecord.read(buffer, 0, buffer.length);
            try {
                fileOutputStream.write(buffer,0,readSize);
            } catch (IOException e) {
                e.printStackTrace();
            }

            //===================
            int inputIndex = mediaCodec.dequeueInputBuffer(1000);
            ByteBuffer inputBuffer = inputBuffers[inputIndex];
//            inputBuffer.clear();
            inputBuffer.put(buffer,0,readSize);
//            inputBuffer.flip();
            mediaCodec.queueInputBuffer(inputIndex,0,readSize,0,0);
            int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 1000);
            switch (outputIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    outputBuffers = mediaCodec.getOutputBuffers();
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    MediaFormat outputFormat = mediaCodec.getOutputFormat();

                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    //超时
                    break;
                default:
                    mediaMuxer.writeSampleData(trackIndex,outputBuffers[outputIndex],bufferInfo);
                    //通过文件写
//                    byte[] array = outputBuffers[outputIndex].array();//会报错 ReadOnlyBufferException
                    ByteBuffer outputBuffer = outputBuffers[outputIndex];
                    byte[] newArray=new byte[outputBuffer.limit()+7];
                    addADTStoPacket(newArray,newArray.length);
                    outputBuffer.get(newArray,7,outputBuffer.limit());
                    try {
                        fileOutputStream2.write(newArray);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }//

                    mediaCodec.releaseOutputBuffer(outputIndex,false);
                    break;
            }
            //================


        }
        try {
            fileOutputStream.close();
            fileOutputStream2.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            audioRecord.release();
            audioRecord=null;

            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec=null;

            mediaMuxer.release();
        }


        System.out.println("finish");
        //读取播放
        FileInputStream fileInputStream = null;
        try {
             fileInputStream=new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record.pcm");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        AudioTrack audioTrack=new AudioTrack(AudioManager.STREAM_MUSIC,44100,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT,minBufferSize,AudioTrack.MODE_STREAM);
        audioTrack.play();
        int rs = 0;
        try {
            while((rs=fileInputStream.read(buffer))!=-1){
                audioTrack.write(buffer,0,rs);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //finally{}
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            audioTrack.release();
        }


    }

    public void stopThread(){
        running=false;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值