当接收到音视频的原始数据后,怎么运用iOSAndroid体系API烘托到扬声器(Speaker)屏幕(View)

声响的烘托在iOS上运用AudioUnit(AUGraph),Android运用OpneSL ESAudioTrack

视频烘托, 运用跨渠道的烘托技能,即OpneGL ES将一张图片烘托到屏幕上

iOS AudioUnit

在 iOS 渠道上,一切的音频结构底层都是依据AudioUnit完成的,较高层次的音频结构包含: Media Player, AV Foundation, OpenAL和Audio Toolbox, 这些结构都封装了AudioUnit, 然后供给更高层次的API

音视频开发攻略(4)-移动渠道音视频烘托

下面是 iOS 中运用AudioUnit来完成一个音频播映的功用

  1. 知道Audio Session
// 获取音频会话单例, 其用于办理与获取iOS音频设备。
AVAudioSession *audioSession = [AVAudioSession sharedInstance]
// 依据硬件设备供给的才能来设置类别
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
// 设置I/O的buffer,buffer越小阐明推迟越低
[audioSession setPreferredBufferDuration:0.002 error:&error];
// 设置采样频率,让硬件设备按照设置的采样频率来收集和播映音频
[audioSession setPreferredSampleRate:44100 error:&error];
// 激活AudioSession
[audioSession setActive:YES error:&error];
  1. 构建AudioUnit

在创立并启用音频会话之后,就能够构建AudioUnit了。构建 AudioUnit的时候需求指定类型(Type)、子类型(subtype)以及厂商 (Manufacture)。


// 结构AudioUnit描绘的结构体, 类型为RemoteIO
AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitType_RemoteIO;
ioUnitDescription.componentManfacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
// 结构真实的AudioUnit, 两种办法
1. 裸创立办法
// 依据AudioUnit描绘,找到实践的AudioUnit类型
AudioComponent ioUnitRef = AudioComponentFindNext(NULL, &ioUnitDescription);
AudioUnit ioUnitInstance;
// 依据类型发明AudioUnit方针
AudioComponentInstanceNew(ioUnitRef, &ioUnitInstance);
2. AUGraph创立办法
// 定义AUGraph
AUGraph processingGraph;
NewAuGraph (&processingGraph);
// 按照AudioUnit描绘在AUGraph中增加AUNode
AUNode ioNode;
AUGraphAddNode (processingGraph, &ioUnitDescription, &ioNode);
// 打开AUGraph
AUGraphOpen processingGraph;
// 在AUGraph中的某个Node里取得AudioUnit的引证
AudioUnit ioUnit;
AUGrophNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
  1. AudioUnit的通用参数设置
  • RemoteIO这个AudioUnit是与硬件IO有关的一个Unit,它分为输入端和输出端(I代表Input,O代表Output)

  • 输入端一般代表麦克风,输出端一般代表扬声器(Speaker)或者耳机

  • RemoteIO Unit分为Element0和Element1,其间Element0操控输出端,Element1操控输入端,一同每个Element分为Input Scope和Output Scope

  • 假如需求运用麦克风的声响播映功用,需求把这个Unit的Element0的OuputScope和Speaker进行衔接

  • 假如开发者想要运用麦克风的录音功用,有必要将Unit的Element1的InputScope和麦克风衔接起来

下面是运用扬声器的代码

CSStatus status = noErr;
UInt32 oneFlag = 1;
UInt32 busZero = 0;// Element 0
// 把RemoteIOUnit的Element0的OuputScope衔接到Speaker
status = AudioUnitSetProperty(remoteIOUnit, kAudioOuputUnitProperty_EnableIO,kAudioUnitScope_Output,busZero, &oneFlag, sizeof(oneFlag));
CheckStatus(status, @"Could not connect to Speaker", YES);

下面是怎么启用麦克风的代码

UInt32 busOne = 1;//Element 1
AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, busOne, &oneFlag, sizeof(oneFlag));

设置AudioUnit数据格局, 分为输入和输出两个部分

Unit32 bytePerSample = sizeof(Float32);
AudioStreamBasicDescription asbd;
bzer(&asbd, sizeof(asbd));
asbd.mFormatID = kAudioFormatLinearPCM;//指定音频的编码办法为PCM
asbd.mSampleRate = _sampleRate;//声响的采样率
asbd.mChannelsPerFrame = channesl; // 声道数
asbd.mFramesPerPacket = 1; // 每个packat有几个Frame(帧)
asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; // 声响表明格局的参数
AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &asbd, sizeof(asbd));

