Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

这篇具有很好参考价值的文章主要介绍了Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

短视频热潮还没有褪去,写这篇文章主要是帮助大部分人,能快速上手实现类似效果,实际上是: CameraX拿相机数据,OpenGL给CameraX提供一个Surface,数据放到OpenGL渲染的线程上去做图像相关操作

OpenGL滤镜来自aserbao_AndroidCamera

视频录制核心代码参考改造自Googlegrafika里面的部分代码,感兴趣的小伙伴,可以细读哦。

注意:文章末尾会贴上本篇文章的最终源代码地址,和快手小红书视频录制效果一样。

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“
GIF压缩失真了,请前往文章末尾下载源码体验

网上有很多,将解OpenGL的文章内容,同样的也很少有文章教大家如何去快速集成实现,大多数文章,上来就是一大篇和别的文章雷同的基础知识点讲解,容易看睡着,对于没有这方面基础的同学很难啃。

二、CameraX拍照的问题

1. CameraX去拍照片:默认图片质量大(可调整),但是:拍照数据返回

2. 没有什么用的CameraX拓展:这玩意并不适合我们,没有这个拓展就用不了,那要这个拓展有什么用。

3. 拍视频并不能ImageAnalysis做滤镜,拍照片可以用ImageAnalysis做滤镜效果

三、自定义GLTextureView

我编写一个GLCameraView方便支持我们的Activity动画打开。

因为我们要给CameraX提供surfaceProvider,所以我们需要实现SurfaceProvider接口

class GLCameraView(context: Context, private val ratio: Float) : GLTextureView(context), SurfaceProvider {
    override fun onSurfaceRequested(request: SurfaceRequest) {
        val resetTexture = resetPreviewTexture(request.resolution) ?: return
        val surface = Surface(resetTexture)
        request.provideSurface(surface, executor) {
            surface.release()
            if(!fromCameraLensFacing) {
                // 注意:切换前置和后置摄像头的时候,不能release surfaceTexture
                surfaceTexture?.release()
            }
        }
    }
    // 其他细节,可以直接用我们文章末尾的源码
}

根据方法提供的参数设置SurfaceTexture#setDefaultBufferSize,然后用surfaceTexture创建一个Surface,将它提供给SurfaceRequest

后面我们结合CameraX的时候就可以把GLCameraView传给Preview#setSurfaceProvider

四、CameraX + GLCameraView预览

1. 提供一个getCameraProvider()将相机的生命周期绑定到 LifecycleOwner

private suspend fun Context.getCameraProvider(): ProcessCameraProvider =
    suspendCoroutine { continuation ->
        ProcessCameraProvider.getInstance(this).also { cameraProvider ->
            cameraProvider.addListener(
                {
                  continuation.resume(cameraProvider.get())
                }, ContextCompat.getMainExecutor(this)
            )
        }
    }

2. 配置相机视图

@Composable
fun CameraXView(
    modifier: Modifier,
    lensFacing: Int,
    content: @Composable (GLCameraView) -> Unit
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    val glCameraPreview = remember {
        // 配置9:16
        GLCameraView(context, 9 / 16F)
    }
    val scope = remember {
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    }
    LaunchedEffect(lensFacing) {
        val cameraProvider = context.getCameraProvider()
        // 解除生命周期绑定,并从 CameraX 中移除
        cameraProvider.unbindAll()

        val preview = Preview.Builder().apply {
            // 设置成9:16
            setTargetAspectRatio(AspectRatio.RATIO_16_9)
        }.build()

        // 需要在preview.setSurfaceProvider前面调用
        glCameraPreview.switchCameraLensFacing(true,lensFacing)
        preview.setSurfaceProvider(glCameraPreview)

        // 设置前置、后置摄像头切换
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(lensFacing)
            .build()
            
        kotlin.runCatching {
            cameraProvider.bindToLifecycle(
                lifecycleOwner,
                cameraSelector,
                preview
            )
        }
    }
    Box(...) {
        DisposableEffect(
            AndroidView(
                factory = { glCameraPreview },
                modifier = Modifier.fillMaxSize()
            )
        ) {
            onDispose {
                scope.launch {
                   glCameraPreview.switchCameraLensFacing(false,lensFacing)
                   val cameraProvider = context.getCameraProvider()
                   // 解除生命周期绑定,并从 CameraX 中移除
                   cameraProvider.unbindAll()
                }
            }
        }
        content(glCameraPreview)
    }
}

