Android固定帧率录屏

本文介绍了一个在Android上实现固定帧率屏幕录制的解决方案,通过MediaCodec和VirtualDisplay技术,创建了一个名为MyShoter的类,用于处理编码、帧率控制和数据传输。同时,还提供了一个EGLRender类,用于处理OpenGL相关的帧率控制和屏幕截图。在Activity中,通过MediaProjectionManager获取屏幕权限并启动录制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近搞了一个固定的帧率录屏的软件,硬生生把我一个对这一块的小白搞成了对这一块有深刻理解的人。

固定帧率录制就是MediaCodec中的Surface自定义一个,然后MediaCodec的Surface和自己定义的进行数据的传输。

Demo暂时不放了,出于公司的原因。关键代码已有,拷贝即可使用。

上代码:

1.录屏类:

public class MyShoter implements Runnable {

    private static final String TAG = MyShoter.class.getSimpleName();

    private static final String VIDEO_MIME_TYPE = "video/avc";
    private static final int IFRAME_INTERVAL = 10;
    private static final int TIMEOUT_US = 10000;


    /**
     * 录制的分辨率
     */
    private int mWidth;
    private int mHeight;

    /**
     * 录制的比特率
     */
    private int mBitRate;

    /**
     * 录制的帧数
     */
    private int mFrameRate;     // 30 FPS

    /**
     * 这个数传 1 就可以
     */
    private int mDpi;

    /**
     * 文件保存路径
     */
    private String mSaveFilePath;

    private MediaProjection mMediaProjection;
    private MediaCodec mVideoEncoder;
    private Surface mSurface;
    private MediaMuxer mMuxer;

    private boolean mMuxerStarted = false;
    private int mVideoTrackIndex = -1;

    private AtomicBoolean mAtomicQuit = new AtomicBoolean(false);

    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();

    private VirtualDisplay mVirtualDisplay;
    private DisplayManager displayManager;

    public MyShoter(int width, int height, int bitRate, int dpi, int fps,
                    MediaProjection mediaProjection, String filePath) {
        this.mWidth = width;
        this.mHeight = height;
        this.mBitRate = bitRate;
        this.mDpi = dpi;
        this.mFrameRate = fps;
        this.mMediaProjection = mediaProjection;
        if (!filePath.endsWith(".mp4"))
            filePath += ".mp4";
        this.mSaveFilePath = filePath;
    }


    public void stopShot() {
        if (eglRender != null) {
            eglRender.stop();
        }
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void run() {

        Log.e(TAG, "run: " + mWidth + "   " + mHeight + "  " + mBitRate + "  " + mDpi + "  " +
                mFrameRate);

        try {

            prepareEncoder();

            mMuxer = new MediaMuxer(mSaveFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            Log.e(TAG, "run: mMuxer init");
            int formatWidth = mWidth;
            int formatHeight = mHeight;
            if ((formatWidth & 1) == 1) {
                formatWidth--;
            }
            if ((formatHeight & 1) == 1) {
                formatHeight--;
            }
            if (mMediaProjection != null) {
                mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen", formatWidth, formatHeight, mDpi,
                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, eglRender.getDecodeSurface(), null, null);
            } else {
                mVirtualDisplay = displayManager.createVirtualDisplay("screen", formatWidth, formatHeight, mDpi,
                        eglRender.getDecodeSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC);
            }
            startRecordScreen();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("Muxur:::", e);
        } finally {
            releaseEncoder();
        }
    }


    /**
     * 开始录屏
     */
    private void startRecordScreen() {
        eglRender.start();
        releaseEncoder();
    }

    private EGLRender eglRender;

    private void prepareEncoder() throws IOException {
        int formatWidth = mWidth;
        int formatHeight = mHeight;
        if ((formatWidth & 1) == 1) {
            formatWidth--;
        }
        if ((formatHeight & 1) == 1) {
            formatHeight--;
        }
        //类型
        MediaFormat format =
                MediaFormat.createVideoFormat(VIDEO_MIME_TYPE, formatWidth, formatHeight);

        //颜色格式
        //从Surface当中获取的
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        //码率
        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
        //帧率
        format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
        //关键帧间隔
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
        //编码器
        mVideoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
        //将参数配置给编码器
        mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        //交给虚拟屏幕 通过OpenGL 将预览的纹理 会知道这一个虚拟屏幕中
        //这样MediaCodec就会自动编码mInputSurface当中的图像了
        mSurface = mVideoEncoder.createInputSurface();
        //录屏的帧率
        eglRender = new EGLRender(mSurface, formatWidth, formatHeight, mFrameRate);
        eglRender.setCallBack(new EGLRender.onFrameCallBack() {
            @Override
            public void onUpdate() {
                startEncode();

            }

            @Override
            public void onCutScreen(Bitmap bitmap) {

            }
        });
        mVideoEncoder.start();
    }

    private void startEncode() {
        ByteBuffer[] byteBuffers = null;
        if (Build.VERSION.SDK_INT< 21) {
            byteBuffers = mVideoEncoder.getOutputBuffers();
        }
        int index = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
        if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            resetOutputFormatNew();
        } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
            Log.d("---", "retrieving buffers time out!");
            try {
                // wait 10ms
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
        } else if (index >= 0) {
            if (Build.VERSION.SDK_INT< 21) {
                encodeToVideoTrackNew(byteBuffers[index]);
            } else {
                encodeToVideoTrackNew(mVideoEncoder.getOutputBuffer(index));
            }
            mVideoEncoder.releaseOutputBuffer(index, false);
        }
    }
    private byte[] sps=null;
    private byte[] pps=null;

    private void encodeToVideoTrackNew(ByteBuffer encodeData) {
        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
            mBufferInfo.size = 0;
        }
        if (mBufferInfo.size == 0) {
            Log.d(TAG, "info.size == 0, drop it.");
            encodeData = null;
        } else {
            Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size
                    + ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
                    + ", offset=" + mBufferInfo.offset);
        }
        if (encodeData != null) {
            encodeData.position(mBufferInfo.offset);
            encodeData.limit(mBufferInfo.offset + mBufferInfo.size);
            mMuxer.writeSampleData(mVideoTrackIndex, encodeData, mBufferInfo);
            byte[] bytes;
            if (mBufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
                //todo 关键帧上添加sps,和pps信息
                bytes = new byte[mBufferInfo.size + sps.length + pps.length];
                System.arraycopy(sps
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值