AudioUnit的分类

  • Effect Unit: 类型是kAudioUnitType_Effect,首要供给声响特效处理的功用。有下面子类型

  • 均衡作用器: (kAudioUnitSubType_NBandEQ), 频带增强或者削弱能量

  • 紧缩作用器: (kAudioUnitSubType_DynamicsProcessor)

  • 混响作用器: (kAudioUnitSubType_Reverb2), 各种声响叠加到一同,让听感有震撼力

  • Mixer Units: 类型是kAudioUnitType_Mixer,首要供给Mix多路声响的功用。

  • I/O Units: 类型是kAudioUnitType_Output,它的用处就像其分类的名字相同,首要供给的便是I/O的功用。

  • Format Converter Units: 类型是kAudioUnitType_FormatConverter,首要用于供给格局转化的功用。

  • Generator Units: 类型是kAudioUnitType_Generator,在开发中咱们常常运用它来供给播映器的功用。

结构一个AUGraph

AUGraph的办法将声响收集、声响处理以及声响输出的整个进程办理起来。

  1. 直接衔接办法
AUGraphConnectNodeInput(mPlayerGraph, mPlayerNode, 0, mPlayerIONode,0);
  1. 回调的办法,在回调函数中获取音频数据
AURenderCallbackStruct renderProc;
renderProc.inputProc = &inputAvailableCallback;
renderProc.inputProcRefCon = (__bridge void*)self;
AUGraphSetNodeInputCallback)mGraph,ioNode,0,&finalRenderProc);

Android音频烘托

有三种办法

  1. MediaPlayer: 合适后台长期播映本地音乐文件或在线的流媒体文件

  2. SoundPool: 合适播映短的音频片段,如按键声响,铃声

  3. AudioTrack: 合适低推迟的播映,供给了强大操控才能,合适流媒体播映,需求结合解码器来运用

AudioTrack运用

  1. 装备AudioTrack