从上面的代码片段中,我们可以看到以下内容:

  1. 初始化ProcessCameraProvider的实例,将相机的生命周期绑定到LifecycleOwner
  2. LaunchedEffect(lensFacing) 只有切换前置/后置摄像头会触发里面的逻辑,需要调用cameraProvider.unbindAll() 解除生命周期绑定,并从 CameraX 中移除。
  3. 初始化Preview设置surfaceProvider,这里需要注意的是调用切换摄像头方向的方法需要在setSurfaceProvider前面调用。
  4. 通过CameraSelector构建切换的前置/后置摄像头。
  5. 调用cameraProvider.bindToLifecycle
  6. AndroidViewfactory里面返回我们的glCameraPreview
  7. onDispose里面我们需要调用cameraProvider.unbindAll()解除生命周期绑定,并从 CameraX 中移除。

五、CameraXView三方app如何使用

上面我们给出了CameraXView的实现,我们在content: @Composable (GLCameraView) -> Unit 这里面可以放我们的拍照/拍视频/其他菜单视图,如果是拍视频,需要控制slideGpuFilterGroupEnable,没有拍摄视频前,可以水平切换滤镜,拍摄中是不可以切换的。

1. 拍摄视频用法:

CameraXView(
    modifier = modifier,
    lensFacing = lensFacingProvider()
) { glCameraPreview ->
    LaunchedEffect(recordingStateProvider()) {
       if (null != recordingStateProvider()) {
          // 拍视频
          glCameraPreview.takeVideo(videoRecordingFileUri?.path ?: "")
       }
       // 为null的状态,才可以滑动切换滤镜
       glCameraPreview.slideGpuFilterGroupEnable = (recordingStateProvider() == null)
   }
   
   // TakeVideoOptionsBar里面显示的视频拍摄的时间
   val recordingTimeState = glCameraPreview.getRecordingTimeState().collectAsState()

   TakeVideoOptionsBar(
       // 用于控制里面按钮状态
       recordingState = recordingStateProvider(),
       // 视频拍摄时长(实时的,单位:分钟:秒:毫秒)
       recordingTime = recordingTimeState.value,
       onTakeVideo = {
           // 回调当前状态,再根据viewModel返回的state去做ui数据展示
           onTakeVideoClick.invoke(glCameraPreview.isRecording())
       },
       ....
    )
}

2. 拍照片用法:

CameraXView(
  modifier = modifier,
  lensFacing = lensFacingProvider()
) { glCameraPreview ->
    TakePhotoOptionsBar(
        onTakePhoto = {
            // OpenGL里面去取一帧图片数据,这个数据是秒生成的
            glCameraPreview.takePicture(takePictureFileUri?.path ?: "")
            // 直接通知ViewModel播放一个“快门声音”,并打开需要接收照片数据的页面
            currentOnTakePhotoClick.invoke()
        }
    )
}

这里再插一段,怎么播放快门声音,这里给大家介绍一下:
Ringtone
提供了一种快速播放铃声、通知或其他类似类型声音的方法,我们可以看到官方文档里面提示我们去看RingtoneManager,当然,你也可以用SoundPool

我们在RingtoneManager找到getRingtone可以根据音频文件的Uri返回一个Ringtone,然后可以拿Ringtone#play去播放一段音频,还没结束,我们还需要配置一下音频属性AudioAttributes:

AudioAttributes.Builder().setUsage(...).build()

我们可以看到AudioAttributes.Builder#setUsage,有下面这么多usage可配置

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

篇幅问题,更多细节,大家可以仔细查看官方的文档,这里我们用的是:AudioAttributes.USAGE_MEDIA

当使用是媒体(如音乐或电影配乐)时使用的值

所以:只要我们的设备播放音乐或者视频能听到声音,那么我们这个快门播放也同样可以听到声音。

从上面的用法,我们可以看出:

  1. 可以在CameraXViewcontent: @Composable (GLCameraView) -> Unit 这里面放我们相机页面的其他菜单和操作视图
  2. 拍照基本上是秒生成返回图片数据,比CameraX直接去拍照快的不止一点两点
  3. 照片和视频都支持水平滑动切换滤镜,同样,外部可以通过当前拍摄视频状态,来控制“水平滑动切换滤镜”功能是否可用
  4. 拍照完成有快门声音播放。

