题记:为了避免枯燥,直接从播放开始,方便了解音视频处理的基本流程和FFmpeg的用法。对应 github.com/honghewang/… 第一个Demo
视频播放基本流程
解协议
解协议一般针对网络数据,对流媒体协议(HTTP,RTMP等)进行解析,获得相应封装数据格式数据。常用协议包括:
- 基于TCP
- RTMP/HTTP-FLV 常用于直播,延迟在2-3秒;
- HLS 用于点播和直播,用于直播实时性较差。包括列表文件(m3u8,相当于目录)和TS文件(视频切片)
- 基于UDP
- WebRTC 用于在线教育、视频会议等,延时小,使用RTP/RTCP等基于UDP的协议;
- 支持TCP和UDP多种传输层协议
- RTSP 常用于监控
解封装
视频流中的数据是音视频交错传输的,解封装是对MP4、TS等格式的数据,进行解析分流操作,获取视频基本信息和音视频流steams。以下是对MP4解封装后调用av_dump_format打印的信息,可以获取到时长、视频流信息(图中stream1,H.264编码数据格式是YUV420P,fps),音频信息(图中stream0,aac及采样率等)
视频流主要包括(还有attachment和普通数据 data):
- 视频流 编码的图片信息,一般包括H.264、H.265系列,VP8、VP9系列等格式
- 音频流 编码的音频信息,一般包括AAC、OGG等格式
- 字幕流 SRT,ASS等格式
解码
编解码是视频开发最重要的一环,不经过编码的原始数据都很大,不利于存储和网络传输。音视频原始数据大小计算
- 视频:视频大小(字节)= 分辨率宽度(像素)× 分辨率高度(像素)× 色彩深度(位/像素)× 帧率(帧/秒)× 时长(秒)
- 深度:RGB是3个字节,RGBA是4个字节
- 帧率(FPS):视频每秒显示的帧数,24帧以上
- 音频:音频大小(字节)= 采样频率 × 深度 × 声道数 × 时长
- 采样频率:常见44.1kHz、48kHz、96kHz
- 深度:位数/8,声音量化时的粒度
假设有一个视频,其分辨率为1920x1080像素,色彩深度为24位,帧率为25帧/秒,时长为60秒(即1分钟)。那么该视频的未压缩仅图像大小1920×1080×3×25×60≈9GB。而编码后则可以降低为原来的几十分之一,其中基本技术通常包括:
- 空间冗余:相邻像素之间有较强的相关性,帧内压缩技术
- 时间冗余:相邻图像之间内容变化不大,帧间压缩技术,I帧P帧B帧的由来
- 编码冗余:不同像素值出现的概率不同,如变长编码技术
- 视觉冗余:视觉系统对某些细节不敏感,如有损压缩技术
- 知识冗余:根据已有知识进行优化,如智能编码技术
解码解析
有了上述的基本认识,就可以想办法让一个视频动起来了。
FFmpeg接入
iOS端编译脚本项目 github.com/kewlbear/FF… 选择合适版本,编译后导入iOS项目即可。需要修改build-ffmpeg.sh(长久未更新):
- FFmpeg下载地址www.ffmpeg.org/releases/$S… 改成https
- FF_VERSION版本号,修改为需要版本
- DEPLOYMENT_TARGET iOS最小支持版本,FFmpeg5.0以上需要iOS13.0以上
- ARCHS去掉armv7 i386
Demo中使用FFmpeg版本为5.0,系统11.7.10,Xcode版本13.2.1,在模拟器上运行报错:
根据记录github.com/kewlbear/FF… 可能是系统问题,添加“--disable-asm”可以在模拟器上运行(应该有些影响,读者可以自己选择版本编译试试)。别忘了添加必要依赖项
FFmpeg处理流程
FFmpeg处理与播放流程对应,主要包括几个模块
- AVFormatContext 解封装的上下文,包含视频信息,贯穿播放的整个过程
- AVInputFormat 封装格式对应的结构体
- AVStream 音视频流
- AVCodecContext 解码上下文,如视频流P帧要根据I帧解码,需要保留上下文。对应一个AVCodec
- AVPacket 音视频流中的数据,编码数据
- AVFrame 解码后的数据,每个AVPacket可以包含一个或者多个AVFrame
代码解析
初始化设置
av_register_all()
注册所有可以用的编解码器、协议等。FFmpeg4.0以后废弃。avformat_network_init()
初始化网络模块。av_dict_set()
设置参数,如rtsp_transport的协议int avformat_open_input(AVFormatContext **ps, const char *url, const AVInputFormat *fmt, AVDictionary **options);
创建context,url可以本地文件和网络流av_dump_format
打印信息,如上一章贴图
流初始化
nb_streams
Context的包含流信息,两种方式获取音视频流对应的序号av_find_best_stream
返回流序号- 遍历stream,通过
codecpar->codec_type
识别流信息
avcodec_find_decoder
获取解码器avcodec_alloc_context3
创建解码上下文avcodec_parameters_to_context
用封装的参数信息给codecontext初始化avcodec_open2
打开解码器
解码流程
av_read_frame
从封装文件中读取编码数据,得到AVPacketavcodec_send_packet
发送packet到解码器中avcodec_receive_frame
获取AVFrame数据,packet与frame是一对多的关系,视频一般(1:1),音频(1:n)解码得到AVFrame即可使用的数据,按照一定方式展示便是视频播放。以下用最简单的方式展示
视频展示
本章节介绍AVFrame的使用,Demo的第一部分使用iOS最简单的方式展示,如果不感兴趣可以跳过。
图像展示
- 视频AVFrame的主要参数
- data 打包格式数据,如YUV420P会有3个维度数据
- linesize 每行的字节数,维度与data对应,标识data的排列,注意有字节对齐可能与width不同
- format 图像格式
对于图像帧打印(AVPixelFormat)frame->format
:AV_PIX_FMT_YUV420P,数据存放在frame->data,YUV可以使用OpenGL或其他GPU方式渲染出来,Demo方便展示,转换为RGBA图片
- SwsContext 视频格式转换上下文 使用sws_getCachedContext获取
- srcFormat 参数传为(AVPixelFormat)frame->format
- dstFormat 参数传为AV_PIX_FMT_RGBA
- sws_scale 的功能
- 图像缩放
- 像素格式转换
在iOS平台上,可以imageWithRGBA
转成图片,使用UIImageView展示出来即可,如图。另外可根据fps信息,使用信号量让图像多停留一会。
音频播放
音频
- 音频AVFrame的主要参数
- sample_rate 采样率
- nb_samples 可以理解为每一帧包含的样本数,那音频帧的时长:nb_samples/sample_rate(1024/44100=23ms左右)
- channels 声道 与视频对应,统一化格式
- SwrContext 音频格式转换上下文 swr_alloc_set_opts
- out_sample_fmt 输出采样格式
- in_sample_fmt 输入采样格式
- swr_convert 音频格式转换
- out 输出音频数据缓冲区的指针
- in 输入音频数据缓冲区的指针
音频数据转换成可播放的PCM数据后,在iOS平台可以AudioToolBox播放(后续有C++通用播放)。按照苹果官网可参考Demo中 OPAudioToolBoxPlayer
后续
Demo第一部分仅展示解码流程,实际上音视频需要同步起来,在后续章节介绍。