iOS h264 硬解

47 篇文章 0 订阅
18 篇文章 0 订阅
记录。


http://www.voidcn.com/blog/dongtinghong/article/p-5047279.html
首先要把 VideoToolbox.framework 添加到工程里,并且包含以下头文件。 
#include <VideoToolbox/VideoToolbox.h>


解码主要需要以下三个函数
VTDecompressionSessionCreate 创建解码 session
VTDecompressionSessionDecodeFrame 解码一个frame
VTDecompressionSessionInvalidate 销毁解码 session


首先要创建 decode session,方法如下:
OSStatus status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              decoderFormatDescription,
                                              NULL, attrs,
                                              &callBackRecord,
                                              &deocderSession);


其中 decoderFormatDescription 是 CMVideoFormatDescriptionRef 类型的视频格式描述,这个需要用H.264的 sps 和 pps数据来创建,调用以下函数创建 decoderFormatDescription
CMVideoFormatDescriptionCreateFromH264ParameterSets
需要注意的是,这里用的 sps和pps数据是不包含“00 00 00 01”的start code的。


attr是传递给decode session的属性词典
CFDictionaryRef attrs = NULL;
        const void *keys[] = { kCVPixelBufferPixelFormatTypeKey };
// kCVPixelFormatType_420YpCbCr8Planar is YUV420
// kCVPixelFormatType_420YpCbCr8BiPlanarFullRange is NV12
        uint32_t v = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
        const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &v) };
        attrs = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
其中重要的属性就一个,kCVPixelBufferPixelFormatTypeKey,指定解码后的图像格式,必须指定成NV12,苹果的硬解码器只支持NV12。


callBackRecord 是用来指定回调函数的,解码器支持异步模式,解码后会调用这里的回调函数。 


如果 decoderSession创建成功就可以开始解码了。 
VTDecodeFrameFlags flags = 0;
            //kVTDecodeFrame_EnableTemporalProcessing | kVTDecodeFrame_EnableAsynchronousDecompression;
            VTDecodeInfoFlags flagOut = 0;
            CVPixelBufferRef outputPixelBuffer = NULL;
            OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(deocderSession,
                                                                      sampleBuffer,
                                                                      flags,
                                                                      &outputPixelBuffer,
                                                                      &flagOut);
其中 flags 用0 表示使用同步解码,这样比较简单。 
其中 sampleBuffer是输入的H.264视频数据,每次输入一个frame。 
先用CMBlockBufferCreateWithMemoryBlock 从H.264数据创建一个CMBlockBufferRef实例。 
然后用 CMSampleBufferCreateReady创建CMSampleBufferRef实例。 
这里要注意的是,传入的H.264数据需要Mp4风格的,就是开始的四个字节是数据的长度而不是“00 00 00 01”的start code,四个字节的长度是big-endian的。 
一般来说从 视频里读出的数据都是 “00 00 00 01”开头的,这里需要自己转换下。 


解码成功之后,outputPixelBuffer里就是一帧 NV12格式的YUV图像了。 
如果想获取YUV的数据可以通过 
CVPixelBufferLockBaseAddress(outputPixelBuffer, 0);
    void *baseAddress = CVPixelBufferGetBaseAddress(outputPixelBuffer);
获得图像数据的指针,需要说明baseAddress并不是指向YUV数据,而是指向一个CVPlanarPixelBufferInfo_YCbCrBiPlanar结构体,结构体里记录了两个plane的offset和pitch。


但是如果想把视频播放出来是不需要去读取YUV数据的,因为CVPixelBufferRef是可以直接转换成OpenGL的Texture或者UIImage的。
调用CVOpenGLESTextureCacheCreateTextureFromImage,可以直接创建OpenGL Texture


从 CVPixelBufferRef 创建 UIImage
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    UIImage *uiImage = [UIImage imageWithCIImage:ciImage];


解码完成后销毁 decoder session
VTDecompressionSessionInvalidate(deocderSession)


硬解码的基本流程就是这样了,如果需要成功解码播放视频还需要一些H.264视频格式,YUV图像格式,OpenGL等基础知识。










