多媒体编程——ios视频图像绘制(2)

在ios视频录像绘制(1)中演示了如何使用CALayer进行主动性的图像绘制。

对于帧率要求不高还可以,但是对于帧率很高的视频播放来说就很捉襟见肘。

一方面因为绘制工作交由主线程完成,主线程还要干别的事,忙不过来。

另一方面绘制的CGContextDrawImage里面有拉伸计算,速度快不起来。


有没有更快的绘制方式呢?当然是有的——OpenGLES

以前就知道OpenGLES可以用来简单的绘制视频图像,但是当时没有转过弯来(OpenGLES里面纹理尺寸必须是2的次数倍,而拉伸一张图片花得时间并不见得比前一种CALayer方式里面CGContextDrawImage所花的时间少,所以一直没有采用)。直到最近才找到了办法使用OpenGLES进行高效率的视频图像绘制方法


之前一直想的是基于OpenGLES,使用2D投影方式,构造一个和屏幕一样大的矩形,然后把纹理1:1贴图上去。最大的花费时间的地方在于拉伸图片以达到2的次数倍。

而实际上有更快的技巧,那就是先构造一个2的次数倍的纹理,比如1024x1024,然后拿到一个图像的buf,比如是540x360的,然后逐行拷贝图像到纹理buf,那么每一行的后1024-540这么多个像素认为是无用的,纹理的后1024-360这么多行 也认为是无用的,将纹理坐标设置1:1映射前面有效部分即可。


原来是这样,这样帧率可以达到30以上。。。


附上代码,头文件形式和前一章的一样,只是实现方式不一样。


//
//  TKVideoPlayer.m
//  FLVPlayer
//
//  Created by administrator on 14-7-11.
//  Copyright (c) 2014年 trustsky. All rights reserved.
//

#import "TKVideoPlayerGL.h"


#import "TKTimer.h"
#import "TKTimer2.h"
#import "TKLock.h"
#import "TKTicker.h"

#include <queue>

#define TKVIDEO_FRAME_CACHE_COUNT 8

/*
 因为OpenGL只支持2的次数倍的尺寸纹理,
 所以解决办法是创建一个符合条件的纹理缓存作为画布,
 再将图像素具画到这个画布上,动态调整纹理坐标即可。
 */

#define TKVIDEO_TEXTURE_WIDTH   1024
#define TKVIDEO_TEXTURE_HEIGHT  1024


/*
 OpenGL 支持的纹理RGBA格式,所以需要使用swscale转换像素格式。
*/

@interface TKVideoPlayerGL ()
{
    //opengles
    GLKView*        _glView  ;
    EAGLContext*    _context ;
    GLuint          _texture ;
    NSData*         _vertexs ;
    NSData*         _txcoods ;
    //
    
    uint8_t*        _txtbuf  ;
    
    UIView*         _view    ;
    float           _frate   ;

    uint16_t        _width   ;
    uint16_t        _height  ;
    
    uint8_t*        _buffer  ;
    uint32_t        _length  ;
    
    TKTimer2*       _timer   ;
    
    bool            _state   ;
    
    TKLock*         _lockEmptyQueue ;
    TKLock*         _lockFilledQueue ;
    
    std::queue<uint8_t*> _fmEmptyQueue ;
    std::queue<uint8_t*> _fmFiledQueue ;
    
    uint8_t*             _fmbuffr[TKVIDEO_FRAME_CACHE_COUNT];
    
    dispatch_semaphore_t _sgEmpty ;
    dispatch_semaphore_t _sgfilled ;
    
}

@end


@implementation TKVideoPlayerGL


