最后
这里我特地整理了一份《Android开发核心知识点笔记》,里面就包含了自定义View相关的内容
除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。
分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~
喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
意思是说,这个操作需要在前台服务中进行。
那我们就写一个服务,并把 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()
复制代码
几行代码就可以实现屏幕采集编码传输~非常的方便
文末
那么对于想坚持程序员这行的真的就一点希望都没有吗?
其实不然,在互联网的大浪淘沙之下,留下的永远是最优秀的,我们考虑的不是哪个行业差哪个行业难,就逃避掉这些,无论哪个行业,都会有他的问题,但是无论哪个行业都会有站在最顶端的那群人。我们要做的就是努力提升自己,让自己站在最顶端,学历不够那就去读,知识不够那就去学。人之所以为人,不就是有解决问题的能力吗?挡住自己的由于只有自己。
Android希望=技能+面试
- 技能
- 面试技巧+面试题
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!