概述
直播系统的架构总体上分为采集模块、预览模块、处理模块、编码模块、推流模块。
把这五个模块串联起来就构成了整个直播系统的数据流。如下图所示:
音频采集:采集原始的PCM数据。
音频处理:对音频进行混音消除、降噪、自动增益等处理。
音频编码:把PCM格式的数据编码为AAC格式。
视频采集:相机/屏幕流的采集;YUV格式或者纹理格式。
视频处理:对视频进行美颜/滤镜等处理。
预 览: 把视频处理后的视频流在屏幕上进行渲染显示。
视频编码:把纹理或者YUV格式的原始视频流压缩成H264格式。
推 流:把AAC格式的音频流与H264格式的视频流,通过flv格式包装、推流。
模块设计
为了充分利用CPU的多核、提升推流系统的效率、低延时等,模块的设计采用多线程模型。线程之间的交互通过Blocking队列实现,由于队列是在多线程都需要操作的,需要保证队列的安全性。为了保证队列的安全性,在放入队列之前先上锁,然后操作队列。在操作队列结束之后,发出一个signal指令来告诉block住的线程继续操作队列。添加队列之后的模块图如下图所示:
音频采集
Android 端音频采集常用的有三种方案:AudioRecord、OpenSL、AAudio。
AudioRecord是Android 上层的一个API,通过它可以采集PCM格式的音频数据。
OpenSL是Native层提供的API,它可以采集或播放PCM。
详情链接: https://developer.android.google.cn/ndk/guides/audio/opensl/getting-started
AAudio 是Native层提供的API,Android O 引入的全新的音频API。
详情链接: https://developer.android.google.cn/ndk/guides/audio/aaudio/aaudio
音频的采集放到一个独立线程中执行。把采集之后的音频放到pcm队列中。这里以AudioRecord为例介绍下AudioRecord采集音频的过程:
配置参数(采样率、声道数、采样格式)并获取AudioRecord采集音频缓冲的大小
public static int SAMPLE_RATE_IN_HZ = 44100;
private final static int CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_IN_MONO;
private final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL_CONFIGURATION, AUDIO_FORMAT)
创建AudioRecord
AudioRecord audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE_IN_HZ, CHANNEL_CONFIGURATION, AUDIO_FORMAT, bufferSizeInBytes);
配置好AudioRecord之后需要检查一下AudioRecord的状态,可能因为权限问题或者其他的原因造成AudioRecord创建失败或者状态不对;
if (audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new AudioConfigurationException();
}
开始采集
AudioRecord创建成功之后,可以根据 startRecording() api 开始采集视频。
获取音频数据
获取音频数据需要一个线程,将AudioRecord缓冲区中的音频数据不断的读出来。
int size = audioRecord.read(audioSamplesBuffer, 0, audioSamplesBufferMaxSize);
把音频数据放入PCM队列中
pcm队列的设计后面会模块介绍
停止采集,释放资源
停止音频采集的线程,并调用stop停止采集,调用release释放AudioRecord创建的资源。
音频编码
音频的编码格式有许多像 mp3、aac、wma、ogg、pous、amr等。 aac在低码率场景下,其音频品质非常好,并且在移动平台上无论是单独的音频编码,还是视频中的音频流部分,使用最广泛的都是aac编码。
音频编码器:fdk_aac、MediaCodec
音频编码线程从pcm队列中获取数据编码,编码完成后,把AAC数据放入AAC队列。
这里介绍下软编libfdk_aac编码pcm为aac码流。这里介绍的libfdk_aac是以ffmpeg为基础的,如果后期想用其他编码库,都可以通过ffmpeg的配置实现。
基于ffmpeg的API编码aac,这样做的好处是,只需要编写一份音频编码的代码即可,对于不同的编码器,后期只需要调整相应的编码器ID或者编码器name就可以编码出不同格式的音频文件。既然要使用第三方库libfdk_aac编码aac文件,那么必须首先在ffmpeg交叉编译的时候把libfdk_aac库编译进去。通过调用ffmpeg的API进行音频编码。
音频编码需要在一个线程中实现,它会从pcm队列中获取pcm数据。当队列为空时,wait当前线程。当队列有数据进入signal唤醒当前线程。获取到pcm音频数据后就开始编码,编码完成后把编码后的aac数据入aac 队列。
OpenGL上下文环境搭建
背景
OpenGL不负责窗口管理及上下文环境管理,OpenGL窗口管理及上下文环境管理将由各个平台或者设备自行完成。为了在openGL的输出与设备的屏幕之间架起一个桥梁,需要EGL。EGL是双缓冲的工作模式,即有一个Back Frame Buffer 和一个 Front Frame Buffer,正常绘制操作的目标都是Back Frame Buffer,操作完毕之后,调用eglSwapBuffer这个api,将绘制完毕的Back Frame Buffer 交换到 Front Frame Buffer 并显示出来。在Android平台,使用的是EGL这一套机制,EGL承担了为OpenGL提供上下文环境以及窗口管理的职责。
Android上下文环境
要在Android平台使用opengl es, 第一种方式是直接使用GLSurfaceView,通过这种方式使用OpenGL ES比较简单,因为不需要开发者搭建OpenGL ES的上下文环境,以及创建OpenGL ES的显示设备。但凡事都有两面性,有好处也有坏处,使用GLSurfaceView不够灵活,很多OpenGL ES的核心用法,比如共享上上下文实现多个线程共同操作一个纹理不能使用。所以这里需要通过EGL的api搭建OpenGL的上下文环境,并且基于C++的环境搭建。因为如果用Java层搭建,那么对于普通的应用也许可行,但对于视频应用,出于效率和性能的考虑,这里需要通过C++语言EGL api搭建一个OpenGL的上下文环境。
任何一条opengl指令都必须在自己的OpenGL上下文环境中运行,所以EGL构建出opengl上下文环境之后,就可以执行OpenGL ES了。
OpenGL处理模块需要在一个线程中实现,该线程功能:
- EGL环境的管理
- 渲染窗口的管理
- 纹理的创建
- 对视频流的处理
视频采集与预览
Camera的参数配置这里不再进行介绍,这里只介绍 Camera 是如何与 OpenGL结合实现预览的。
SurfaceView的转化
首先UI层构造一个SurfaceView通过SurfaceView获取Surface,然后在NDK把Surface转化为ANativeWindow,在EGL中通过ANativeWindow创建EGLSurface。
外部纹理的创建
通过OpenGL创建一个外部纹理
把外部纹理设置给Camera
通过2创建的外部纹理ID,创建一个SurfaceTexture,并设置一个监听器SurfaceTexture.setOnFrameAvailableListener();每次视频流渲染到SurfaceTexture上时,都会回调该方法。把SurfaceTexture通过setPreviewTexture api设置给Camera。
OpenGL操作外部纹理实现预览
Camera/Camera2采集相机流把采集的相机流渲染到一个外部纹理中,然后通过OpenGL 把外部纹理渲染到窗口,实现预览。
视频采集与预览的流程图如下:
视频编码
MediaCodec的配置及使用不再介绍,这里只介绍OpenGL ES如何与MediaCodec交互的。
MediaCodec的输入视频流是通过OpenGL模块处理的,通过createInputSurface() api获取一个Surface,最终该Surface创建一个EGL Surface。
OpenGL通过把外部纹理渲染到MediaCodec inputsurface上,通知MediaCodec获取编码之后的数据,获取编码之后的数据后通过处理入h264队列。
MediaCodec与OpenGL操作图:
推流:
推流模块根据具体的顺序分别从aac队列/h264队列获取数据,处理,然后封装成flv 格式,推流。
推流模块通过FFmpeg中集成的rtmp协议实现推流。这样我们就可以通过ffmpeg的写文件的API实现推流。
这样做的好处是,只需要编写一份推流模块的代码即可,当我们想把音视频流写到本地时,推流模块的代码不需要修改,只需要修改我们的传给推流模块的path就可以。比如:mnt/sdcard/0/test.mp4,就可以把视频流以mp4格式保存到本地。
今天的文章Android端直播SDK实现方案分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/26534.html