【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

怎样实现一款「视频录制」应用?

  • 2019-10-10
  • 本文字数:3697 字

    阅读完需:约 12 分钟

怎样实现一款「视频录制」应用?

随着智能手机的普及和网络传输技术的发展,人类的传播媒介正在由图文向视频过度。短视频作为一种底层的内容形态,可以渗入到任何领域、任何行业的 App 中。如今,例如社交、金融、电子商务、政务民生等相当一部分的 APP 都在持续集成这一功能。


相比图片和文字,视频所传递的信息更为具象和丰富,视频录制正从「专业短视频制作功能」演变为「一个基础功能」,从而嵌套在各个 APP 的使用场景里。


那么对于有「视频录制」需求的 APP 来说,要怎样实现这一功能呢?在这里,主要以七牛短视频 SDK 中的 Android 平台为例来简单讨论一下此功能的实现方法(当然感兴趣的伙伴也可以直接拉到文末,扫描二维码进行体验)。


需求分析可以更好地辅助落地执行的实施,所以在下手之前,可以先对「视频录制」这样一个需求做一下简单的分析。从功能性需求点上,可以分为以下几块:


1.核心需求


  • 摄像头拍摄视频

  • 麦克风采集音频

  • 能够预览

  • 编码压缩

  • 能够在本地保存为一个 mp4 文件


2.控制型需求


  • 能够控制摄像头拍摄,支持曝光度,闪光灯,前后摄像头切换,画面对焦等功能

  • 能够控制麦克风采集,包括声道数,采样率,音频格式等参数

  • 能够控制最终输出视频的分辨率,码率,FPS 等参数


3.开放型需求


  • 能够支持一些第三方的视频特效(美颜特效,AR 特效等)

  • 能够支持一些第三方的音频特效(变声等)


4.性能和兼容性需求


  • 整个过程的耗时不可太长

  • 能够覆盖尽可能多的 Android 机型


5.高级需求


  • 支持录制时增加背景音乐混音

  • 支持分段拍摄


从以上归纳可以看出,除了核心需求外,在大家的使用场景中,潜在的需求是比较多的。需求决定着架构的设计,只有真正理清楚需求之后,才能去谈如何设计。


比如,NO.3 就决定着我们应该有丰富的回调接口,能够把视频或者音频数据回调给外部,从而满足多样化的二次开发。NO.4 则需要七牛云同时支持硬编码和软编码来减少时耗和增加兼容性。而 NO.5 则是一些比较高级的功能,是一些发散的需求,可能会发散出很多的玩法,这就要求我们对需求的发展及外延有一定的预判能力。


从逻辑层面来说,我们整体的架构图可以是这样的:



宏观来讲,整个过程可分为数据的采集、处理、编码、封装和输出这几个部分。接下来咱们分别简单讨论一下每一个模块大致是如何实现的。

采集模块

采集模块是整个数据的输入源头。对于 Android 平台来说,视频和音频的采集模块主要是用 Camera 和 AudioRecord 来分别实现的。


Camera 能够分别回调 YUV 和纹理两种形式的数据,其相应的方法分别如下:


// 从摄像头回调 YUV 数据void Camera.setPreviewCallbackWithBuffer(PreviewCallback cb);// 从摄像头回调纹理数据void Camera.setPreviewTexture(SurfaceTexture surfaceTexture);
复制代码


这两种数据分别由 CPU 和 GPU 来处理,我们主要用纹理来传递数据,以帮助客户减少耗时。当把一个 SurfaceTexture 作为 Camera 的预览目标,Camera 则会把 SurfaceTexture 创建的 Surface 作为一个输出源,我们通过调用 updateTexImage() 方法,从摄像机采集的图片流中取得每帧图片的纹理。


这里需要说明的是,从 Camera 中获取的纹理并不是我们常用的 GL_TEXTURE_2D 类型,而是 GL_TEXTURE_EXTERNAL_OES 类型,所以我们对纹理设置参数也要使用 GL_TEXTURE_EXTERNAL_OES 类型。同样的,在 shader 中也需要使用 samplerExternalOES 采样方式来声明纹理,如在 FragmentShader 的代码如下:


public static final String TEXTURE_EXTERNAL_FS =        "#extension GL_OES_EGL_image_external : require\n" +        "precision mediump float;\n" +        "uniform samplerExternalOES u_tex;\n" +        "varying vec2 v_tex_coord;\n" +        "void main() {\n" +        "  gl_FragColor = texture2D(u_tex, v_tex_coord);\n" +        "}\n";
复制代码


