网站首页 博客 Android录制声音+屏幕视频
Android录制声音+屏幕视频
 2020-04-16 14:57:28  管理员  193

1.申请所需权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.RECORD_VIDEO" />

2.引入所需依赖

import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Environment;
import android.provider.Settings;
import android.support.annotation.NonNull;

3.定义全局变量

    final int REQUSET_VIDEO = 10;
    final int REQUSET_AUDIO = 11;
    MediaProjectionManager projectionManager;

    private int width;//屏幕宽度
    private int height;//屏幕高度
    private int dpi;//dpi

    private MediaRecorder mediaRecorder;
    private MediaRecordThread mediaRecordThread;
    private ScreenAsyn screenAsyn;
    private AudioRecorderThread audioRecorderThread;

    public String saveFilepath = "";
    public String saveFilename = "";

4.初始化录制

        //获取屏幕宽高
        Display display = getWindowManager().getDefaultDisplay();
        Point point = new Point();
        display.getSize(point);
        width = point.x;
        height = point.y;
        //获取屏幕DPI
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        dpi = metrics.densityDpi;
        //检查 读写 和录音权限
        requestPermission();

5.具体实现代码

    /**
     * 请求录音读写权限
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    void requestPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PERMISSION_DENIED) {
            String[] permission = {Manifest.permission.RECORD_AUDIO};
            requestPermissions(permission, REQUSET_AUDIO);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUSET_AUDIO) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == PERMISSION_GRANTED) {
                    Toast.makeText(this, "获得权限" + permissions[i], Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUSET_VIDEO && resultCode == RESULT_OK) {//已经成功获取到权限
            MediaProjection projection = projectionManager.getMediaProjection(resultCode, data);
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
            Date date = new Date(System.currentTimeMillis());
            saveFilename = String.format("meeting_%s.mp4", format.format(date));
            saveFilepath = getfilefullPath(saveFilename);
            //仅录屏,没有声音
            //screenAsyn = new ScreenAsyn(width, height, 6000000, dpi, projection, saveFilepath);
            //new Thread(screenAsyn).start();
            //录制声音+屏幕
            audioRecorderThread = new AudioRecorderThread(width, height, 6000000, dpi, projection, saveFilepath);
            new Thread(audioRecorderThread).start();
        }
    }


    /**
     * 录屏方式一,使用 MediaRecorder类
     * 这种方式 可以录制视频到本地,但是不能取得视频流,无法进行直播
     *
     * @param projection
     */
    void startRecorderWithMediaRecorder(MediaProjection projection) {
        Objects.requireNonNull(projection);
        //这个呢 一定要注意 文件名后面有 文件格式,并且要和下面你录制视频的格式相同
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
        Date date = new Date(System.currentTimeMillis());
        saveFilename = String.format("meeting_%s.mp4", format.format(date));
        saveFilepath = getfilefullPath(saveFilename);
        MediaRecordThread mMediaRecordThread = mediaRecordThread = new MediaRecordThread(720, 1080, 8 * 1024 * 600, dpi, projection, saveFilepath);
        mMediaRecordThread.start();
    }

    //录制视频 放在子线程最好,所以线程
    class MediaRecordThread extends Thread {
        private int mWidth;//录制视频的宽
        private int mHeight;//录制视频的高
        private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值
        private int mDpi;//视频的DPI
        private String mDstPath;//录制视频文件存放地点
        private MediaRecorder mMediaRecorder;//通过这个类录制
        private MediaProjection mediaProjection;//通过这个类 生成虚拟屏幕
        private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值
        private VirtualDisplay virtualDisplay;

        MediaRecordThread(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {
            mWidth = width;
            mHeight = height;
            mBitRate = bitrate;
            mDpi = dpi;
            this.mediaProjection = mediaProjection;
            mDstPath = dstPath;
        }

        @Override
        public void run() {
            try {
                //先实例化
                initMediaRecorder();
                //下面这个方法的 width height  并不是录制视频的宽高。他更明显是虚拟屏幕的宽高
                //注意 mediaRecorder.getSurface() 这里我们mediaRecorder的surface 传递给虚拟屏幕,
                // 虚拟屏幕显示的内容就会反映在这个surface上面,自然也就可以录制了
                virtualDisplay = mediaProjection.createVirtualDisplay("luing", mWidth, mHeight, mDpi,
                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null
                );
                //开始
                mMediaRecorder.start();
                Zprint.log(this.getClass().getName(), "录屏线程内部开始工作");
            } catch (IllegalStateException | IOException e) {
//                e.printStackTrace();
                Zprint.log(this.getClass().getName(), " 异常  ", e.toString());
            }
        }

        //实例化MediaRecordor
        void initMediaRecorder() throws IOException {
            mMediaRecorder = mediaRecorder = new MediaRecorder();
            //设置视频来源  录屏嘛 肯定是使用一个Surface作为视频源,如果录制视频的话 就是使用摄像头作为来源了 CAMERA
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            //设置要用于录制的音频源,必须在setOutputFormat()之前调用。参数有很多,英文注释也很简单,下面这个是录制麦克风,也就是外放的
            //记住 这个必要获得录制视频权限才行,要不然 报错
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            //设置录制期间生成的输出文件的格式。必须在prepare()之前调用
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            //录制文件存放位置
            mediaRecorder.setOutputFile(mDstPath);
            //录制视频的宽高
            mediaRecorder.setVideoSize(mWidth, mHeight);
            //FPS
            mediaRecorder.setVideoFrameRate(FRAME_RATE);
            //比特率
            mediaRecorder.setVideoEncodingBitRate(mBitRate);
            //视频编码格式
            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            //音频编码格式
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //准备 到这里 就可以开始录制视频了
            mediaRecorder.prepare();
        }

        /**
         * 释放资源
         */
        void release() {
            if (mediaRecorder != null) {
                mediaRecorder.setOnErrorListener(null);
                mediaRecorder.stop();
                mediaRecorder.reset();
                mediaRecorder.release();
                mediaRecorder = null;//help GC
            }
            if (virtualDisplay != null) {
                virtualDisplay.release();
                virtualDisplay = null;//help GC
            }
            if (mediaProjection != null) {
                mediaProjection.stop();
                mediaProjection = null;//help GC
            }
        }
    }


    /**
     * 录制视频 通过MediaCodec 和MediaMuxer ,这个可以获取视频流,也就是说可以直播
     *
     * @param isAsyn 是否异步录制,true 异步,false 同步
     */
    void startRecorderWithMediaCodec(boolean isAsyn, MediaProjection mediaProjection) {

    }

    //录制屏幕 加声音 同步方式
    class AudioRecorderThread extends Thread {
        private AudioRecord mAudiorecord;//录音类

        private MediaMuxer mMediaMuxer;//通过这个将视频流写入本地文件,如果直播的话 不需要这个

        // 音频源:音频输入-麦克风  我使用其他格式 就会报错
        private final static int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
        // 采样率
        // 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
        // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
        private final static int AUDIO_SAMPLE_RATE = 44100;
        // 音频通道 默认的 可以是单声道 立体声道
        private final int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_DEFAULT;
        // 音频格式:PCM编码   返回音频数据的格式
        private final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
        //记录期间写入音频数据的缓冲区的总大小(以字节为单位)。
        private int audioBufferSize = 0;
        //缓冲数组 ,用来读取audioRecord的音频数据
        private byte[] byteBuffer;

        private int audioIndex;//通过MediaMuxer 向本地文件写入数据时候,这个标志是用来确定信道的
        private int videoIndex;//上同

        private MediaCodec mAudioMediaCodec;//音频编码器

        private MediaCodec mVideoMediaCodec;//视频编码器

        private MediaFormat audioFormat;//音频编码器 输出数据的格式
        private MediaFormat videoFormat;//视频编码器 输出数据的格式

        private MediaProjection mediaProjection;//通过这个类 生成虚拟屏幕
        private Surface surface;//视频编码器 生成的surface ,用于充当 视频编码器的输入源
        private VirtualDisplay virtualDisplay; //虚拟屏幕
        //这个是每次在编码器 取数据的时候,这个info 携带取出数据的信息,例如 时间,大小 类型之类的  关键帧 可以通过这里的flags辨别
        private MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();
        private MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();

        private volatile boolean isRun = true;//用于控制 是否录制,这个无关紧要


        private int mWidth;//录制视频的宽
        private int mHeight;//录制视频的高
        private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值
        private int mDpi;//视频的DPI
        private String mDstPath;//录制视频文件存放地点
        private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值

        public AudioRecorderThread(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {
            this.mediaProjection = mediaProjection;
            this.mWidth = width;
            this.mHeight = height;
            mBitRate = bitrate;
            mDpi = dpi;
            mDstPath = dstPath;
        }

        @RequiresApi(api = Build.VERSION_CODES.M)
        @Override
        public void run() {
            super.run();
            try {
                //实例化 AudioRecord
                initAudioRecord();
                //实例化 写入文件的类
                initMediaMuxer();
                //实例化 音频编码器
                initAudioMedicode();
                //实例化 视频编码器
                initVideoMedicodec();
                //开始
                mAudioMediaCodec.start();
                mVideoMediaCodec.start();
                int timeoutUs = -1;//这个主要是为了 第一次进入while循环 视频编码器 能阻塞到 有视频数据输出 才运行
                String TAG = "audio";
                while (isRun) {
                    //获取 输出缓冲区的索引 通过索引 可以去到缓冲区,缓冲区里面存着 编码后的视频数据 。 timeoutUs为负数的话,会一直阻塞到有缓冲区索引,0的话 立刻返回
                    int videoOutputID = mVideoMediaCodec.dequeueOutputBuffer(videoInfo, timeoutUs);
                    Log.d(TAG, "video flags " + videoInfo.flags);
                    timeoutUs = 0;//第二次 视频编码器 就不需要 阻塞了  0 立刻返回
                    //索引大于等于0 就代表有数据了
                    if (videoOutputID >= 0) {
                        Zprint.log(this.getClass().getName(), "VIDEO 输出", videoOutputID, videoInfo.presentationTimeUs);
                        //flags是2的时候 代表输出的数据 是配置信息,不是媒体信息
                        if (videoInfo.flags != 2) {
                            //得到缓冲区
                            //这里就可以取出数据 进行网络传输
                            ByteBuffer outBuffer = mVideoMediaCodec.getOutputBuffer(videoOutputID);
                            outBuffer.flip();//准备读取
                            //写入文件中  注意 videoIndex
                            mMediaMuxer.writeSampleData(videoIndex, outBuffer, videoInfo);
                        }
                        //释放缓冲区,毕竟缓冲区一共就两个 一个输入 一个输出,用完是要还回去的
                        mVideoMediaCodec.releaseOutputBuffer(videoOutputID, false);
                    } else if (videoOutputID == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {//输出格式有变化
                        Zprint.log(this.getClass().getName(), "video Format 改变");
                        videoFormat = mVideoMediaCodec.getOutputFormat();//得到新的输出格式
                        videoIndex = mMediaMuxer.addTrack(videoFormat);//重新确定信道
                    }
                    //得到 输入缓冲区的索引
                    int audioInputID = mAudioMediaCodec.dequeueInputBuffer(0);
                    //也是大于等于0 代表 可以输入数据啦
                    if (audioInputID >= 0) {
                        Zprint.log(this.getClass().getName(), "audio 输入", audioInputID);
                        ByteBuffer audioInputBuffer = mAudioMediaCodec.getInputBuffer(audioInputID);
                        audioInputBuffer.clear();
                        //从 audiorecord 里面 读取原始的音频数据
                        int read = mAudiorecord.read(byteBuffer, 0, audioBufferSize);
                        if (read < audioBufferSize) {
                            System.out.println(" 读取的数据" + read);
                        }
                        //上面read可能小于audioBufferSize  要注意
                        audioInputBuffer.put(byteBuffer, 0, read);
                        //入列  注意下面的时间,这个是确定这段数据 时间的 ,视频音频 都是一段段的数据,每个数据都有时间 ,这样播放器才知道 先播放那个数据
                        // 串联起来 就是连续的了
                        mAudioMediaCodec.queueInputBuffer(audioInputID, 0, read, System.nanoTime() / 1000L, 0);
                    }
                    //音频输出
                    int audioOutputID = mAudioMediaCodec.dequeueOutputBuffer(audioInfo, 0);
                    Log.d(TAG, "audio flags " + audioInfo.flags);
                    if (audioOutputID >= 0) {
                        Zprint.log(this.getClass().getName(), "audio 输出", audioOutputID, audioInfo.presentationTimeUs);
                        audioInfo.presentationTimeUs = videoInfo.presentationTimeUs;//保持 视频和音频的统一,防止 时间画面声音 不同步
                        if (audioInfo.flags != 2) {
                            //这里就可以取出数据 进行网络传输
                            ByteBuffer audioOutBuffer = mAudioMediaCodec.getOutputBuffer(audioOutputID);
                            audioOutBuffer.limit(audioInfo.offset + audioInfo.size);//这是另一种 和上面的 flip 没区别
                            audioOutBuffer.position(audioInfo.offset);
                            mMediaMuxer.writeSampleData(audioIndex, audioOutBuffer, audioInfo);//写入
                        }
                        //释放缓冲区
                        mAudioMediaCodec.releaseOutputBuffer(audioOutputID, false);
                    } else if (audioOutputID == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        Zprint.log(this.getClass().getName(), "audio Format 改变");
                        audioFormat = mAudioMediaCodec.getOutputFormat();
                        audioIndex = mMediaMuxer.addTrack(audioFormat);
                        //注意 这里  只在start  视频哪里没有这个,这个方法只能调用一次
                        mMediaMuxer.start();
                    }
                }
                //释放资源
                stopRecorder();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //实例化 AUDIO 的编码器
        void initAudioMedicode() throws IOException {
            audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL);
            audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率
            //描述要使用的AAC配置文件的键(仅适用于AAC音频格式)。
            audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, audioBufferSize << 1);//最大输入

            //这里注意  如果 你不确定 你要生成的编码器类型,就通过下面的 通过类型生成编码器
            mAudioMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            //配置
            mAudioMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        }

        //实例化 VIDEO 的编码器
        void initVideoMedicodec() throws IOException {
            //这里的width height 就是录制视频的分辨率,可以更改  如果这里的分辨率小于 虚拟屏幕的分辨率 ,你会发现 视频只录制了 屏幕部分内容
            videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
            videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);//比特率 bit单位
            videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);//FPS
            videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧  是完整的一张图片,其他的都是部分图片
            //通过类型创建编码器  同理 创建解码器也是一样
            mVideoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            //配置
            mVideoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            //让视频编码器 生成一个弱引用的surface, 这个surface不会保证视频编码器 不被回收,这样 编码视频的时候 就不需要 传输数据进去了
            surface = mVideoMediaCodec.createInputSurface();
            //创建虚拟屏幕,让虚拟屏幕内容 渲染在上面的surface上面 ,这样 才能 不用传输数据进去
            virtualDisplay = mediaProjection.createVirtualDisplay("video", mWidth, mHeight, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
        }

        //录音的类,用于给音频编码器 提供原始数据
        @RequiresApi(api = Build.VERSION_CODES.M)
        void initAudioRecord() {
            //得到 音频录制时候 最小的缓冲区大小
            audioBufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);
            byteBuffer = new byte[audioBufferSize];
            //两种方式 都可以
            //mAudiorecord = new AudioRecord(AUDIO_SOURCE, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, audioBufferSize);
            //通过builder方式创建
            mAudiorecord = new AudioRecord.Builder()
                    .setAudioSource(AUDIO_SOURCE)
                    .setAudioFormat(new AudioFormat.Builder()
                            .setEncoding(AUDIO_ENCODING)
                            .setSampleRate(AUDIO_SAMPLE_RATE)
                            .setChannelMask(AUDIO_CHANNEL)
                            .build())
                    .setBufferSizeInBytes(audioBufferSize)
                    .build();
            //开始录制,这里可以检查一下状态,但只要代码无误,检查是无需的 state
            mAudiorecord.startRecording();
        }

        //如果 要录制mp4文件的话,需要调用这个方法 创建 MediaMuxer
        private void initMediaMuxer() throws Exception {
            //注意格式  创建录制的文件
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
            Date date = new Date(System.currentTimeMillis());
            saveFilename = String.format("meeting_%s.mp4", format.format(date));
            saveFilepath = getfilefullPath(saveFilename);
            //实例化 MediaMuxer 编码器取出的数据,通过它写入文件中
            mMediaMuxer = new MediaMuxer(saveFilepath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        }

        //释放资源
        void stopRecorder() {
            if (mVideoMediaCodec != null) {
                mVideoMediaCodec.stop();
                mVideoMediaCodec.release();
                mVideoMediaCodec = null;
            }
            if (mAudioMediaCodec != null) {
                mAudioMediaCodec.stop();
                mAudioMediaCodec.release();
                mAudioMediaCodec = null;
            }
            if (mAudiorecord != null) {
                mAudiorecord.stop();
                mAudiorecord.release();
                mAudiorecord = null;
            }

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

            virtualDisplay.release();
            virtualDisplay = null;
        }
    }


    //录屏 异步模式  没有声音的
    class ScreenAsyn implements Runnable {
        private MediaProjection mediaProjection;
        private MediaFormat videoFormat;
        private MediaCodec mVideoMediaCodec;
        private VirtualDisplay virtualDisplay;
        private MediaMuxer mediaMuxer;
        private int videoIndex;

        private int mWidth;//录制视频的宽
        private int mHeight;//录制视频的高
        private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值
        private int mDpi;//视频的DPI
        private String mDstPath;//录制视频文件存放地点
        private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值

        public ScreenAsyn(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {
            this.mWidth = width;
            mHeight = height;
            mBitRate = bitrate;
            mDpi = dpi;
            this.mediaProjection = mediaProjection;
            mDstPath = dstPath;
        }

        @Override
        public void run() {
            try {
                mediaMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

                Surface surface;
                //这里的width height 就是录制视频的分辨率,可以更改  如果这里的分辨率小于 虚拟屏幕的分辨率 ,你会发现 视频只录制了 屏幕部分内容
                videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
                videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
                videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);//比特率 bit单位
                videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);//FPS
                videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧  是完整的一张图片,其他的都是部分图片
                //通过类型创建编码器  同理 创建解码器也是一样
                mVideoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
                mVideoMediaCodec.setCallback(new MediaCodec.Callback() {
                    @Override
                    public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {

                    }

                    @Override
                    public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
                        Zprint.log(this.getClass().getName(), "info", info.offset, info.size, info.presentationTimeUs);
                        ByteBuffer outBuffer = codec.getOutputBuffer(index);
                        outBuffer.flip();
                        mediaMuxer.writeSampleData(videoIndex, outBuffer, info);
                        codec.releaseOutputBuffer(index, false);
                    }

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

                    }

                    @Override
                    public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
                        //这是h.264需要的
                        ByteBuffer sps = format.getByteBuffer("csd-0");    // SPS
                        ByteBuffer pps = format.getByteBuffer("csd-1");    // PPS
                        //VP9 需要的
                        ByteBuffer CodecPrivate = format.getByteBuffer("csd-0"); //
                        videoIndex = mediaMuxer.addTrack(format);
                        mediaMuxer.start();
                    }
                });

                mVideoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                surface = mVideoMediaCodec.createInputSurface();
                virtualDisplay = mediaProjection.createVirtualDisplay("asyn", mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
                mVideoMediaCodec.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //暂停录制
        void stopRecord() {
            if (mVideoMediaCodec != null) {
                mVideoMediaCodec.stop();
                mVideoMediaCodec.release();
                mVideoMediaCodec = null;
            }
            if (mediaMuxer != null) {
                mediaMuxer.release();
                mediaMuxer = null;
            }
            if (virtualDisplay != null) {
                virtualDisplay.release();
            }
        }
    }

    /**
     * 截图
     * @param mediaProjection
     * @return bitmap
     */
    public Bitmap screenShot(MediaProjection mediaProjection){
        Objects.requireNonNull(mediaProjection);
        ImageReader imageReader = ImageReader.newInstance(1080, 1920, PixelFormat.RGBA_8888, 60);
        VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("screen", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,imageReader.getSurface(), null, null);
        //这个是取 现在最新的图片
        Image image = imageReader.acquireLatestImage();
        //也可以挨个取图片
//        Image image = imageReader.acquireNextImage();
        return image2Bitmap(image);
    }

    public static Bitmap image2Bitmap(Image image) {
        if (image == null) {
            System.out.println("image 为空");
            return null;
        }
        int width = image.getWidth();
        int height = image.getHeight();
        System.out.println(width+"    "+height);
        final Image.Plane[] planes = image.getPlanes();
        final ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;

        Bitmap bitmap = Bitmap.createBitmap(width+ rowPadding / pixelStride , height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);

    /*    //压缩图片
        Matrix matrix = new Matrix();
        matrix.setScale(0.5F, 0.5F);
        System.out.println(bitmap.isMutable());
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
*/
        image.close();
        return bitmap;
    }
    /**
     * @param fileName 文件名字
     * @return 文件的地址,默认在recorder文件夹下
     */
    public String getfilefullPath(String fileName) {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            saveFilepath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "recorder" + File.separator + fileName;
        } else {
            saveFilepath = getApplication().getCacheDir().getAbsolutePath() + File.separator + "recorder" + File.separator + fileName;
        }
        File file = new File(saveFilepath);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdir();
        }
        return saveFilepath;
    }

没有了

来说两句吧
最新评论