代码https://github.com/stevenyao/iOSHardwareDecoder/blob/master/H264DecodeDemo/ViewController.m


 #import "ViewController.h"
#import "VideoFileParser.h"
#import "AAPLEAGLLayer.h"
#import <VideoToolbox/VideoToolbox.h>


@interface ViewController ()
{
    uint8_t *_sps;
    NSInteger _spsSize;
    uint8_t *_pps;
    NSInteger _ppsSize;
    VTDecompressionSessionRef _deocderSession;
    CMVideoFormatDescriptionRef _decoderFormatDescription;
    AAPLEAGLLayer *_glLayer;
}

@end



static void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){

    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);

}

@implementation ViewController

-(BOOL)initH264Decoder {

    if(_deocderSession) {
        return YES;
    }

    const uint8_t* const parameterSetPointers[2] = { _sps, _pps };
    const size_t parameterSetSizes[2] = { _spsSize, _ppsSize };
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                          2, //param count
                                                                          parameterSetPointers,
                                                                          parameterSetSizes,
                                                                          4, //nal start code size
                                                                          &_decoderFormatDescription);


  
    if(status == noErr) {
        CFDictionaryRef attrs = NULL;
        const void *keys[] = { kCVPixelBufferPixelFormatTypeKey };
        //      kCVPixelFormatType_420YpCbCr8Planar is YUV420
        //      kCVPixelFormatType_420YpCbCr8BiPlanarFullRange is NV12
        uint32_t v = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
        const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &v) };
        attrs = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        VTDecompressionOutputCallbackRecord callBackRecord;
        callBackRecord.decompressionOutputCallback = didDecompress;
        callBackRecord.decompressionOutputRefCon = NULL;
        status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              _decoderFormatDescription,
                                              NULL, attrs,
                                              &callBackRecord,
                                              &_deocderSession);
        CFRelease(attrs);

    } else {

        NSLog(@"IOS8VT: reset decoder session failed status=%d", status);

    }

    return YES;

}




-(void)clearH264Deocder {

    if(_deocderSession) {
        VTDecompressionSessionInvalidate(_deocderSession);
        CFRelease(_deocderSession);
        _deocderSession = NULL;

    }

    if(_decoderFormatDescription) {

        CFRelease(_decoderFormatDescription);
        _decoderFormatDescription = NULL;
    }

    free(_sps);
   free(_pps);
    _spsSize = _ppsSize = 0;
}



-(CVPixelBufferRef)decode:(VideoPacket*)vp {

    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;

    OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,

                                                          (void*)vp.buffer, vp.size,
                                                          kCFAllocatorNull,

                                                          NULL, 0, vp.size,

                                                          0, &blockBuffer);

    if(status == kCMBlockBufferNoErr) {

        CMSampleBufferRef sampleBuffer = NULL;


        const size_t sampleSizeArray[] = {vp.size};


        status = CMSampleBufferCreateReady(kCFAllocatorDefault,


                                           blockBuffer,


                                           _decoderFormatDescription ,


                                           1, 0, NULL, 1, sampleSizeArray,


                                           &sampleBuffer);


        if (status == kCMBlockBufferNoErr && sampleBuffer) {


            VTDecodeFrameFlags flags = 0;


            VTDecodeInfoFlags flagOut = 0;


            OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(_deocderSession,


                                                                      sampleBuffer,


                                                                      flags,


                                                                      &outputPixelBuffer,


                                                                      &flagOut);


            


            if(decodeStatus == kVTInvalidSessionErr) {


                NSLog(@"IOS8VT: Invalid session, reset decoder session");


            } else if(decodeStatus == kVTVideoDecoderBadDataErr) {


                NSLog(@"IOS8VT: decode failed status=%d(Bad data)", decodeStatus);


            } else if(decodeStatus != noErr) {


                NSLog(@"IOS8VT: decode failed status=%d", decodeStatus);


            }


            


            CFRelease(sampleBuffer);


        }


        CFRelease(blockBuffer);


    }


    


    return outputPixelBuffer;


}


