android 录制屏幕 带声音 可直播方案 截屏

本文详述如何在Android中实现录屏并同时录制声音,结合MediaCodec、MediaMuxer和MediaRecorder等组件,创建可直播的解决方案。文章涵盖权限申请、关键类的使用以及异步录屏方法,还提供了截图功能和代码示例。

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

这篇博客应该是相当有分量的博客了。篇幅会比较长,因为内容很多。我尽力的想写的详细,而又不至于繁琐。这之间的程度是很难把握的,话不多说 进入主题。
首先,在这之前,需要对几个类,以及他们的方法的有所了解。

MediaCodec

谷歌对这个类的描述如下,MediaCodec类可用于访问底层媒体编解码器,即编码器/解码器组件。它是Android底层多媒体支持基础架构的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用)。重点是 编码解码器,因为系统产生的数据 ,都是原始的数据,需要他进行处理。

原理:
下面这张图,只需要粗略看一看,你只需要知道 MediaCodec 有两个ByteBuffer,一个是输入,一个是输出。这也很好理解。毕竟编码解码器,肯定是要你给它旧数据,它编码解码完,还给你一个新数据。两个ByteBuffer 就相当于两个篮子,接受发送数据。
在这里插入图片描述
重要方法:

//返回要用有效数据填充的输入缓冲区的索引,如果当前没有可用的缓冲区,则返回-1。如果timeoutUs == 0,该方法将立即返回;如果timeoutUs < 0,则无限期等待输入缓冲区的可用性;如果timeoutUs > 0,则等待“timeoutUs”微秒。
public int dequeueInputBuffer (long timeoutUs)

这个方法呢,就是返回 输入缓冲区的索引(mediaCodec可以通过索引找到缓冲区)。也就是上面的ByteBuffer。

//通过上面的索引,找到输入缓冲区。
public ByteBuffer getInputBuffer (int index)

注意 上面这个是input

//返回输出缓冲区队列索引,最多阻塞“timeoutUs”微秒。返回已成功解码的输出缓冲区和INFO_*常量之一的索引。
//info 就是描述输出缓冲区数据的,例如时间,大小
public int dequeueOutputBuffer (MediaCodec.BufferInfo info,  long timeoutUs)
//通过上面的索引,找到输出缓冲区。
public ByteBuffer getOutputBuffer (int index)

注意 上面这个是output

//释放输出缓冲区 ,这个也好理解,你从输出缓冲区取完数据了,得要把缓冲区清空,放回去,取下一次的数据
public void releaseOutputBuffer (int index,  boolean render)

MediaMuxer

谷歌描述:MediaMuxer为muxing基本流提供便利。目前MediaMuxer支持MP4、Webm和3GP文件作为输出。它还支持muxing b帧在MP4自从Android牛轧糖。
上面编码解码完的数据,还需要写入到文件里面,这个类呢,主要就是帮助我们写文件的。

重要方法

//添加具有指定格式的跟踪。
public int addTrack (MediaFormat format)

上面这个呢,如果你了解视频的话就知道,视频里面画面 和声音 是两个不同的东西,但是都在一个文件里面。所以,他们有一个叫信道的东西。比如,声音在1信道,画面在2信道之类的。MediaMuxer只有知道信道,才知道接下来的数据要写到哪里。

//写入数据的方法
public void writeSampleData (int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo)

上面这个 byteBuf参数 就是mediaCodec的输出缓冲区,info 也是mediaCodec输出缓冲区的,我们可以info里面的值进行更改,例如时间,这样就可以暂停视频

附加:
录制视频画面的方向。

public void setOrientationHint (int degrees)

要注意,应该在start()之前调用这个方法

MediaRecorder

这个类呢,综合了上面mediaCodec和mediaMuxer,使用这个类,你可以很轻松的录制到本地。这个只能视频录制到本地,不能用于直播,因为你取不到编码后的数据。

权限申请

录屏权限

录屏权限是需要申请的,每次录制都必须要申请。

           //获取MediaProjectionManager ,通过这个类 申请权限,
            // 录屏是一个危险的权限,所以每次录屏的时候都得这么申请,用户同意了才行
            MediaProjectionManager  projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
            //开启activity 然后在onActivityResult回调里面判断 用户是否同意录屏权限
            startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUSET_VIDEO);

申请后需要查看用户是否同意,在onActivityResult周期里面查看

  @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);
    
        }
    }

注意上面的 MediaProjection我下面所有的代码,都会用它,一定要记得了。

录音和读写文件权限

在androidManiFest.xml 里面

    <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" />

从android6.0以后还需要 动态申请一下。

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

在onRequestPermissionsResult周期里面得到结果

   @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();
                }
            }
        }
    }

MediaRecorder使用范例

注意将上面申请录屏后生成的MediaProjection 传入

    //录制视频 放在子线程最好,所以线程
    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(), "录屏线程内部开始工作");
            } catch (IllegalStateException
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值