public AudioTrack(int streamType, int sampeRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
  • streamType: 电话声响, 体系声响, 铃声,音乐声,正告声,告诉声

  • sampeRateInHz: 采样率

// 切换到播映状况
audioTrack.paly();
// 开启播映线程
playerThread = new Thread(new PlayerThread(), "playerThread");
playerThread.start();
class PlayerThread implements Runable {
    private short[] samples;
    public void run() {
        samples = new short[minBufferSize];
        while(!isStop) {
            int actualSize = decoder.readSamples(samples);
            audioTrack.write(samples, actualSize);
        }
    }
}

OpenSL ES

全称为 Open Sound Libarary for Embedded Systems, 免费,跨渠道针对嵌入式设备优化的硬件音频加速API

视频烘托

OpenGL ES 介绍

OpenGL(Open Graphics Library)定义了一个跨编程言语、跨渠道的专业图形程序接口。

可用于二位或三位图像的处理与烘托, 它是一个偶功用强大,调用方便的底层图形库

对于嵌入式设备如手机, 供给了 OpenGL ES

在iOS渠道上运用EAGL供给本地渠道对OpenGL ES的完成。

OpenGL首要是做图形图像处理的库,尤其是在移动设备上进行图形图像处理,它的功用优势更能体现出来。

GLSL(OpenGL Shading Language)是OpenGL的着色器言语。开发者运用这种言语编写程序运行在GPU上以进行图形的处理和烘托, 分为两个部分

  • Vetex Shader(极点着色器)

  • Fragment Shader(片元着色器)

OpenGL ES的实践

  1. OpenGL 烘托管线

相关的术语有:

  • 几许图元: 包含点,直线,三角形,都是经过极点(vertext)来指定的

  • 模型: 依据几许图元发明的物体

  • 烘托: 依据模型发明图形的进程

而在显卡中,这些像素点能够组织成帧缓冲区(FrameBuffer)的办法,帧缓冲区保存了图形硬件为了操控屏幕上一切像素的色彩和强度所需求的悉数信息。

烘托管线分为以下几个阶段。

阶段一 指定几许方针

几许方针,便是几许图元,依据详细履行的指令制作几许图形,OpenGL供给给开发者制作办法glDrawArrays的第一个参数是制作mode, 制作办法有以下几种

  1. GL_POINTS: 以点的办法进行制作,一般用在制作粒子作用的场景中

  2. GL_Lines: 以线的办法制作

  3. GL_TRAINGLE_STRIP: 以三角形的办法进行制作, 一切二位图形的制作都会用到这种办法

阶段二 极点处理

不管几许方针怎么拟定的,一切几许数据都会经过这个阶段,依据模型视图和投影矩阵进行变换来改动极点的方位,依据纹路坐标和纹路矩阵来改动纹路坐标的方位

阶段三 图元拼装

极点处理后,不管是模型的极点,仍是纹路坐标都是现已确认好了,极点会将纹路拼装成图元

阶段四 栅格化操作

图元数据会被分解成更小的单元并对应帧缓冲区的各个像素, 这些单元被称为片元,一个片元可能包含窗口色彩,纹路坐标等属性

阶段五 片元处理

经过纹路坐标取得纹路(texture)中相对应的片元像素值(texel),依据自己的业务处理(如提亮,饱和度调理,高斯模糊等)来变换这个片元的色彩,输出位gl_FragColor,用于表明修改后的像素终究结果

阶段六 帧缓冲操作

帧缓冲区的写入操作,也是烘托管线的最后一步,担任将终究的像素值写到帧缓冲区

在OpenGL ES 2.0 ,供给了可编程的着色器替代了烘托管线的某一阶段

  • Vertex Shader(极点着色器) 替换极点处理阶段

  • Fragment Shader(片元着色器,又称像素着色器)替换片元处理阶段

glFinish和glFlush

提交给OpenGL的绘图指令并不会立刻发送给图形硬件,而是放到帧缓冲区,等候缓冲区满了再发送给图形硬件履行。

因而每次写完绘图代码,需求让其当即完成作用时,开发者都需求在代码后边调用glFlush(异步)或 glFinish(同步)函数。

GLSL 语法和内建函数

OpenGL Shading Language: 为了完成着色器功用而面向开发者供给的一种开发言语

  1. GLSL 修饰符和根本数据类型

语法与C言语非常相似

  • const: 常量

  • attribute: 常常更改的信息,智能在极点着色器中运用

  • uniform: 不常常更改的信息, 用于极点着色器和片元着色器中

  • varying: 修复从极点着色器向片元着色器传递的变量

根本数据类型: int, float,bool

向量类型: 用于传递多个参数

attriibute vec4 position: Vertex Shader中极点便是向量
uniform lowp mat4 colorMatrix; // 矩阵类型(4x4), mat2便是2x2, mat3便是3x3
glUniformMatrix4fv(mColorMatrixLocation, 1, false, mColorMatrix); // 传递矩阵到实践的shader中
uniform sample2D textSampler; // Fragment Shader中运用的纹路类型
// 客户端收到句柄后, 就能够为它绑定一个纹路
texId = glActiveTexture(GL_TEXTURE0); // 激活第一个纹路
glBindTexture(GL_TEXTURE_2D, texId);
glUniformli(mGLUniformTexture, 0);
attribute vec2 texcoord;
varing vec2 v_texcoord;
vodi main (void)
{
    // 计算极点坐标
    v_texcoord = texcoord;
}

创立显卡履行程序

怎么让显卡履行这一组Shader,或者用Shader替换掉OpenGL烘托管线那两个阶段

下图描绘了怎么创立一个显卡的可履行程序,统称为Program

音视频开发攻略(4)-移动渠道音视频烘托

完好的进程如下

// 1. 创立shader的容器方针,回来一个容器的句柄, shaderType有GL_VERTEX_SHADER和GL_FRAGMENT_SHADER两种类型
GLuint glCreateShader(GLenum shaderType);
// 2. 创立这个shader的源代码,便是图最右边的两个Shader Content, 把开发者编写的着色器程序加载到着色器句柄所关联的内存中
void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings)
// 编译该Shader
void glCompileShader(GLuint shader);
// 验证Shader是否编译成功了
void glGetShaderiv(GLuint shader, GLenum pname, GLint*params);
// Vertex Shader 和 Fragment Shader创立后, 下面是怎么经过这两个Shader来创立Program(显卡可履行程序)
1. 创立一个方针,作为程序的容器,函数回来容器的句柄
GLunit glCreateProgram(void);
2. 编译的Shader附加到刚刚创立的程序
void glAttachShader(GLuint program, GLuint shader);
3. 链接程序
void glLinkProgram(GLunint program);