音频采集则相对简单一些,AudioRecord 的关键使用方法如下:


// AudioRecord 的构造函数,我们可以把一些配置相关的参数传递进去
AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes);// 创建好了AudioRecord实例之后,通过该方法开始麦克风采集void AudioRecord.startRecording();// 在采集的过程中,通过该方法不断的从缓冲区循环提取 PCM 数据int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes);// 停止采集,释放资源void AudioRecord.stop();
复制代码

处理模块

处理模块可以分为「外部处理」和「内部处理」两部分,外部处理是指对第三方合作伙伴的可扩展,内部处理是我们自有的处理逻辑。


拿到摄像机和麦克风采集的数据之后,首先要把数据回调给最外层。当第三方的合作伙伴拿到我们传出的纹理或者 PCM 数据,就可在此基础上做一些特效处理,如音频相关的变声特效、视频相关的人脸识别、美颜、AR、滤镜等等,继而把处理完的数据再次返回给我们。当数据进到的内部处理模块之后,又可以根据自身的处理业务来进行二次处理,例如纹理的裁剪,旋转或者音频相关的混音。


层与层之间的数据传输是通过回调接口来实现的,我们可以设计如下接口来实现视频和音频的数据传输:


// 视频 yuv 数据回调public interface VideoYUVFrameListener { boolean onVideoFrameAvailable(byte[] data, int width, int height, int rotation, int fmt, long timestampNs);}
// 视频纹理数据回调public interface VideoTextureFrameListener { void onSurfaceCreated(); void onSurfaceChanged(int width, int height); void onSurfaceDestroy(); int onVideoFrameAvailable(int texId, int texWidth, int texHeight, long timestampNs, float[] transformMatrix);}
// 音频 PCM 数据回调public interface AudioFrameListener { void onAudioFrameAvailable(byte[] data, long timestampNs);}
复制代码


视频处理模块的主要原理是通过回调机制,用 OpenGL 把相应的特效离屏渲染到纹理上,进而把纹理进行裁剪,缩放,旋转等操作。作为「一张纹理的艺术之旅」,经过如此层层处理后,最终的纹理囊括了各层处理的效果之和。而相比于视频,音频处理模块的主要原理是通过重采样、混音、或一些 3A 算法直接对 PCM 数据作修改。

预览编码模块

视频帧处理完成之后,我们需要把纹理数据传递给一个 SurfaceView 用于预览。此时,上一阶段对纹理的处理结果便可以在此 SurfaceView 上表现出来。另外,我们还需要把该纹理数据传递给视频编码器进行编码。预览和编码虽然是两个线程,但是却共享一个纹理,如此会减少资源的占用,帮助效率的提高。


为了适配更多的 Android 机型,系统在支持 MediaCodec 的同时,也要支持 x264 软编,不过最主要的编码方式应该还是以硬编码为主,原因是硬编码在时耗上要远远优于软编码。在硬编模式下,整个拍摄模块核心实现图如下所示:



可以看出,整个流程是生产者/消费者模式,摄像机首先作为数据的生产者向外提供数据,数据会输出到一个 Surface 上,此 Surface 即 SurfaceTexture 内部创建的。与此同时,我们会通过 SurfaceTexture 源源不断的获取数据,接着把数据通过 GLES 分别渲染到 SurfaceView 的 Surface 和 MediaCodec 的 Surface 中。


下一步, SurfaceFlinger 作为消费者,负责把 SurfaceView 的 Surface 中的数据输出到屏幕。同理,MediaServer 作为消费者,负责把 MediaCodec 的 Surface 中的数据输出到编码器进行编码。


以上是视频编码的方式,相较于视频编码,音频编码则简单的多,我们只需对编码器设定编码参数后持续向编码器输入 PCM 数据即可,编码器会把编码后的数据回调给开发者。

封装和输出模块

mp4 的封包可以用 MediaMuxer 来实现,但从兼容性上来考虑,最好用 FFmpeg 来封包。例如,倘若编码出来的视频带有 B 帧,那么如下图所示,MediaMuxer 仅仅在 Android 7.0 以上才能够支持。



无论是使用 MediaMuxer 抑或 FFmpeg 来封包,最终都会在本地输出一个视频文件,至此大家即完成了从拍摄视频到最终输出的整个流程。