六、处理grafik视频保存问题

无论是AndroidCamera还是grafika它们两个用的都是同一套视频录制方法,同样的在源码中一开始注释的内容是:

// We don't know when these will actually finish (or even start). 
// We don't want to delay the UI thread though, so we return immediately.

很显然,它并不知道这些何时会真正完成,那么怎么去解决呢?

点击停止录制的时候,肯定需要找到stopRecording对吧,我们进这里面看看

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

这个骚操作,我们得治治,我们先调用:

mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT))

然后再直接调用handleStopRecording方法,这里需要注意

需要将耗时操作移动到gl线程外面,否则OpenGL ES所在的线程被阻塞或者被挂起,导致渲染设备上下文丢失(直接表现就是:画面不会再动,无法继续绘制)

切记切记!!

所以这里我们可以放在子线程里面去执行handleStopRecording方法。

我们稍微修改了一下handleStopRecording方法里面的内容

private fun handleStopRecording() {
        Log.d(TAG, "handleStopRecording")
        kotlin.runCatching {
            mVideoEncoder?.drainEncoder(true)
        }
        mVideoEncoder?.stopAudRecord()
        releaseEncoder()
}

我们可以看到多了一个try/catch,为什么?drainEncoder方法的注释中写到它:

从编码器中提取所有待处理的数据并将其转发到复用器

CameraDrawer#onDrawFrame,系统会在每次重新绘制GLTextureView时调用此方法。
然后会触发我们的TextureMovieEncoder#handleFrameAvailable

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

这里我们针对上面红色框区域的代码做调整:

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

强行停止结束,会导致drainEncoder异常终止,需要捕获一下异常,如果发现异常同样需要停止后面的代码执行

我们在TextureMovieEncoder里面看到fun handleFrameAvailable的调用上一级来自TextureMovieEncoder#frameAvailable

同样我们需要调整这个方法里面的代码,调整前:

mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE,
     (int) (timestamp >> 32), (int) timestamp, transform));

调整后:

if(isRecording){
    mHandler?.apply {
        sendMessage(
            obtainMessage(
                MSG_FRAME_AVAILABLE,
                (timestamp shr 32).toInt(),
                timestamp.toInt(),
                transform
            )
        )
    }
}

我们再看一下TextureMovieEncoder的源码,里面只有触发了Looper#quitisRecording才为false,因为我们上面修改了TextureMovieEncoder#stopRecording方法,提前调用了Looper#quit,所以这里需要同步停止发送MSG_FRAME_AVAILABLE消息。

核心解决处理的关键代码,全部讲完。

下面就来看看我们最终提供给大家的项目功能视频吧,拍照和拍视频都支持水平滑动切换滤镜功能

Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“
GIF压缩失真了,请下载源码体验

目前遗留有个问题是:切换前置摄像头和后置摄像头会有个颠倒的问题,除了这个问题,一切正常满足生产开发需求。

源码地址

如果你可以访问github网站,点击此链接:
https://github.com/TheMelody/ComposeCameraxOpenGL

如果 不能访问github ,可点击这里下载,我们建议到 github 上下载:
https://download.csdn.net/download/logicsboy/87328624

点赞❤️+关注❤️+收藏❤️+评论❤️,划走了可就找不到了哦文章来源地址https://www.toymoban.com/news/detail-415994.html

