怎么实现一个视频录制应用,教您实现视频录制

JSON 2019-09-03 18:18:46 34320

互联网时代随着智能手机的普及和网络传输技术的发展,人类的传播媒介正在由图文向视频过度。比如眼下火爆的抖音、快手、火山小视频等等,短视频作为一新型的内容形态,可以涉及到任何领域、任何行业的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的使用上,七牛云大大简化了接入流程,力求缩短从想法到产品的距离。

欢迎点击链接:https://jinshuju.net/f/gcxLqZ?x_field_1=sojson 免费领取 license 时长为2个月的七牛云「短视频SDK专业版」使用权以及专属优惠。限时限量,不容错过!

「体验是最直观的测试」


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


版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/334.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

关于作者
一个低调而闷骚的男人。
相关文章
AI 视频云 VS 窄带高清,谁是视频时代的宠儿
Java 实现多个二级域名访问同一个Tomcat(系统)。
自媒体时代的贤内助——AI 视频
利用七牛镜像扒网站的源代码,操作视频讲解。
Shiro教程(十一)Shiro 控并发登录人数限实现,登录踢出实现
HttpClient 多次请求session保持同一个Session
IE、Firefox对同域名访问并发限,及解决优化方案
查询任意一个域名是否是阿里云备案接入 API
多说迁移,Java开发模仿自主实现评论(
2018总结及2019年计划与2019定下一个小目标
最新文章
C语言的变量和常量 30
PostgreSQL:数据库角色 96
NumPy:数组操作例程 74
Python正则表达式详细剖析 146
PHP变量剖析 113
SQL全外连接剖析 298
PHP面向对象编程最详讲解和例子 256
PHP用户定义函数详细讲解 48
SQL交叉连接剖析 136
SQL自然连接剖析 184
最热文章
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 680244
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 674611
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 599966
免费天气API,天气JSON API,不限次数获取十五天的天气预报 568894
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 552088
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 509262
Elasticsearch教程(四) elasticsearch head 插件安装和使用 479677
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 263408
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 244147
Elasticsearch教程(一),全程直播(小白级别) 225263
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...