OpenGL上下文环境建立

EGL是双缓冲的作业形式,即有一个Back Frame Buffer和一个Front Frame Buffer。

正常制作的操作方针都是Back Frame Buffer, 操作结束之后,调用eglSwapBuffer这个API,将制作结束的FrameBuffer交换到Front Frame Buffer并显现出来

Android 渠道运用EGL这套机制,EGL承当了为OpenGL供给上下文环境以及窗口办理的责任。

iOS渠道不允许直接烘托到屏幕上,因而要运用EAGL的 renderBuffer来替代。

Android 环境建立

Android渠道运用OpenGL ES, 有两种办法

  1. 直接运用GLSurfaceView, 经过这个办法运用OpenGL ES比较简单,但由于不需求开发者建立上下文环境,以及创立显现设备,可是不行灵活,很多真实的OpenGL 中心用法(比如共享上下文来到达多线程一起操作一份纹路)都不能直接运用

  2. 运用EGL的API来建立OpenGL ES 上下文环境,下面是完好进程

1. 引进.so库
LOCAL_LDLIBS += -lEGL
LOCAL_LDLIBS += -lGLESv2
// EGL库头文件
#include <EGL/egl.h>
#include <EGL?eglext.h>
// OpenGL ES库头文件
#include <GLES2/g12.h>
#include <GLES2/gl2ext.h>
// 获取EGLDisplay(封装体系物理屏幕的数据类型)作为OpenGL ES烘托的方针
if ((display == eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
    return false;
}
// 初始化显现设备
if (!eglInitialize(display, 0, 0)) {
    return false;
}
// 装备色彩格局,像素格局,RGBA的表明以及SurfaceType
const EGLint attribs[] = {EGL_BUFFER_SIZE, 32,
EGL_ALPHA_SIZE,8,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE,8,
EGL_RED_SIZE,8,
EGL_RENDERABLE_TYPE, egl_PENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE};
if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs)) {
    return false;
}
// 创立OpenGL 上下文环境-EGLContext
EGLint attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
if (!(context = eglCreateContext(display, config, NULL, eglContextAttributes))) {
    return false;
}
EGLSurface surface = NULL;
EGLint format;
if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
    return surface;
}
// 将输出烘托到设备的屏幕上的,将EGL和设备的屏幕衔接起来, EGL作为桥的功用, 运用EGLSurface衔接起来
ANativeWindow_setBuffersGeometry(_window, 0, 0, format);
if (!(surface = eglCreateWindowSurface(display, config, _window, 0))) {
    return;
}
#include <android/native_widnow.h>
#include <android/native_window_jni.h>
// ANATiveWindow API 的代码如下所示
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
EGLSurface surface;
EGLint PbufferAttributes[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONe, EGL_NONE};
egelCreatePbufferSurface(display, config, PbufferAttributes))
// 开新线程,履行OpenGL ES烘托操作,绑定显现设备和上下文环境,然后履行RenderLoop循环了
eglMakeCurrent(display, eglSurface, eglSurface, context);
// 毁掉资源, 有必要在这个线程毁掉
eglDestorySurface(display, eglSurface);
eglDestoryContext(display, context);

iOS 环境建立

在iOS渠道上不允许开发者运用OpenGL ES直接烘托屏幕,有必要使 用FrameBuffer与RenderBuffer来进行烘托。

若要运用EAGL,则有必要先创立一个RenderBuffer,然后让OpenGL ES烘托到该RenderBuffer上去。 而该RenderBuffer则需求绑定到一个CAEAGLLayer上面去,这样开发者 最后调用EAGLContext的presentRenderBuffer办法,就能够将烘托结果输出到屏幕上去了。

实践上,在调用这个办法时,EAGL也会履行类似于前面的swapBuffer进程,将OpenGL ES烘托的结果制作到物理屏幕上去(View的Layer)。

进程如下:

  1. 首要编写一个View类,承继自UIView, 然后重写父类UIView的办法layerClass,而且回来CAEAGLLayer类型
+ (Class)layerClass {
    return [CAEAGLLayer class];
}
  1. 然后在改View 的 initWithFrame 办法中,取得layer而且设置参数,包含色彩形式