综上流程,我们实现「视频录制」中主要用到的 API 如下图所示:



可以看到,多媒体开发和 APP 开发有所不同,主要用到的是更偏底层的一些 API。除此之外,还需要对音视频的编码标准,常用格式,FFmpeg,OpenGL 等知识有一定的了解,所以开发的门槛还是相对高的。


正是因为有这些门槛,中小型团队、创业初阶段的公司、或不把视频拍摄作为核心业务的团队实在无需像这样从 0 到 1 的重量化地造轮子。这就譬如厨师无需从除草、耕作、施肥、种菜都一一亲力亲为,而是需要把时间和精力用来钻研食材的味道和烹饪本身。


同样的道理,开发者们选择直接用一款适合自己公司的音视频 SDK 其实是效率最高的做法。七牛云短视频 SDK 包含量以上所有需求的实现,帮助 B 端用户节省时间成本和人力成本,帮助用户专注精力于自己的核心业务。在 SDK 的使用上,七牛云大大简化了接入流程,力求缩短从想法到产品的距离。


自从 2017 年上线以来,七牛云已经帮助数以千计的客户快速地集成音视频能力,受到了业内合作伙伴和客户的高度评价。未来,七牛云短视频 SDK 将持续在音视频领域深耕,为更多的客户提供优质的解决方案。


本文转载自公众号七牛云(ID:qiniutek)。


原文链接:


https://mp.weixin.qq.com/s/c4XiFNs66Qa5Yyb3k2zhbw


2019-10-10 18:27735

评论

发布
暂无评论
发现更多内容

千万级学生管理系统的考试试卷存储方案

刘琦Logan

架构实战营 - 模块五作业

en

#架构实战营

真香!肝完Alibaba这份面试通关宝典,我成功拿下今年第15个Offer

收到请回复

Java 面试 大厂Offer 20+大厂面经

阿里P8高级架构师开发高并发系统经验总结

Java 程序员 架构 面试 后端

区块链与智能革命的未来

CECBC

Leetcode 题目解析:287. 寻找重复数

程序员架构进阶

算法 LeetCode 10月月更

【LeetCode】 山峰数组的顶部Java题解

Albert

算法 LeetCode 10月月更

【Flutter 专题】29 图解自定义底部状态栏 ACEBottomNavigationBar (一)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

阿里内部教程:千页Redis源码笔记,涨薪必备

Java 程序员 架构 面试 后端

绿色电力交易是一场迫在眉睫,区块链记录每一笔绿色电力交易

CECBC

链路层的封装成帧和透明传输基本问题

Regan Yue

计算机网络 10月月更

linux之sudo使用技巧汇总

入门小站

Linux

架构实战营 - 模块五作业

Alex.Wu

016云原生之安全技术

穿过生命散发芬芳

云原生 10月月更

Prometheus 基本查询(二)时序数据的瞬时向量

耳东@Erdong

Prometheus 10月月更

Go 中 Nil 理论上有类型,实践中无类型

baiyutang

golang 10月月更

每个数据工程师都应该知道的 6 个 SQL 查询

云原生

sql 职业生涯 数据工程师

ThreadPoolExecutor学习笔记

风翱

ThreadPoolExecutor 10月月更

CSS架构之Acss层

Augus

CSS 10月月更

【Vuex 源码学习】第十三篇 - Vuex 辅助函数的实现

Brave

源码 vuex 10月月更

微博评论高性能高可用计算架构

刘琦Logan

百度智能云布局粤港澳大湾区,打造AI+工业互联网新高地

百度大脑

人工智能 百度

【Android构建新工具】Bazel构建工具介绍

轻口味

android 构建工具 10月月更

SpringBoot 实战:JUnit5+MockMvc+Mockito 做好单元测试

看山

Java Spring Boot Effective Spring 10月月更

架构实战营_模块六作业_拆分电商系统为微服务

Rabbit

在线最大公因数计算器

入门小站

工具

Vue进阶(幺叁陆):el-steps 实现页面内导航

No Silver Bullet

Vue 10月月更

架构实战营模块5课后作业

apple

生命中不重要的九件事情

石云升

10月月更

Mock Service Worker:可用于浏览器的Mock服务

devpoint

Vue Mock msw 10月月更

为何实现碳中和已刻不容缓?

CECBC

怎样实现一款「视频录制」应用?_移动_七牛云_InfoQ精选文章