到了这里,关于Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python 实现海康机器人工业相机 MV-CU060-10GM 的实时显示视频流及拍照功能

    一、背景介绍 1、最近项目中需要给客户对接海康机器人工业相机 MV-CU060-10GM; 2、客户要求通过部署的管理平台,可以在页面上实现如下功能: 1)相机视频流开始预览; 2)相机视频流停止预览; 3)相机拍照功能。 需求背景:客户需要对生产的产品进行定期抽样质检,其中

    2024年02月08日
    浏览(77)
  • Android OpenGL EGL使用——自定义相机

    如果要使用OpenGl来自定义相机,EGL还是需要了解下的。 可能大多数开发者使用过OpengGL但是不知道EGL是什么?EGL的作用是什么?这其实一点都不奇怪,因为Android中的GlSurfaceView已经将EGL环境都给配置好了,你一直在使用,只是不知道他的存在罢了。 很多人可能在使用OpenGl ES渲

    2024年01月22日
    浏览(39)
  • 【Android】使用 CameraX 实现基础拍照功能

    目录 1. 基础开发环境 2. 添加相关依赖 3. APP 布局 4. 主流程逻辑 5. 调试或安装 APK 6. 项目完整代码 JDK:JDK17 Android Studio:Android Studio Giraffe | 2022.3.1 Android SDK:Android API 34 Gradle: gradle-7.2-bin.zip CameraX Version: 1.1.0-alpha05 在 build.gradle 中添加 CameraX 的相关依赖 在 AndroidManifest.xml 文件

    2024年02月14日
    浏览(40)
  • 【Android】使用 CameraX 实现基础录像功能

    目录 1. 基础开发环境 2. 添加相关依赖 3. APP 布局 4. 主流程逻辑 5. 调试或安装 APK 6. 项目完整代码 JDK:JDK17 Android Studio:Android Studio Giraffe | 2022.3.1 Android SDK:Android API 34 Gradle: gradle-8.0-bin.zip CameraX Version: 1.1.0-alpha05 在 build.gradle 中添加 CameraX 的相关依赖 在 AndroidManifest.xml 文件中

    2024年02月14日
    浏览(42)
  • Android相机调用-CameraX【外接摄像头】【USB摄像头】

    Android相机调用有原生的Camera和Camera2,我觉得调用代码都太复杂了,CameraX调用代码简洁很多。 说明文档:https://developer.android.com/jetpack/androidx/releases/camera?hl=zh-cn 现有查到的调用资料都不够新,对于外接摄像头(USB摄像头)这类非前置也非后置摄像头的设备调用,都说是没有实

    2024年02月09日
    浏览(55)
  • 使用手机摄像头实现视频监控实时播放

    视频监控实时播放的原理与目前较为流行的直播是一致的,所以采用直播的架构实现视频监控实时播放,流程图如下: 目前实时视频流的传输协议有以下几种:RTSP、RTMP、HLS、Http-flv。 安卓APP开发使用HBuilder,而HBuilder内置了LivePusher直播推流控件,该控件使用了RTMP协议,所以

    2023年04月08日
    浏览(50)
  • 使用 MFC 和 OpenCV 实现实时摄像头视频显示

    1、引言 MFC 是一个在 Windows 平台上编写 C++ 应用程序的库,提供了丰富的用户界面功能。OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,包含了丰富的图像处理和计算机视觉功能。本文将向大家展示如何将这两个库结合起来,实现一个实时显示摄像头画面的简

    2024年02月13日
    浏览(98)
  • 【OpenGL】杂谈一、通过鼠标拖拽实现相机绕空间中的某点进行球面旋转查看

    这是我最近遇到的一个问题,如题所示,我需要通过鼠标拖拽实现相机绕点的球面旋转,原本的想法很简单,类似笔记八、摄像机中提到的那样,从聚焦中心点的视角出发,将鼠标移动的距离xoffset和yoffset转换为yaw和pitch角,然后计算出该视角的向量,与球面求交从而得到相机

    2024年02月02日
    浏览(49)
  • 【NET 7.0、OpenGL ES】使用Silk.NET渲染MMD,并实时进行物理模拟。

    有关mmd播放器,网上也有许多非常漂亮的实现,如 pmxeditor、saba、blender_mmd_tools等等。。 首先我想先介绍下我参考实现的仓库: sselecirPyM/Coocoo3D: Experimental MMD renderer using DX12 and DXR. (github.com),这是sselecirPyM大神使用NET 6.0和DX12实现的mmd渲染器,支持自定义渲染管线、光照等,感

    2024年02月08日
    浏览(41)
  • libVLC 提取视频帧使用OpenGL渲染

    在上一节中,我们讲解了如何使用QWidget渲染每一帧视频数据。 由于我们不停的生成的是QImage对象,因此对 CPU 负荷较高。其实在绘制这块我们可以使用 OpenGL去绘制,利用 GPU 减轻 CPU 计算负荷,本节讲解使用OpenGL来绘制每一帧视频数据。 libVLC 提取视频帧使用QWidget渲染-CSDN博

    2024年04月10日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包