惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Apple Machine Learning Research
Apple Machine Learning Research
Blog — PlanetScale
Blog — PlanetScale
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
G
Google Developers Blog
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Threat Research - Cisco Blogs
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Cloudflare Blog
博客园 - 【当耐特】
AWS News Blog
AWS News Blog
U
Unit 42
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
T
Tor Project blog
Microsoft Security Blog
Microsoft Security Blog
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Recorded Future
Recorded Future
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
Latest news
Latest news
GbyAI
GbyAI
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
Hacker News: Ask HN
Hacker News: Ask HN
美团技术团队
N
News | PayPal Newsroom
J
Java Code Geeks
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Hacker News
The Hacker News
The GitHub Blog
The GitHub Blog
V
V2EX
N
News and Events Feed by Topic
T
Troy Hunt's Blog
Security Latest
Security Latest
博客园 - 叶小钗
P
Palo Alto Networks Blog

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 MediaCodec 硬编码之相机内容编码成 H264 文件 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
【WebRTC 专栏】--创建相机预览
音视频开发进阶 · 2021-09-02 · via 音视频开发进阶

一个专注音视频领域的小圈子

用 WebRTC 创建相机预览,不到 50 行核心代码就可以轻松搞定了。

直接使用官方给的版本就好了,不需要再去额外编译。

implementation 'org.webrtc:google-webrtc:1.0.30039'

后面都会使用该版本做测试的。

相机权限申请

WebRTC 虽说功能强大,代码简洁,但是并没有封装一个应用权限申请的接口,这需要自己去操作了。

相机预览

有个段子是把大象放进冰箱有多少步骤,共三步,打开冰箱,塞进大象,关上冰箱。

用 WebRTC 创建相机预览和上面的段子步骤一样,打开相机,设置接收,开启预览。

至于中间的繁琐步骤,比如相机创建的内部实现,预览绘制的内部实现都不用去关心了,调用好接口,设置好参数就行。

创建相机实例

在 WebRTC 中相机实例统一继承了 VideoCapturer 接口,不管是 Camera1 还是 Camera2 。

public interface VideoCapturer {
    void initialize(SurfaceTextureHelper var1, Context var2, CapturerObserver var3);

    void startCapture(int var1, int var2, int var3);

    void stopCapture() throws InterruptedException;

    void changeCaptureFormat(int var1, int var2, int var3);

    void dispose();

    boolean isScreencast();
}

该接口也比较简单,只需要相机实例对外提供一些简单的预览能力就好。

创建相机实例的代码如下:

private fun createVideoCapture(): VideoCapturer? {
    val enumerator = Camera1Enumerator(false)
    val deviceNames = enumerator.deviceNames

    for (deviceName in deviceNames) {
        if (enumerator.isFrontFacing(deviceName)) {
            val videoCapture = enumerator.createCapturer(deviceName, null)
            if (videoCapture != null) {
                return videoCapture
            }
        }
    }
    return null
}

Camera1Enumerator 是用来枚举设备上有多少摄像头的,一般只有前置和后置两种,,也可以用 Camera2Enumerator 来获取 Camera2 的相机调用。

deviceNames 对应 getDeviceNames 方法,只不过用了 kotlin 变成缩写了,它表示设备上的摄像头集合,这个接口其实就已经屏蔽了 Camera1 和 Camera2 内部检索不同摄像头的实现。

满足前后置条件时,调用 createCapturer 来创建相机实例就好了。

相机预览接收

需要有分别对应的组件去接收相机输出的画面并且显示到屏幕上。

显示到屏幕上的控件既不是 SurfaceView 也不是 TextureView ,而是 WebRTC 自己封装的控件 SurfaceViewRenderer 。

它其实就是继承了 SurfaceView ,并且内部有个 SurfaceEglRenderer 变量,用来将外界传递的 VideoFrame 绘制到屏幕上。

<org.webrtc.SurfaceViewRenderer android:id="@+id/localView"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"/>

// SurfaceViewRenderer 的绘制方法
public void onFrame(VideoFrame frame) {
        this.eglRenderer.onFrame(frame);
}

SurfaceEglRenderer 也是走的 OpenGL 渲染进行预览,在创建 OpenGL 环境可以决定是否要以 ShareContext 的形式创建。

