下载
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.csdn.net/hejjunlin/article/details/62976457
相关文章
基于iOS的网络音视频实时传输系统(三)- VideoToolbox编码音视频数据为H264、AAC
基于iOS的网络音视频实时传输系统(四)- 自定义socket协议(TCP、UDP)
基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264
基于iOS的网络音视频实时传输系统(六)- AudioQueue播放音频,OpenGL渲染显示图像