1、Android 平台的音频渲染
在一对一直播系统源码开发中,要实现 Android 平台的音视频渲染,就要了解Android SDK 提供的三套音频播放 API, 分别是 MediaPlayer、SoundPool 和 AudioTrack。
MediaPlayer:适合在后台长时间播放本地音乐文件或在线的流式媒体文件,它属于高层 API,使用比较简单。
SoundPool: 适合播放比较短的音频片段,比如游戏的声音、按键声音、铃声片段,它可以同时播放多个音频。
AudioTrack:适合低延迟的播放,是更底层的 API,提供了非常强大的控制能力,适合流媒体的播放等。由于它比较底层,所以需要配合解码器来使用。
Android NDK 提供了 OpenSL ES 的 C 语言接口,可以提供非常强大的音效处理、低延时播放等功能。
2、 AudioTrack 的使用
由于 AudioTrack 是 Android SDK 层提供的最底层的音频播放 API,因此只允许输入裸数据。和 MediaPlayer 相比,对于一对一直播系统源码中的一个压缩的音频文件(比如 MP3、AAC 等文件),它需要自行实现解码操作和缓冲区控制。
AudioTrack 的流程:
- 设置音频参数,创建 AudioTrack 的实例
- 调用 play 方法,将 AudioTrack 切换到播放状态
- 启动播放线程,循环向 AudioTrack 缓冲区写入音频数据
- 当数据写完或者停止播放时,停止播放线程,释放资源
package com.wuba.kzw.androidmedia.com.wuba.audiotrack;
import android.media.AudioTrack;
public class AudioTrackTest {
private final AudioTrack audioTrack;
private Thread thread;
/**
* 构造器,创建一个 {@link AudioTrack} 实例
*
* @param streamType STREAM_VOCIE_CALL:电话声音 | STREAM_SYSTEM:系统声音 | STREAM_RING:铃声 | STREAM_MUSCI:音乐声 | STREAM_ALARM:警告声 | STREAM_NOTIFICATION:通知声
* @param sampleRateInHz 采样率,即播放的音频每秒钟会有多少次采样,可选用的采样频率列表为:8000、16000、22050、24000、32000、44100、48000等
* @param channelConfig 声道数
* @param audioFormat 采样格式,可选值以常量的形式定义在类AudioFormat中,分别为ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),注意,前者是可以兼容所有Android手机的
* @param buffferSizeInBytes AudioTrack内部的音频缓冲区的大小, 可以通过 AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) 来获取
* @param mode 播放模式,有两种,一种是 MODE_STATIC: 一次性将所有数据写入播放缓存中,简单高效,通常用于铃声播放、系统提醒;另一种是 MODE_STREAM, 按照一定时间不间断地吸入音频数据
*/
public AudioTrackTest(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int buffferSizeInBytes, int mode) {
audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, buffferSizeInBytes, mode);
}
/**
* 将 AudioTrack 切换到播放状态
*/
public void play() {
if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.play();
}
}
/**
* 停止播放,释放资源
*/
public void stop() {
if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.stop();
}
if (thread != null) {
try {
thread.join();
thread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 开启播放线程,往缓冲区写入数据
*/
public void startPlayBuffer() {
thread = new Thread(new PlayerThread(audioTrack));
thread.start();
}
private static class PlayerThread implements Runnable {
private static final int minBufferSize = 1024;
private final AudioTrack audioTrack;
private boolean isStop;
public PlayerThread(AudioTrack audioTrack) {
this.audioTrack = audioTrack;
}
@Override
public void run() {
short[] samples = new short[minBufferSize];
while (!isStop) {
// 解码器解码
// int actualSize = decoder.readSmaples(samples);
// audioTrack.write(samples, 0, actualSize);
}
}
}
}
3、OpenSL ES 的使用
在 Android中,High Level Audio Libs 是音频 Java 层 API 输入输出,属于高级 API,相对来说,OpenSL ES 则是比较低层级的API,属于 C 语言 API。在一对一直播系统源码开发中,一般会使用高级 API,除非遇到性能瓶颈,开发者可以直接通过 C/C++ 开发基于 OpenSL ES 音频的应用。
需要使用到 JNI 来编写:
package com.wuba.kzw.androidmedia.opensles;
public class OpenSLESTest {
// 导入 OpenSL ES 的头文件
// #include <SLES/OpenSLES.h>
// #include <SLES/OpenSLES_Android.h>
//
//
// mk: 加入链接选项,以便在链接阶段使用到系统的 OpenSL ES 的 so 库
// LOCAL_LDLIBS += -lOpenSLES
/**
* 1)创建一个引擎对象接口。引擎对象是 OpenSL ES 提供 API 的唯一入口,开发者需要调用全局函数 slCreateEngine 来获取 SLObjectItf 类型的引擎对象接口:
* SLObjectItf engineObject;
* SLEngineOption engineOptions[] = { { (SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE }};
* slCreateEngine(&engineObject, ARRAY_LEN(engineOptions), engineOptions, 0, 0, 0);
*/
/**
* 2)实例化引擎对象,需要通过在第1步得到的引擎对象接口来实例化引擎对象,否则会无法使用这个对象,其实在 OpenSL ES 的使用中,
* 任何对象都需要使用接口来进行实例化,所以这里也需要封装出一个实例化对象的方法,代码如下:
* RealizeObject(engineObject);
* SLresult RealizeObject(SLObjectItf object) {
* return (*object)->Realize(object, SL_BOOLEAN_FALSE);
* };
*/
/**
* 3)获取这个引擎对象的方法接口,通过 GetInterface 方法,使用第2步已经实例化好了的对象,获取对应的 SLEngineItf 类型的对象接口,
* 该接口将会是开发者使用所有其他 API 的入口:
*
* SLEngineItf engineEngine;
* (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
*/
/**
* 4)创建需要的对象接口,通过调用 SLEngineItf 类型的对象接口的 CreateXXX 方法返回新的对象的接口,
* 比如,调用 CreateOutputMix 方法来获取一个 outputMixObject 接口,或者调用 CreateAudioPlayer 方法来获取一个 audioPlayerObject 接口。
* 仅列出创建 outputMixObject 的接口代码,播放器接口的获取可以参考代码仓库中的代码:
* SLObjectItf outputMixObject;
* (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
*/
/**
* 5)实例化新的对象,任何对象接口获取出来之后,都必须要实例化,与第2步操作其实是一样的:
* realizeObject(outputMixObject);
* realizeObject(audioPlayerObject);
*/
/**
* 6)对于某些比较复杂的对象,需要获取新的接口来访问对象的状态或者维护对象的状态,比如在播放器 AudioPlayer
* 或录音器 AudioRecorder 中注册一些回调方法等,代码如下:
* SLPlayItf audioPlayerPlay;
* (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_PLAY, &audioPlayerPlay);
* // 设置播放状态
* (*audioPlayerPlay)->SetPlayState(audioPlayerPlay, SL_PLAYSTATE_PLAYING);
* // 设置暂停状态
* (*audioPlayerPlay)->SetPlayState(audioPlayerPlay, SL_PLAYSTATE_PAUSED);
*/
/**
*
* 7)待使用完该对象之后,要记得调用Destroy方法来销毁对象以及相关的资源:
* destroyObject(audioPlayerObject);
* destroyObject(outputMixObject);
* void AudioOutput::destroyObject(SLObjectItf& object) {
* //...
* }
*/
}
4、视频渲染 OpenGL ES 的介绍和实践
OpenGL: 跨语言、跨平台专业的图形程序接口,用于二维或三维的处理和渲染
OpenGL ES: OpenGL 的子集,为嵌入式设备使用,OpenGL ES 最新版本 3.0,使用最广泛的还是 2.0
GLSL: 说到 OpenGL ES 2.0 就不得不提到 GLSL,GLSL 是 OpenGL 的着色器语言,开发人员利用这种语言编写程序运行在 GPU 上以进行图形的处理或渲染。GLSL 着色器代码分为两个部分:Vertex Shader(顶点着色器) 与 Fragment Shader (片元着色器),分别完成各自在 OpenGL 渲染管线中的功能。
对于 OpenGL ES,业界有一个非常著名的开源库 GPUImage,包括摄像头采集、实时渲染、视频播放器、离线保存等,更有强大的滤镜实现。在 GPUImage 的滤镜实现中,可以找到大部分图形图像处理 Shader 的实现,包括:亮度、对比度、饱和度、色调曲线、白平衡、灰度等调整颜色的处理,以及锐化、高斯模糊等图像像素处理的实现等,还有素描、卡通效果、浮雕效果等视觉效果的实现,最后还有各种混合模式的实现等。当然,除了GPUImage 提供的这些图像处理的 Shader 之外,开发者也可以自己实现一些有意思的 Shader,比如美颜滤镜效果、瘦脸效果以及粒子效果等。
OpenGL 渲染管线
学习理解着色器的工作机制,就要对 OpenGL 固定的渲染管线有深入的了解:
几何图元:包括点、直线、三角形,均是通过顶点(vertex)来指定的
模型:根据几何图元创建的物体
渲染:计算机根据模型创建图像的过程
最终一对一直播系统源码的渲染过程结束后,人眼看到的图像就是由屏幕上的所有像素点组成中,在内存中,这些像素点可以组织成一个大的一维数组,每4个 Byte 即表示一个像素点的 RGBA 数据,而在显卡中,这些像素点可以组织成帧缓冲区(FrameBuffer)的形式,帧缓冲区保存了图形硬件为了控制屏幕上所有像素的颜色和强度所需要的全部信息。理解了帧缓冲区的概念,接下来就来讨论一下OpenGL 的渲染管线,这部分内容对于 OpenGL 来说是非常重要的。
以上就是一对一直播系统源码开发- Android 平台的音视频渲染的全部内容了。