- (bool) create:(UIView*)target width:(uint16_t)width height:(uint16_t)height frate:(float)frate;
{
    self->_view    = target ;
    self->_width   = width  ;
    self->_height  = height ;
    self->_frate   = frate  ;
    self->_length  = width * height * 4 ;
    
    self->_sgfilled = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT);
    self->_sgEmpty  = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT);
    
    for(int idx=0; idx<TKVIDEO_FRAME_CACHE_COUNT; idx++)
    {
        _fmbuffr[idx] = (uint8_t*)malloc(_length) ;
        _fmEmptyQueue.push(_fmbuffr[idx]);
        dispatch_semaphore_wait(_sgfilled, DISPATCH_TIME_FOREVER);
    }
    
    self->_lockFilledQueue = [[TKLock alloc] init];
    [self->_lockFilledQueue open];
    
    self->_lockEmptyQueue = [[TKLock alloc] init];
    [self->_lockEmptyQueue open];
    
    [self initOpenGLContext];
    
    return true ;
}




- (bool) destory
{
    [self uninitOpenGLContext];
    
    self->_view.layer.delegate = nil ;
    self->_view = nil ;
    
    self->_buffer = NULL ;
    
    for(int idx=0; idx<TKVIDEO_FRAME_CACHE_COUNT; idx++)
    {
        free(_fmbuffr[idx]) ;
        _fmbuffr[idx] = NULL ;
    }
    
    [self->_lockFilledQueue close];
    [self->_lockFilledQueue release];
    self->_lockFilledQueue = nil ;
    
    [self->_lockEmptyQueue close];
    [self->_lockEmptyQueue release];
    self->_lockEmptyQueue = nil ;
    

    int lastCount = TKVIDEO_FRAME_CACHE_COUNT - _fmEmptyQueue.size() - _fmFiledQueue.size() ;
    for(int idx=0; idx<_fmEmptyQueue.size()+lastCount; idx++)
        dispatch_semaphore_signal(self->_sgfilled);
    for(int idx=0; idx<_fmFiledQueue.size()+lastCount; idx++)
        dispatch_semaphore_signal(self->_sgEmpty);
    
    dispatch_release(self->_sgfilled);
    self->_sgfilled = nil ;
    
    dispatch_release(self->_sgEmpty);
    self->_sgEmpty = nil ;
    
    return true ;
}

- (void)initOpenGLContext
{
    self->_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    
    self->_glView = [[GLKView alloc] initWithFrame: self->_view.bounds];
    self->_glView.context = self->_context ;
    self->_glView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888 ;
    self->_glView.drawableDepthFormat = GLKViewDrawableDepthFormat24 ;
    self->_glView.delegate = self ;
    self->_glView.enableSetNeedsDisplay = false ;
    
    [self->_view addSubview: self->_glView];
    
    [EAGLContext setCurrentContext: self->_context]; //这个函数的位置很重要,后面的指令都作用于当前context。
    

    float vertexArray[] = {
        -1,  1, 0,
        -1, -1, 0,
         1, -1, 0,
        
        -1,  1, 0,
         1, -1, 0,
         1,  1, 0,
    };
    
    float xrate = 1.0f*_width/TKVIDEO_TEXTURE_WIDTH ;
    float yrate = 1.0f*_height/TKVIDEO_TEXTURE_HEIGHT ;
    
    float textureArray[] = {
        0,          0,
        0,      yrate,
        xrate,  yrate,
        
        0,          0,
        xrate,  yrate,
        xrate,      0
    };
    
    uint8_t* bufVertex  = (uint8_t*)malloc(sizeof(vertexArray));
    uint8_t* bufTxtCood = (uint8_t*)malloc(sizeof(textureArray));
    
    memcpy(bufVertex, vertexArray, sizeof(vertexArray));
    memcpy(bufTxtCood, textureArray, sizeof(textureArray));
    
    _vertexs = [[NSData alloc] initWithBytesNoCopy:bufVertex length:sizeof(vertexArray) freeWhenDone:true ] ;
    _txcoods = [[NSData alloc] initWithBytesNoCopy:bufTxtCood length:sizeof(textureArray) freeWhenDone:true ] ;
    
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &_texture);
    glBindTexture(GL_TEXTURE_2D, _texture);
    
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) ;
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) ;
    
    
    CGSize size = self->_glView.frame.size;
    glViewport(0, 0, size.width, size.height); // 设置视口宽度高度。
    
    // {修改投影矩阵
    glMatrixMode(GL_PROJECTION); // 修改投影矩阵
    glLoadIdentity(); // 复位,将投影矩阵归零
    glOrthof(-1, 1, -1, 1, 1.0f, -1.0f);
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();//归零模型视图
    GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);
    glMultMatrixf(viewMatrix.m);
    //}
    
    _txtbuf = (uint8_t*)malloc(TKVIDEO_TEXTURE_WIDTH * TKVIDEO_TEXTURE_HEIGHT * 4) ;

}

