• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

一行代码实现安卓屏幕采集编码

android 搞代码 4年前 (2022-03-01) 27次浏览 已收录 0个评论
文章目录[隐藏]

越来越多的App须要共享手机屏幕给别人观看,特地是在线教育行业。Android 从5.0开始反对了MediaProjection,利用MediaProjection ,能够实现截屏录屏性能。

本库对屏幕采集编码进行了封装,简略的调用即可实现MediaProjection权限申请,H264硬编码,错误处理等性能。

特点

  • 适配安卓高版本
  • 应用 MediaCodec 异步硬编码
  • 编码信息可配置
  • 告诉栏显示
  • 链式调用

应用

ScreenShareKit.init(this)
          .onH264{ buffer, isKeyFrame, ts ->

         }.start()

Github

 源码地址

实现

1 申请用户受权屏幕采集

@TargetApi(Build.VERSION_CODES.M)
    fun requestMediaProjection(encodeBuilder: EncodeBuilder){
        this.encodeBuilder = encodeBuilder;
        mediaProjectionManager  = activity?.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        startActivityForResult(mediaProjectionManager?.createScreenCaptureIntent(), 90000)
    }

startActivityForResult 是须要在 Activity 或者 Fragment中应用的,受权后果会在 onActivityResult 中回调。所以咱们须要对这一步进行一个封装,使其能以回调到形式拿到后果。这里咱们采纳一个无界面的 Fragment,有很多库都是应用这种模式。

private val invisibleFragment : InvisibleFragment
        get() {
            val existedFragment = fragmentManager.findFragmentByTag(FRAGMENT_TAG)
            return if (existedFragment != null) {
                existedFragment as InvisibleFragment
            } else {
                val invisibleFragment = InvisibleFragment()
                fragmentManager.beginTransaction()
                    .add(invisibleFragment, FRAGMENT_TAG)
                    .commitNowAllowingStateLoss()
                invisibleFragment
            }
        }

fun start(){
    invisibleFragment.requestMediaProjection(this)
}

这样咱们就能够在一个无界面的 Fragment 中拿到 onActivityResult中的受权后果和 MediaProjection 对象。

2.适配安卓10

如果 targetSdkVersion 设置的 29及以上,在获取到 MediaProjection 后调用 createVirtualDisplay ,将会收到一条异样

java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION

意思是说,这个操作须要在前台服务中进行。

那咱们就写一个服务,并把 onActivityResult 获取到的后果全传过来。

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.let {
            if(isStartCommand(it)){
                val notification = NotificationUtils.getNotification(this)
                startForeground(notification.first, notification.second) //告诉栏显示
                startProjection(
                    it.getIntExtra(RESULT_CODE, RESULT_CANCELED), it.getParcelableExtra(
                        DATA
                    )!!
                )
            }else if (isStopCommand(it)){
                stopProjection()
                stopSelf()
            }
        }
        return super.onStartCommand(intent, flags, startId)
    }

在 startProjection 办法中,咱们须要获取 MediaProjectionManager,再获取 MediaProjection,接着创立一个虚构显示屏。

private fun startProjection(resultCode: Int, data: Intent) {
        val mpManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        if (mMediaProjection == null) {
            mMediaProjection = mpManager.getMediaProjection(resultCode, data)
            if (mMediaProjection != null) {
                mDensity = Resources.getSystem().displayMetrics.densityDpi
                val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
                mDisplay = windowManager.defaultDisplay
                createVirtualDisplay()
                mMediaProjection?.registerCallback(MediaProjectionStopCallback(), mHandler)
            }
        }
    }

private fun createVirtualDisplay() {
        mVirtualDisplay = mMediaProjection!!.createVirtualDisplay(
            SCREENCAP_NAME,
            encodeBuilder.encodeConfig.width,
            encodeBuilder.encodeConfig.height,
            mDensity,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
            surface,
            null,
            mHandler
        )
    }

在 createVirtualDisplay 办法中,有一个 Surface 参数,屏幕上的所有动作,都会映射到这个 Surface 中,这里咱们应用 MediaCodec 创立一个输出Surface用来接管屏幕的输入并编码。

3.MediaCodec 编码

 private fun initMediaCodec() {
        val format = MediaFormat.createVideoFormat(MIME, encodeBuilder.encodeConfig.width, encodeBuilder.encodeConfig.height)
        format.apply {
            setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) //色彩格局
            setInteger(MediaFormat.KEY_BIT_RATE, encodeBuilder.encodeConfig.bitrate) //码流
            setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR)
            setInteger(MediaFormat.KEY_FRAME_RATE, encodeBuilder.encodeConfig.frameRate) //帧数
            setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
        }
        codec = MediaCodec.createEncoderByType(MIME)
        codec.apply {
            setCallback(object : MediaCodec.Callback() {
                override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                }
                override fun onOutputBufferAvailable(
                    codec: MediaCodec,
                    index: Int,
                    info: MediaCodec.BufferInfo
                ) {
                    val outputBuffer:ByteBuffer?
                    try {
                        outputBuffer = codec.getOutputBuffer(index)
                        if (outputBuffer == null){
                            return
                        }
                    }catch (e:IllegalStateException){
                        return
                    }
                    val keyFrame = (info.flags and  MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0
                    if (keyFrame){
                        configData = ByteBuffer.allocate(info.size)
                        configData.put(outputBuffer)
                    }else{
                        val data = createOutputBufferInfo(info,index,outputBuffer!!)
                        encodeBuilder.h264CallBack?.onH264(data.buffer,data.isKeyFrame,data.presentationTimestampUs)
                    }
                    codec.releaseOutputBuffer(index, false)

                }

                override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                    encodeBuilder.errorCallBack?.onError(ErrorInfo(-1,e.message.toString()))
                }

                override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
                }

            })
            configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            surface = createInputSurface()
            codec.start()
        }
    }

以上进行了一些惯例的配置,MediaFormat 能够为编码器设置一些参数,比方码率,帧率,关键帧 距离等。

MediaCodec 编码提供同步异步两种形式,这里采纳异步设置回调的形式(异步 API 21以上可用)

4.封装作用

在 onOutputBufferAvailable 回调中,我曾经将编码后的数据回调进来,并且判断了是关键帧还是一般帧。那封装这个库有什么用呢????
其实,能够联合一些第三方的音视频SDK,间接将编码后的屏幕流数据通过第三方SDK推流,就能实现屏幕共享性能。

这里以 anyRTC 音视频SDK的 pushExternalVideoFrame办法为例

        val rtcEngine = RtcEngine.create(this,"",RtcEvent())
        rtcEngine.enableVideo()
        rtcEngine.setExternalVideoSource(true,false,true)
        rtcEngine.joinChannel("","111","","")
        ScreenShareKit.init(this)
            .onH264 {buffer, isKeyFrame, ts ->
                rtcEngine.pushExternalVideoFrame(ARVideoFrame().apply {
                    val array = ByteArray(buffer.remaining())
                    buffer.get(array)
                    bufType = ARVideoFrame.BUFFER_TYPE_H264_EXTRA
                    timeStamp = ts
                    buf = array
                    height = Resources.getSystem().displayMetrics.heightPixels
                    stride = Resources.getSystem().displayMetrics.widthPixels
                })
            }.start()

几行代码就能够实现屏幕采集编码传输~十分的不便、
关注我,每天分享常识干货~


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:一行代码实现安卓屏幕采集编码

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址