首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

音视频基础能力之 iOS 视频篇(三):视频硬件解码

  • 24-12-16 12:05
  • 4341
  • 14136
juejin.cn

涉及硬件的音视频能力,比如采集、渲染、硬件编码、硬件解码,通常是与客户端操作系统强相关的,就算是跨平台的多媒体框架也必须使用平台原生语言的模块来支持这些功能

本系列文章将详细讲述移动端音视频的采集、渲染、硬件编码、硬件解码这些涉及硬件的能力该如何实现

本文为该系列文章的第 3 篇,将详细讲述在 iOS 平台下如何实现视频的硬件解码

往期精彩内容,可参考

音视频基础能力之 iOS 视频篇(一):视频采集

音视频基础能力之 iOS 视频篇(二):视频硬件编码

前言

视频解码是视频编码的逆过程,就是将压缩后的图像数据还原成原始未压缩的图像数据,可用于图像处理或渲染到屏幕。有关原始图像数据渲染到屏幕的内容,本系列后续文章中会详细介绍,敬请期待

在 iOS 平台,Apple 提供的硬件解码功能,目前仅支持 H.264 和 H.265,本文也将介绍这 2 种格式的硬件解码该如何实现。在阅读本文之前,建议预先了解下 H.264 和 H.265 的码流结构这些原理性的内容,方便后续更好的理解本文内容

整体流程

本文所介绍的解码流程,如下图所示

1.jpg

数据变化的流程,如下图所示

2.jpg

系统框架

用到了 VideoToolbox,引入头文件

arduino
代码解读
复制代码
#import

关键类型

VTDecompressionSessionRef

CMVideoFormatDescriptionRef

CMSampleBufferRef

创建视频格式

视频格式的类型为 CMVideoFormatDescriptionRef

  • 对于 H.264,解码器的初始化需要 SPS 和 PPS 信息,顺序是 SPS、PPS