- (void)uninitOpenGLContext
{
    free(_txtbuf);

    [EAGLContext setCurrentContext:nil];
    
    [_vertexs release];
    [_txcoods release];
    _vertexs = nil ;
    _txcoods = nil ;

    glDeleteTextures(1, &_texture);
    
    [self->_glView removeFromSuperview];
    [self->_glView release];
    self->_glView = nil ;
    
    [self->_context release];
    self->_context = nil ;
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    [self glTexture];

    glClear(GL_COLOR_BUFFER_BIT);// 清空相关缓存。
    glClearColor(0, 0, 0, 0.0f); // 清空场景为黑色。
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    glBindTexture(GL_TEXTURE_2D, _texture);
    
    glTexCoordPointer(2, GL_FLOAT, 0, [_txcoods bytes]);
    glVertexPointer(3, GL_FLOAT, 0, [_vertexs bytes]);
    
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

- (void)glTexture
{
    if(_state && _buffer)
    {
        uint32_t bytesPerRowA = _width * 4 ;
        uint32_t bytesPerRowB = TKVIDEO_TEXTURE_WIDTH * 4 ;
        for(uint32_t h=0; h<_height; h++)
            memcpy(_txtbuf + h * bytesPerRowB, _buffer + h * bytesPerRowA, bytesPerRowA);
        
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TKVIDEO_TEXTURE_WIDTH, TKVIDEO_TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, _txtbuf);
    }
}

- (bool) update:(uint8_t*)buf len:(uint32_t) len
{
    if(_state)
    {
        dispatch_semaphore_wait(_sgEmpty, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC* 100));
        
        [_lockEmptyQueue lock];
        if(_fmEmptyQueue.size() == 0)
        {
            [_lockEmptyQueue unlock];
            return true;
        }
        uint8_t* cachebuf = _fmEmptyQueue.front();
        _fmEmptyQueue.pop();
        [_lockEmptyQueue unlock];
        
        memcpy(cachebuf, buf, len);
        
        [_lockFilledQueue lock];
        _fmFiledQueue.push(cachebuf);
        [_lockFilledQueue unlock];
        
        dispatch_semaphore_signal(self->_sgfilled);
    }
    return true ;
}

- (void) timer_call
{
    if(_state)
    {
        dispatch_semaphore_wait(self->_sgfilled, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC*100)); //等待100毫秒
        
        [_lockFilledQueue lock];
        if(_fmFiledQueue.size() == 0)
        {
            [_lockFilledQueue unlock];
            return ;
        }
        uint8_t* cachebuf = _fmFiledQueue.front();
        _fmFiledQueue.pop();
        [_lockFilledQueue unlock];
        
        self->_buffer = cachebuf ;

        [self timer_draw];
    }
}

- (bool) timer_draw
{
    if(_state && _buffer)
    {
        [self->_glView display];

        [_lockEmptyQueue lock];
        _fmEmptyQueue.push(self->_buffer);
        [_lockEmptyQueue unlock];
        
        dispatch_semaphore_signal(self->_sgEmpty);
    }
    else
    {
        
    }
    
    return true ;
}


- (bool) clear
{

    return true ;
}

- (bool) start
{
    if(_timer == nil)
    {
        _timer = [[TKTimer2 alloc] init];
        _timer.delay   = 1000/_frate ;
        _timer.objcall = self ;
        _timer.selcall = @selector(timer_call);
        [_timer start];
        _state = true ;
    }
    return true ;
}


- (bool) stop
{
    if(_timer)
    {
        _state = false ;
        [_timer stop];
        [self clear];
    }
    return true ;
}

@end




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值