val eglBaseContext = EglBase.create().eglBaseContext
localView.init(eglBaseContext, null)

接收相机预览流的组件就是 SurfaceTexture ,只不过 WebRTC 将它包装到了 SurfaceTextureHelper 变量中。

创建 SurfaceTextureHelper 的方法如下:

val eglBaseContext = EglBase.create().eglBaseContext
val surfaceTextureHelper = surfaceTextureHelper.create("CaptureThread", eglBaseContext)

SurfaceTextureHelper 内部会创建一个线程,并且也可以通过外部传递 EGLContext 以决定是否要走 ShareContext 方式的调用。

有了相机实例 VideoCapturer 和接收预览的组件 SurfaceTextureHelper ,就可以将他们关联起来:

videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)
videoCapture?.startCapture(480, 640, 30)

videoCapture 调用 initialize 方法实现两者的关联,同时 startCapture 方法决定相机采集的宽高和帧率。

开启相机预览

在开启相机预览时,就需要涉及到和 WebRTC 相关内容了。

WebRTC 本身是用来做即时通信的,它将音频和视频流都抽象成了一个个轨道 MediaStreamTrack ,有音频轨 AudioTrack 也有视频轨 VideoTrack。

而轨道上的内容来源就对应 MedisSource ,有音频源 AudioSource 和视频源 VideoSource 。

相机输出就是提供视频源的,需要将 VideoCapturer 和 VideoSource 关联起来。

在上面代码中 initialize 方法实际上就建立了关联。

videoSource = videoCapture?.isScreencast?.let { factory.createVideoSource(it) }
videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)

initialize 方法的最后一个参数就是一个回调,典型的观察者模式,VideoCapturer 相关的状态都会通过 capturerObserver 通知到 VideoSource ,从而实现关联。

创建 videoSource 的 factory ,对应的就是一条即时通信端对端的连接,而 videoTrack 和 audioTrack 就是这条连接上的内容。

创建 factory 的代码比较固定:

val options = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();
PeerConnectionFactory.initialize(options)
factory = PeerConnectionFactory.builder().createPeerConnectionFactory()

创建 VideoTrack 的代码如下,需要将视频源和视频轨道关联起来。

videoTrack = factory.createVideoTrack("101",videoSource)

完成了所有的创建和关联之后,就可以开启预览了。需要将视频轨道内容显示到画面上,也就是上面的 SurfaceViewRenderer 控件上。

videoTrack?.addSink(localView)

完整代码示例:

class CameraActivity : AppCompatActivity() {

    private lateinit var factory: PeerConnectionFactory
    private var videoCapture:VideoCapturer? = null
    private var videoSource: VideoSource? = null
    private var videoTrack: VideoTrack? = null
    private lateinit var localView:SurfaceViewRenderer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_camera)
        localView = findViewById(R.id.localView)
        
        val options = PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions();
        PeerConnectionFactory.initialize(options)
        factory = PeerConnectionFactory.builder().createPeerConnectionFactory()

        val eglBaseContext = EglBase.create().eglBaseContext
        val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext)

        videoCapture = createVideoCapture()
        videoSource = videoCapture?.isScreencast?.let { factory.createVideoSource(it) }

        videoCapture?.initialize(surfaceTextureHelper, applicationContext, videoSource?.capturerObserver)
        videoCapture?.startCapture(480, 640, 30)

        localView.setMirror(true)
        localView.init(eglBaseContext, null)

        videoTrack = factory.createVideoTrack("101",videoSource)
        videoTrack?.addSink(localView)
    }

    private fun createVideoCapture(): VideoCapturer? {
        val enumerator = Camera1Enumerator(false)
        val deviceNames = enumerator.deviceNames

        for (deviceName in deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                val videoCapture = enumerator.createCapturer(deviceName, null)
                if (videoCapture != null) {
                    return videoCapture
                }
            }
        }
        return null
    }
}

不到 50 行代码就完成了相机预览,Github 仓库地址后续会给出。

这篇文章就先讲到这里,持续更新中~~

原创文章,转载请注明来源:    【WebRTC 专栏】--创建相机预览