下载
GitHub:
client 端:https://github.com/AmoAmoAmo/Smart_Device_Client
server端:https://github.com/AmoAmoAmo/Smart_Device_Server
另还写了一份macOS版的server,但是目前还有一些问题,有兴趣的去看看吧:https://github.com/AmoAmoAmo/Server_Mac
AudioQueue播放音频
在上一篇中写了解码H264,不过AAC可以省略解码的步骤
因为AudioQueue函数提供的接口可以直接播放AAC音频,估计解码的操作它内部自己帮我们做了,
AudioQueue的使用主要就是几个函数,还有就是它是偏C的函数,所以ARC管不了,我们自己要注意内存的管理。
具体可以参考下面的123篇文章
当时我是直接在官方的代码上找的示例,然后一试就可以了。下面是引用官方指南的部分信息:
“
用于播放的音频队列
播放音频队列的结构如下所示。
播放过程如下所示:
1、给buffer填充数据,并把buffer放入就绪的buffer queue;
2、应用通知队列开始播放;
3、队列播放第一个填充的buffer;
4、队列返回已经播放完毕的buffer,并开始播放下面一个填充好的buffer;
5、队列调用之前设置的回调函数,填充播放完毕的buffer;
6、回调函数中把buffer填充完毕,并放入buffer queue中。
”
OpenGL渲染显示图像
这里视频渲染使用的是 OpenGL ES。关于这部分,网上的教程还是挺多的。
OpenGL是一个非常庞大而又专业的知识,如果想完全撑握它需要花不少时间。而视频渲染只用到了其中的一小部分知识,
建议参考文末的参考文章
初始化OpenGL ES上下文
// 初始化EAGLContext时指定ES版本号
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!_context || ![EAGLContext setCurrentContext:_context] || ![self loadShaders]) {
return nil;
}
设置帧缓冲区
- (void)setupBuffers
{
glDisable(GL_DEPTH_TEST);
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(ATTRIB_TEXCOORD);
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glGenFramebuffers(1, &_frameBufferHandle); // 创建帧缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferHandle); // 绑定帧缓冲区到渲染管线
glGenRenderbuffers(1, &_colorBufferHandle); // 创建绘制缓冲区
glBindRenderbuffer(GL_RENDERBUFFER, _colorBufferHandle);// 绑定绘制缓冲区到渲染管线
// 为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth); // 获取绘制缓冲区的像素宽度
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight); // ...
// 绑定绘制缓冲区到帧缓冲区
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBufferHandle);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
着色器编译
- (BOOL)loadShaders
{
GLuint vertShader, fragShader;
NSURL *vertShaderURL, *fragShaderURL;
self.program = glCreateProgram();
// 创建并编译顶点着色器。
vertShaderURL = [[NSBundle mainBundle] URLForResource:@"Shader" withExtension:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) {
NSLog(@"Failed to compile vertex shader");
return NO;
}
// 创建和编译帧着色器(fragment shader)。
fragShaderURL = [[NSBundle mainBundle] URLForResource:@"Shader" withExtension:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) {
NSLog(@"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program. 附上顶点着色器
glAttachShader(self.program, vertShader);
// Attach fragment shader to program. 附上帧着色器
glAttachShader(self.program, fragShader);
// Bind attribute locations. This needs to be done prior to linking.
glBindAttribLocation(self.program, ATTRIB_VERTEX, "position");
glBindAttribLocation(self.program, ATTRIB_TEXCOORD, "texCoord");
// Link the program.
if (![self linkProgram:self.program]) {
NSLog(@"Failed to link program: %d", self.program);
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (self.program) {
glDeleteProgram(self.program);
self.program = 0;
}
return NO;
}
// 等等等等,代码省略
......
return YES;
}
传入视频帧,开始绘制
使用像素缓冲区的颜色附件来确定适当的颜色转换矩阵。
CFTypeRef colorAttachments = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, NULL);
_preferredConversion = kColorConversion601FullRange; // YCbCr->RGB
从像素缓冲区创建y和UV纹理
这些纹理将被绘制在帧缓冲Y平面。
glActiveTexture(GL_TEXTURE0);
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
_videoTextureCache,
pixelBuffer,
NULL,
GL_TEXTURE_2D,
GL_LUMINANCE,
frameWidth,
frameHeight,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
0,
&_lumaTexture);
使用着色器
glUseProgram(self.program);
glUniformMatrix3fv(uniforms[UNIFORM_COLOR_CONVERSION_MATRIX], 1, GL_FALSE, _preferredConversion);
设置四个顶点
四顶点数据定义了我们绘制像素缓冲区的二维平面区域。
顶点数据分别用(-1,-1)和(1,1)作为左下角和右上角坐标,覆盖整个屏幕。
GLfloat quadVertexData [] = {
-1 , -1 ,
1, -1,
-1 , 1,
1, 1,
};
// 更新顶点数据
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
glEnableVertexAttribArray(ATTRIB_VERTEX);
参考文章
1. http://www.cnblogs.com/perryxiong/p/3790008.html
2. http://www.jianshu.com/p/279a9e5b36b5
3. https://developer.apple.com/documentation/audiotoolbox
4. http://www.jianshu.com/p/750fde1d8b6a
5. http://blog.youkuaiyun.com/hejjunlin/article/details/62976457
相关文章
基于iOS的网络音视频实时传输系统(三)- VideoToolbox编码音视频数据为H264、AAC
基于iOS的网络音视频实时传输系统(四)- 自定义socket协议(TCP、UDP)
基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264
基于iOS的网络音视频实时传输系统(六)- AudioQueue播放音频,OpenGL渲染显示图像