ini
代码解读
复制代码
CMVideoFormatDescriptionRef video_format; uint8_t* sps_data; uint8_t* pps_data; size_t sps_data_length; size_t pps_data_length; const uint8_t* param_set_pointers[2] = {sps_data, pps_data}; size_t param_set_sizes[2] = {sps_data_length, pps_data_length}; int nalu_header_length = 4; OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, param_set_pointers, param_set_sizes, nalu_header_length, &video_format); if (status != noErr) { // error logic }
  • H.265 则在 H.264 的基础上,额外需要 VPS 信息,顺序是 VPS、SPS、PPS
ini
代码解读
复制代码
uint8_t* vps_data; uint8_t* sps_data; uint8_t* pps_data; size_t vps_data_length; size_t sps_data_length; size_t pps_data_length; const uint8_t* param_set_pointers[3] = {vps_data, sps_data, pps_data}; size_t param_set_sizes[3] = {vps_data_length, sps_data_length, pps_data_length}; int nalu_header_length = 4; CMVideoFormatDescriptionRef video_format; OSStatus status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault, 3, param_set_pointers, param_set_sizes, nalu_header_length, NULL, &video_format); if (status != noErr) { // error logic }

初始化解码器

构造 dest_image_buffer_attributes,与编码时类似,是与最终图像有关的一系列参数

  • kCVPixelBufferOpenGLESCompatibilityKey、kCVPixelBufferMetalCompatibilityKey,无脑设置为 true
  • kCVPixelBufferIOSurfacePropertiesKey 要设置成非 NULL 的值,简单来说能够让解码器更高效的与 CVPixelBuffer 中的图像数据进行交互
  • 解码器支持的格式只有 NV12,也就是 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

解码器的类型不需要指定,因为 video_format 中已经存储了关键信息,直接创建即可

ini
代码解读
复制代码
const size_t attributes_size = 4; CFTypeRef keys[attributes_size] = { kCVPixelBufferOpenGLESCompatibilityKey, kCVPixelBufferMetalCompatibilityKey, kCVPixelBufferIOSurfacePropertiesKey, kCVPixelBufferPixelFormatTypeKey }; CFDictionaryRef io_surface_ref = CFDictionaryCreate(kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); OSType pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; CFNumberRef pixel_format_ref = CFNumberCreate(nullptr, kCFNumberLongType, &pixelFormat); CFTypeRef values[attributes_size] = { kCFBooleanTrue, kCFBooleanTrue, io_surface_ref, pixel_format_ref }; CFDictionaryRef destination_image_buffer_attributes = CFDictionaryCreate(kCFAllocatorDefault, keys, values, attributes_size, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // 创建解码器 OSStatus status = VTDecompressionSessionCreate(nullptr, video_format, NULL, destination_image_buffer_attributes, nullptr, &decode_session_); CFRelease(io_surface_ref); CFRelease(pixel_format_ref); CFRelease(destination_image_buffer_attributes);

设置解码器参数

设置实时解码

ini
代码解读
复制代码
OSStatus status = VTSessionSetProperty(decode_session_, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);

处理解码前数据

每帧开始解码之前要处理 NALU 的起始码

  • 一般通过网络传输的视频帧,NALU 开头都是 0x00000001 的起始码,此时需要替换成 4 个字节的 NALU 长度,也就是按照 AVCC/HVCC 格式进行封装,AVCC 对应 H.264,HVCC 对应 H.265
  • 在 iOS 中,要求送进解码器的 NALU 头部 4 个字节按照大端字节序存储,因此要做一次额外的转换
  • 对于关键帧,需要跳过 vps、sps、pps 信息,因为这些信息在创建解码器的时候,已经包含在视频格式里了
  • 处理之前的帧数据 3.jpg
  • 处理之后的帧数据 4.jpg
ini
代码解读
复制代码
uint8_t* nalu_data; uint32_t nalu_length; // 对于关键帧,跳过 vps、sps、pps // 将 Annex-B 格式中的 0x00000001 起始码替换成大端模式的 4 字节 NALU 长度 nalu_length = CFSwapInt32HostToBig(nalu_length); memcpy(nalu_data, &nalu_length, 4);

进行解码

解码之前,需要将 NALU 数据包含开头 4 字节长度,一起封装在 CMSampleBuffer 中,同时需要传入最开始创建的 video_format

ini
代码解读
复制代码
uint8_t* data; uint32_t data_length; CMBlockBufferRef block_buffer = NULL; OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, data, data_length, kCFAllocatorNull, NULL, 0, data_length, 0, &block_buffer); if (status != noErr) { // error logic return; } CMSampleBufferRef sample_buffer = NULL; status = CMSampleBufferCreate(kCFAllocatorDefault, block_buffer, true, nullptr, nullptr, video_format, 1, 0, nullptr, 0, nullptr, &sample_buffer); CFRelease(block_buffer);

进行解码,iOS 9 开始支持用 block 处理解码回调,比起静态函数方便了很多,解码后的数据存储在 CVImageBufferRef 当中,CVImageBufferRef 跟 CVPixelBuffer 是同一个东西,拿到之后就可以做后流程了,不管是图像处理还是渲染,都可以

ini
代码解读
复制代码
VTDecodeFrameFlags decode_flags = kVTDecodeFrame_EnableAsynchronousDecompression; OSStatus status = VTDecompressionSessionDecodeFrameWithOutputHandler(decode_session_, sample_buffer, decode_flags, nullptr, ^(OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration) { if ((status != noErr) || (infoFlags == kVTEncodeInfo_FrameDropped)) { // 当前帧解码出错 return; } // 拿到 CVImageBufferRef 之后,做后续流程 });

释放资源

scss
代码解读
复制代码
VTDecompressionSessionWaitForAsynchronousFrames(decode_session_); VTDecompressionSessionInvalidate(decode_session_); CFRelease(decode_session_);

写在最后

以上就是本文的所有内容了,详细讲述了在 iOS 平台下如何实现视频的硬件解码

本文为音视频基础能力系列文章的第 3 篇

往期精彩内容,可参考

音视频基础能力之 iOS 视频篇(一):视频采集

音视频基础能力之 iOS 视频篇(二):视频硬件编码

后续精彩内容,敬请期待

如果您觉得以上内容对您有所帮助的话,欢迎关注我们运营的公众号声知视界,会定期的推送音视频技术、移动端技术为主轴的科普类、基础知识类、行业资讯类等文章。

注:本文转载自juejin.cn的声知视界的文章"https://juejin.cn/post/7447993920187695130"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

141
iOS
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top