-(void)decodeFile:(NSString*)fileName fileExt:(NSString*)fileExt {

    NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:fileExt];
    VideoFileParser *parser = [VideoFileParser alloc];
    [parser open:path];
    VideoPacket *vp = nil;
    while(true) {
        vp = [parser nextPacket];
        if(vp == nil) {
            break;
        }
        uint32_t nalSize = (uint32_t)(vp.size - 4);
        uint8_t *pNalSize = (uint8_t*)(&nalSize);
        vp.buffer[0] = *(pNalSize + 3);
        vp.buffer[1] = *(pNalSize + 2);
        vp.buffer[2] = *(pNalSize + 1);
        vp.buffer[3] = *(pNalSize);

        CVPixelBufferRef pixelBuffer = NULL;
        int nalType = vp.buffer[4] & 0x1F;

        switch (nalType) {


            case 0x05:


                NSLog(@"Nal type is IDR frame");


                if([self initH264Decoder]) {


                    pixelBuffer = [self decode:vp];


                }


                break;


            case 0x07:


                NSLog(@"Nal type is SPS");


                _spsSize = vp.size - 4;


                _sps = malloc(_spsSize);


                memcpy(_sps, vp.buffer + 4, _spsSize);


                break;


            case 0x08:


                NSLog(@"Nal type is PPS");


                _ppsSize = vp.size - 4;


                _pps = malloc(_ppsSize);


                memcpy(_pps, vp.buffer + 4, _ppsSize);


                break;

            default:

                NSLog(@"Nal type is B/P frame");

                pixelBuffer = [self decode:vp];

                break;

        }


        if(pixelBuffer) {

            dispatch_sync(dispatch_get_main_queue(), ^{
                _glLayer.pixelBuffer = pixelBuffer;

            });

            CVPixelBufferRelease(pixelBuffer);
        }


        


        NSLog(@"Read Nalu size %ld", vp.size);


    }


    [parser close];


}




-(IBAction)on_playButton_clicked:(id)sender {


    dispatch_async(dispatch_get_global_queue(0, 0), ^{


        [self decodeFile:@"mtv" fileExt:@"h264"];


    });


}



- (void)viewDidLoad {


    [super viewDidLoad];


    // Do any additional setup after loading the view, typically from a nib.

    _glLayer = [[AAPLEAGLLayer alloc] initWithFrame:self.view.bounds];

    [self.view.layer addSublayer:_glLayer];


}



- (void)didReceiveMemoryWarning {


    [super didReceiveMemoryWarning];


    // Dispose of any resources that can be recreated.


}


@end
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用FFmpeg编译h265带硬解是一项比较复杂的过程,需要了解一些有关编译的知识和操作方法。首先需要准备好编译环境,包括相应的工具链和文件。其次需要下载FFmpeg的源代码并进行配置和编译。在配置时需要加入相应的选项,以启用h265的硬件解码功能。具体的操作方法如下: 1.准备编译环境 首先需要安装相应的编译工具和文件,比如GCC、make、x264、yasm等。以Ubuntu系统为例,可以通过以下命令来安装: sudo apt-get install build-essential git-core checkinstall yasm texi2html libvorbis-dev libx11-dev libvpx-dev \ libxext-dev libxfixes-dev zlib1g-dev pkg-config nasm libmp3lame-dev libopus-dev 2.下载并配置FFmpeg源代码 可以从FFmpeg官网或者Github上下载最新版源代码,并解压到本地。然后在命令行中进入FFmpeg源代码所在的目录,执行以下命令进行配置和编译: ./configure --enable-gpl --enable-libx265 --enable-nonfree --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libfreetype --enable-libfontconfig --enable-libass 3.编译并安装 执行完以上命令后,在同级目录会生成一个Makefile文件,可以使用make命令进行编译。编译完成后,可以使用checkinstall命令来将生成的二进制文件打包安装。 sudo checkinstall --pkgname=ffmpeg --pkgversion="$(date +%Y%m%d%H%M)git" --backup=no --deldoc=yes --fstrans=no --default 以上就是使用FFmpeg编译h265带硬解的基本过程,需要注意的是,由于硬解需要依赖具体的硬件设备,因此在不同的平台上可能会存在一些细节上的差异。如果遇到问题,可以参考FFmpeg的官方文档或者社区中的相关讨论进行解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值