前言
逐步深入多媒体技术。
前置知识补充
除了前篇必会的
https://xn--74q78i15hxv3arigm4e.cn/2019/04/23/FFmpeg-%E5%9F%BA%E7%A1%80%E7%90%86%E8%AE%BA%E4%B8%8E%E5%85%A5%E9%97%A8/
https://xn--74q78i15hxv3arigm4e.cn/2019/04/05/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E7%8E%A9%E7%8E%A9%E5%A4%9A%E5%AA%92%E4%BD%93%E5%91%A2/
接下来介绍些开发中需要用到的前置
PPM格式
一种简单的图像格式
头:
pn\n表示内容与格式
图像宽度 高度\n
最大像素值\n 0-255
后面直接按pn的要求存图像数据即可
SDL
wiki: https://zh.wikipedia.org/wiki/SDL
SDL(Simple DirectMedia Layer)开源的跨平台多媒体开发库。
其跨平台方式是源码跨平台,分发不同平台的链接库
其目的是精简控制图像声音与输入工具所需要的代码,因此其实现基本为各个平台系统库的再包装。
如win上为directX、linux为Xlib。此外还提供了很多功能函数库(网络等)
openGL
跨语言、平台的专注图形渲染库,图形加速硬件需要厂商提供驱动实现。
directX
linux下的驱动:硬件->驱动实现->注册为设备(字符设备、块设备)->成为文件系统->对外系统调用接口(read\write)
微软系统专为多媒体以及游戏开发的应用程序接口,硬件厂商需要为每款硬件产品写DX驱动。
PTS、DTS
视编码中,并不是每一帧都是完整的画面,处理分为:
I帧:仅利用单帧图像内的空间相关性压缩
P帧:空间与时间都参考,向前时间参考
B帧:空间与时间都参考,双向时间参考,B帧不可作为参考
DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
当没有b帧时,二者通常一致,有b帧时,DTS告诉解码顺序,PTS告诉显示顺序
采集顺序与显示顺序相同。编码顺序、传输顺序和解码顺序相同。
杂
指令架构AMD64、Intel64、x64等等兼容x86的都是AMD64指令集…intel商业混淆了半天起了一堆名字….
FreeBSD也是开源的类unix系统,ps4使用其内核。
音频转码也叫重采样
解复用(demux),表示从一路输入中分离出多路流(视频、音频、字幕等)。
复用(mux),是multiplex的缩写,表示将多路流(视频、音频、字幕等)混入一路输出中(普通文件、流等)。
FFmpeg使用
可直接使用的库
libavcodec 多媒体编解码器库
libavdevice 设备库
libavfilter 滤镜库
libavformat 媒体格式库
libavutil 实用工具
libpostproc 后处理库
libswresample 音频重采样库
libswscale 媒体放缩
FFmpeg可以识别5种流类型:音频(audio, a),视频(video, v),字幕(subtitle, s),附加数据(attachment, t)和普通数据(data, d)。容器可含很多种不同的流。
内存IO与外部IO
所谓内存IO,在FFmpeg中叫作“buffered IO”或“custom IO”,指的是将一块内存缓冲区用作FFmpeg的输入或输出。与内存IO操作对应的是指定URL作为FFmpeg的输入或输出,比如URL可能是普通文件或网络流地址等。
步骤
输出使用类似,只要提供适量缓存就可以将流式结构型文件解析出来。
AVBuffer、AVFrame、AVPacket
|
|
AVBuffer为FFmpeg中使用的缓冲区,使用引用计数管理
AVFrame中存储的是经过解码后的原始数据
AVPacket中存储的是经过编码的压缩数据。
AVFrame:
data是一个指针数组,数组的每一个元素是一个指针,指向视频中图像的各个plane或音频中的各个plane。交织时如YUVYUV,指向这一个plane即可。
对于视频来说,linesize是每行图像的大小(字节数)。注意有对齐要求。
对于音频来说,linesize是每个plane的大小(字节数)。音频只使用linesize[0]。对于planar音频来说,每个plane的大小必须一样。
width, height视频帧宽和高(像素)。
format帧格式
nb_samples音频帧中单个声道中包含的采样点数
pict_type视频帧类型(I、B、P等)
sample_aspect_ratio视频帧的宽高比。
pts显示时间戳。单位是time_base。
pkt_pts此frame对应的packet中的显示时间戳。
pkt_dts此frame对应的packet中的解码时间戳。
coded_picture_number在编码流中当前图像的序号。
display_picture_number在显示序列中当前图像的序号。
sample_rate音频采样率。
channel_layout音频声道布局。每bit代表一个特定的声道
alloc分配、free释放、ref拷贝并计数
AVPacket:
对于视频而言,一个AVPacket通常只包含一个压缩视频帧。而对于音频而言,一个AVPacket可能包含多个完整的音频压缩帧。编码结束后只需要更新一些参数时就可以发空packet。
AVPacket对象可以在栈上分配,注意此处指的是AVPacket对象本身。而AVPacket中包含的数据缓冲区是通过av_malloc()在堆上分配的。
pts,显示时间戳
dts,解码时间戳
duration,当前包解码后的帧播放持续的时长。单位timebase。值等于下一帧pts减当前帧pts。
pos,流中的位置
time_base。pts与dts的基本单位,AVStream中精度高,AVCodecContext中为1/FPS。
AVPacket下的pts和dts以AVStream->time_base为单位
AVFrame里面的pkt_pts和pkt_dts是拷贝自AVPacket,同样以AVStream->time_base为单位;而pts是为输出(显示)准备的,以AVCodecContex->time_base为单位)。
滤镜filter
滤镜filter用于修改未编码的原始音视频数据,多个滤镜可以连接起来
FFmpeg比较常用的滤镜有:scale、trim、overlay、rotate、movie、yadif。scale滤镜用于缩放,trim滤镜用于帧级剪切,overlay滤镜用于视频叠加,rotate滤镜实现旋转,movie滤镜可以加载第三方的视频,yadif滤镜可以去隔行。
编解码
解码使用avcodec_send_packet()和avcodec_receive_frame()两个函数。
编码使用avcodec_send_frame()和avcodec_receive_packet()两个函数。
关于avcodec_send_packet()与avcodec_receive_frame()的使用说明:
- 按dts递增的顺序向解码器送入编码帧packet,解码器按pts递增的顺序输出原始帧frame,实际上解码器不关注输入packet的dts(错值都没关系),它只管依次处理收到的packet,按需缓冲和解码
- avcodec_receive_frame()输出frame时,会根据各种因素设置好frame->best_effort_timestamp(文档明确说明),实测frame->pts也会被设置(通常直接拷贝自对应的packet.pts,文档未明确说明)用户应确保avcodec_send_packet()发送的packet具有正确的pts,编码帧packet与原始帧frame间的对应关系通过pts确定
- avcodec_receive_frame()输出frame时,frame->pkt_dts拷贝自当前avcodec_send_packet()发送的packet中的dts,如果当前packet为NULL(flush packet),解码器进入flush模式,当前及剩余的frame->pkt_dts值总为AV_NOPTS_VALUE。因为解码器中有缓存帧,当前输出的frame并不是由当前输入的packet解码得到的,所以这个frame->pkt_dts没什么实际意义,可以不必关注
- avcodec_send_packet()发送第一个NULL会返回成功,后续的NULL会返回AVERROR_EOF。
- avcodec_send_packet()多次发送NULL并不会导致解码器中缓存的帧丢失,使用avcodec_flush_buffers()可以立即丢掉解码器中缓存帧。因此播放完毕时应avcodec_send_packet(NULL)来取完缓存的帧,而SEEK操作或切换流时应调用avcodec_flush_buffers()来直接丢弃缓存帧。
- 解码器通常的冲洗方法:调用一次avcodec_send_packet(NULL)(返回成功),然后不停调用avcodec_receive_frame()直到其返回AVERROR_EOF,取出所有缓存帧,avcodec_receive_frame()返回AVERROR_\EOF这一次是没有有效数据的,仅仅获取到一个结束标志。
关于avcodec_send_frame()与avcodec_receive_packet()的使用说明:
- 按pts递增的顺序向编码器送入原始帧frame,编码器按dts递增的顺序输出编码帧packet,实际上编码器关注输入frame的pts不关注其dts,它只管依次处理收到的frame,按需缓冲和编码
- avcodec_receive_packet()输出packet时,会设置packet.dts,从0开始,每次输出的packet的dts加1,这是视频层的dts,用户写输出前应将其转换为容器层的dts
- avcodec_receive_packet()输出packet时,packet.pts拷贝自对应的frame.pts,这是视频层的pts,用户写输出前应将其转换为容器层的pts
- avcodec_send_frame()发送NULL frame时,编码器进入flush模式
- avcodec_send_frame()发送第一个NULL会返回成功,后续的NULL会返回AVERROR_EOF
- avcodec_send_frame()多次发送NULL并不会导致编码器中缓存的帧丢失,使用avcodec_flush_buffers()可以立即丢掉编码器中缓存帧。因此编码完毕时应使用avcodec_send_frame(NULL)来取完缓存的帧,而SEEK操作或切换流时应调用avcodec_flush_buffers()来直接丢弃缓存帧。
- 编码器通常的冲洗方法:调用一次avcodec_send_frame(NULL)(返回成功),然后不停调用avcodec_receive_packet()直到其返回AVERROR_EOF,取出所有缓存帧,avcodec_receive_packet()返回AVERROR_EOF这一次是没有有效数据的,仅仅获取到一个结束标志。
- 对音频来说,如果AV_CODEC_CAP_VARIABLE_FRAME_SIZE(在AVCodecContext.codec.capabilities变量中,只读)标志有效,表示编码器支持可变尺寸音频帧,送入编码器的音频帧可以包含任意数量的采样点。如果此标志无效,则每一个音频帧的采样点数目(frame->nb_samples)必须等于编码器设定的音频帧尺寸(avctx->frame_size),最后一帧除外,最后一帧音频帧采样点数可以小于avctx->frame_size
流媒体
将媒体数据压缩并封装成为流
FFmpeg中若URL携带“rtmp://”、“rpt://”、“udp://”等前缀,则表示涉及流处理
分别使用对应的协议
实现播放器
VS通用c++配置方式
头文件搜索:项目属性、c++中添加附加路径。若使用脚本集成编译则将库头文件复制到项目中添加或使用相对vcxproj添加
库文件添加:项目属性、连接器中添加附加路径(lib放进项目时没加也能编)、代码&配置中添加链接目标。若使用脚本集成编译环境则将lib复制进项目中一起编译
动态库添加:动态库需要放于应用程序同级便于搜索,也可添加path。编译时加lib用于启动时加载dll。也可全手动加载
基本过程
容器->流*n->包->帧
流程熟悉
简单实现官方教程,但由于接口过时且说的不够详细,所以仅作为流程了解,核心的参考为之后收集的博客实现
官方实现:https://github.com/mpenkov/ffmpeg-tutorial
新版本实现:(基于https://www.cnblogs.com/leisure_chn/p/10284653.html)
https://github.com/imbaya2466/FFmpeg-play