- (id)initWithFrame:(CGRect)frame {
    CAEAGLLayer *eaglLayer = (CAEAGLLayer*)self.layer;
    NSDictionary *dict = @{@(kEAGLDrawablePropertyRetainedBacking):@(NO), @(KEAGLDrawablePropertyColorFormat):@(KEAGLColorFormatRGB565)}
    [eaglLayer setOpaque:YES];
    [eaglLayer setDrawableProperties:dict];
    return self;
}
// 3. 结构EAGLContext 与RenderBuffer并绑定到Layer上, 一同有必要开辟一个线程中履行如下操作
EAGLContext *_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// 绑定操作
[EAGLContext setCurrentContext:_context];
// 创立帧缓冲区
glGenFramebuffers[1, &_FrameBuffer);// 创立制作缓冲区glGenRenderbuffers(1, &_renderBuffer);// 绑定帧缓冲区到烘托管线glBindFrameBuffer(GL_FRAMEBUFFER, _FrameBuffer);// 绑定制作缓冲区到烘托管线glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);// 为制作缓冲区分配存储区,将CAEAGLLayer的制作存储区作为制作缓冲区[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
// 获取制作缓冲区的像素宽度
glGetRenderBufferParameteriv(GL_RENDER_BUFFER, GL_RENDER_BUFFER_WIDTH, &_backingWidth);
// 获取制作缓冲区的像素高度
glGetRenderBufferParameteriv(GL_RENDER_BUFFER, GL_RENDER_BUFFER_HEIGHT, &_backingHeight);
// 将制作缓冲区绑定到帧缓冲区
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER, _renderbuffer);
// 查看FrameBuffer的status
GLenum status = glCheckFrameBufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// failed to make complete frame buffer object
}

这样就能够将制作的结果显现到屏幕上了

OpenGL ES中的纹路

OpenGL中的纹路能够用来表明图像、相片、视频画面等数据。

下面是怎么加载一张图片作为OpenGL的纹路, 首要要在显卡建立一个纹路方针

// 第一个参数需求几个纹路方针, 第二个存储创立的纹路方针句柄数组
void glGenTextures(GLSizeei n, GLuint *textures);
// 假如只创立一个纹路方针,能够直接传递纹路id
glGetTextures(1, &texId);
// 绑定纹路方针
// glBindTexture(GLTEXTURE_2D, texId);

可是在OpenGL ES的操作进程中有必要告诉OpenGL ES详细操作的是哪一个纹路方针。

一般在视频的烘托与处理的时候运用GL_LINEAR这种过滤办法。

接下来怎么将PNG资料的内容放到该纹路方针上, OpenGL的大部分纹路一般都只承受RGBA类型的数据,所以咱们需求对PNG这种紧缩格局进行解码操作。

glTextImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0 , GL_RGBA, gL_UNSIGNED_BYTE, pixels);

这样讲该RGBA数组表明的像素内容上传到显卡里边texId所代表的纹路方针中去了, 以后只需运用该纹路方针, 其实表明的便是这个PNG图片

书写Vertext Shader,代码如下

static char *common_vertext_shader = "attribute vec4 postion; n attribute verc2 texcoord; n varying vec2 v_texcoord;"

下面是真实的制作操作

// 规则窗口的巨细, screenWidth表明制作区域的宽度, screenHeight表明高度
glViewport(0, 0, screenWidth, screenHeight);
// 运用显卡制作程序
glUseProgram(mGLProgId);
// 设置物体坐标
GLFloat vertices[] = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
glVertexAttribPointer(mGLVertexCorrds, 2, GL_FLOAT, 0,0,vertices};
glEnableVertexAttribArray(mGLVertexCoords);
// 设置纹路坐标
GLFloat texCoords1[] = {0, 0, 1, 0, 0, 1, 1,1};
GLFloat texCoords2[] = {0,1,1,1,0,0,1,0};
glVertexAttribPointer(mGLTextureCorrds, 2, GL_FLOAT, 0,0,texCoords2};
glEnableVertexAttribArray(mGLTextureCoords);
// 指定将要制作的纹路方针而且传递给对应的FragmentShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texId);
glUniformli(mGLUniformTexture, 0);
// 履行制作操作
glDrawArrays(GL_TRANGLE_STRIP, 0, 4);

至此就能够在制作区域(屏幕)制作出最初的PNG图片了