一.FFMPEG概念
- 概念:FFMPEG是一种音视频推流工具,把RV1126编码的视频,通过FFMPEG推流到流媒体服务器上,让大家都能访问和观看。
- 为什么RV1126 编码的视频码流要利用 FFMPEG 框架推送到流媒体服务器,之前通过终端ffplay.exe 打开编码过的视频不就行了吗 为什么需要利用FFMPEG推流到流媒体服务器? 答:直接使用
ffplay
本地播放仅适用于开发调试阶段,而实际项目中,通过 FFmpeg 将视频流推送到服务器是实现远程访问、多用户支持、协议兼容和性能优化的必要步骤。两者并非替代关系,而是开发与生产环境的不同选择。
二.FFMPEG 重要结构体
- FFMPEG 中有这几个比较重要的结构体,分别是 AVFormatContext、AVOutputFormat、 AVStream、AVCodec、AVCodecContext、AVPacket、AVFrame、AVIOContext 结构体,这几个结构体是贯穿着整个 FFMPEG 核心功能,并且也是我们这个项目中最重要的
几个结构体。
- AVFormatContext 结构体:这个结构体是统领全局的基本结构体,这个结构体最主要作用的是处理封装、解封装等核心功能。(最核心,是整个FFMPEG的核心)
AVInputFormat * iformat:输入数据的封装格式,仅作用于解封装用 avformat_open_input
AVOutputFormat * ofomat:输出数据的封装格式,仅作用于解封装用 avformat_write_header
AVIOContext * pb : I/O 的 上 下 文 , 在 解 封 装 中 由 用 户 在 avformat_open_input 之 前 来 设 置 , 若 封 装 的 时 候 用 户 在avformat_write_header 之前设置
int nb_streams:流的个数,若只有视频流 nb_streams = 1, 若同时有视频流和音频流nb_streams = 2。
AVStream **stream:列出文件中所有流的列表
int64_t start_time:第一帧的位置
int64_t duration:流的持续时间
int64_t bit_rate:流的比特率
int64_t probsize:输入读取的用于确定容器格式的大小
AVDictionary *metadata:元数据
AVDictionary *metadata:元数据
AVCodec * video_codec:视频编解码器
AVCodec * audio_codec:音频编解码器
AVCodec *subtitle_codec:字幕编解码器
AVCodec *data_codec:数据编解码器
- AVOutputFormat 结构体:这个结构体的功能类似于 COM 接口,表示文件容器输出格式,这个结构体的特点是着重于函数的实现 int (*write_header)(struct AVFormatContext *);(根据不同流媒体协议去写入头部,如rtsp,srt,tcp等) int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);(对不同流媒体服务器,写入每一帧AVPacket包) int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush);(对每一个AVPacket进行刷新);这三个最重要,主要用于不同协议的数据包的写入。
const char *name; //描述的名称
const char *long_name;//格式的描述性名称,易于阅读。
enum AVCodecID audio_codec; //默认的音频编解码器
enum AVCodecID video_codec; //默认的视频编解码器
enum AVCodecID subtitle_codec; //默认的字幕编解码器
struct AVOutputFormat *next; //链表 NEXT
int (*write_header)(struct AVFormatContext *); //写入数据头部
int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//写一个数据包。 如果在标志中设置 AVFMT_ALLOW_FLUSH,则 pkt 可以为 NULL。
int (*write_trailer)(struct AVFormatContext *); //写入数据尾部
int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush); //刷新 AVPacket,并写入
int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允许从应用程序向设备发送消息。
int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, AVFrame **frame, unsigned flags);//写一个未编码的 AVFrame。
int (*init)(struct AVFormatContext *);//初始化格式。 可以在此处分配数据,并设置在发送数据包之前需要设置的任何AVFormatContext 或 AVStream 参数。
void (*deinit)(struct AVFormatContext *);//取消初始化格式。
int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//设置任何必要的比特流过滤,并提取全局头部所需的任何额外数据
- AVStream 结构体:包含了每一个视频/音频流信息的结构体 ,包括时间基,帧率等等。
index/id:这个数字是自动生成的,根据 index 可以从 streams 表中找到该流
time_base:流的时间基,这是一个实数,该 AVStream 中流媒体数据的 PTS 和 DTS 都将会以这个时间基准。视频时间基准以帧率为基准,音频以采样率为时间基准
start_time:流的起始时间,以流的时间基准为单位,通常是该流的第一个 PTS
duration:流的总时间,以流的时间基准为单位
need_parsing:对该流的 parsing 过程的控制
nb_frames:流内的帧数目
avg_frame_rate:平均帧率
codec:该流对应的 AVCodecContext 结构,调用 avformat_open_input 生成
parser:指向该流对应的 AVCodecParserContext 结构,调用 av_find_stream_info 生成
- AVCodec 结构体: AVCodec 是 ffmpeg 的音视频编解码,包括像素,帧率等信息,根据AVCodec打开AVCodeContext结构体,真正起作用的还是AVCodeContext,很多AVCodeContext的成员都是AVCodec传过来的
AVCodecID : AVCODECID 编 码 器 的 ID 号 , 这 里 的 编 码 器 ID 包 含 了 视 频 的 编 码 器 ID , 如 : AV_CODEC_ID_H264 、AV_CODEC_ID_H265 等等。音频的编码器 ID:AV_CODEC_ID_AAC、AV_CODEC_ID_MP3 等等。
AVMediaType : 指 明 当 前 编 码 器 的 类 型 , 包 括 : 视 频 (AVMEDIA_TYPE_VIDEO) 、 音 频 (AVMEDIA_TYPE_AUDIO) 、 字 幕(AVMEDIA_TYPE_SUBTITILE)。
AVRotational supported_framerates:支持的帧率,这个参数仅支持视频设置
enum AVPixelFormat * pix_fmts:支持的像素格式,这个参数支持视频设置
int * supported_samplerates:支持的采样率,这个参数支持音频设置
enum AVSampleFormat * sample_fmts:支持的采样格式,这个参数仅支持音频设置
uint64_t * channel_layouts:支持的声道数,这个参数仅支持音频设置
int private_data_size:私有数据的大小
- AVCodecContext 结构体:是 FFMPEG 编解码上下文的结构体,它内部包含了 AVCodec 编解码参数结构体
AVMediaType codec_type:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。
AVCodec * codec:指明相应的编解码器,如 H264/H265 等等
AVCodecID * codec_id:编解码器的 ID,这个在上面有详细的说明
void * priv_data:指向相对应的编解码器
int bit_rate:编码的码流,这里包含了音频码流和视频码流码率的设置
thread_count:编解码时候线程的数量,这个由用户自己设置和 CPU 数量有关。
AVRational time_base:根据该参数可以将 pts 转换为时间
int width, height:每一帧的宽和高
AVRational time_base:根据该参数可以将 pts 转换为时间
int gop_size:一组图片的数量,专门用于视频编码
enum AVPixelFormat pix_fmt:像素格式,编码的时候用户设置
int refs:参考帧的数量
enum AVColorSpace colorspace:YUV 色彩空间类型
enum AVColorSpace color_range:MPEG JPEG YUV 范围
int sample_rate:音频采样率
int channel:声道数(音频)
AVSampleFormat sample_fmt:采样格式
int frame_size:每个音频帧中每个声道的采样数量
int profile:配置类型
int level:级别
- AVPacket:它存的是压缩数据的音视频数据,除了压缩数据后,它还包含了一些重要的参数信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长、流媒体索引等。(AVPacket存储的是压缩完了的原始数据,然后FFMPEG根据AVPacket进行推流)
AVBufferRef * buf:对数据包所在的引用的计数缓冲区引用
int64_t pts:显示时间戳,它是来视频播放的时候用于控制视频显示顺序和控制速度。PTS 是一个相对的时间戳,通常它以毫秒为单位,表示该帧在播放器的哪个时间节点进行显示
int64_t dts:解码时间戳,它是来指视频或者音频在解码时候的时间戳,用于控制解码器的解码顺序和速度。DTS 也是一个相对的时间戳,通常它以毫秒为单位,表示该帧在解码器中哪个时间节点进行解码
uint8_t * data:具体的缓冲区数据,这里的数据可以是视频 H264 数据也可以是音频的 AAC 数据
int size:缓冲区长度,这里就是具体的码流数据的长度
int stream_index:流媒体索引值,假设同时有音频和视频。那 stream_index = 0 等于视频数据,stream_index = 1 等于音频数据
flags:专门用在视频编码码流的标识符,默认都要添加 AV_PKT_FLAG,这指的是每一帧都要添加一个关键帧,否则画面则无法正常解码出来。
AVPacketSideData * side_data:容器可以提供的额外数据包数据,包含多种类型的边信息。
int side_data_element:额外数据包的长度
int64_t duration:AVPacket 的持续时间,它的时间以 AVStream->time_base 为单位作为计算,若未知则为 0.
int64_t pos:字节的位置,这个很少用
- AVFrame: 一般存储音视频的原始数据,若存储视频数据的话则存储 YUV/RGB 等数据;若存储音频数据的话,则会存储PCM 数据
uint8_t * data[AV_NUM_DATA_POINTERS]:原始数据,如果是视频数据则是(YUV,RGB 这些数据;音频数据的话是 PCM)
int linesize[AV_NUM_DATA_POINTERS]:data 中一行数据的大小,要注意:这个值未必就完全等于图像的宽,一般都会大于图像的宽度
width、height:视频帧的宽度和高度
int nb_samples:音频的一个 AVFrame 种可能包含多个音频帧,这个参数就是包含了多少个音频帧
format:解码后的原始数据类型(如:YUV420、YUV422、RGB24 等等)
int key_frame:是否是关键帧
eum AVPictureType pict_type:帧类型(I 帧、P 帧、B 帧)
AVRational sample_aspect_ration:宽高比(16:9、4:3)
int64_t pts:显示时间戳
coded_picture_number:编码帧序号
display_picture_number:显示帧序号
- AVIOContext 结构体 : 管理FFMPEG输入输出的结构体,处理FFMPEG里面IO文件。
unsigned char * buffer:缓存开始位置
int buffer_size:缓存大小
unsigned char * buf_end:缓存结束的位置
void * opaque:URLContext 结构体
三.FFMPEG输出模块初始化
- 输出模块作用:输出模块的最大作用是对音视频推流模块进行初始化让其能够正常工作起来,RV1126 的码流通过 FFMPEG 进 行 推 流 , 输 出 模 块 一 般 由 几 个 步 骤 。 分 别 由 avformat_alloc_output_context2 分 配 AVFormatContext 、avformat_new_stream 初始化 AVStream 结构体、avcodec_find_encoder 找出对应的 codec 编码器、利用 avcodec_alloc_context3分配 AVCodecCotext、设置 AVCodecContext 结构体参数、利用 avcodec_parameters_from_context 把 codec 参数传输到 AVStream里面的参数、avio_open 初始化 FFMPEG 的 IO 结构体、avformat_write_header 初始化 AVFormatContext。
- 模块初始化流程图:
四.FLV格式
1.定义
视频/音频通过压缩过的数据传给FLV流媒体复合流格式,然后放RTMP流媒体服务器上播放。FLV 流媒体格式的特点是封装过后的音视频数据非常小、并且封装的规范相对更加简单,所以 FLV 流媒体格式非常适合网络传输,它支持的播放器协议有:如 RTMP、HTTP-FLV。注意:视频压缩完,音频压缩完都是单流,而FLV只能传输复合流,如果只有视频流,那音频流会自动填充。
2.FLV组成
FLV 流媒体封装格式一般由两个部分组成,一个是 FLV Header、另外一个是 FLV Body。
- FLV Header:标识FLV格式
- FLV Body:由 FLV Tag Header 和 Tag Data 组成
(1).FLV Tag Header:主要描述这个是视频还是音频,他有多大数据
(2).Tag Data:具体一些信息
(3).完整的一个Body:
视频:
从这张图可以看出,VIDEO DATA TAG 是视频的具体信息,这其中包括:STREAMID 视频流 ID、FrameType 视频帧类型(1: avckeyframe 指的是关键帧、2: avc inter frame 指的是普通帧)、CODECID 编码 ID(默认 7:AVC 编码)、AVCPacketType 编码包类型(0: avc sequence hdr、1: NALU 类型)、CompositionTime 构造时间、Data 具体的视频数据
音频:
从这张图可以看出,AUDIO DATA TAG 是视频的具体信息,这其中包括:STREAMID 音频流 ID、SoundFormat 音频类型(10: aac)、SoundRate 音频采样率、SoundSize 音频采样深度、SoundType 音频编码类型、AACPacketType AAC 包的类型、Data 就是具体的音频数据
五.RTMP协议
1.定义
RTMP 协议是实时消息传输协议的缩写(Real Time Messaging Protocol)的缩写。RTMP 协议是基于 TCP 协议开发的消息传输协议,它是 Adobe 公司开发的一种私有传输协议。它主要的用途是音视频推流、实时交互语音和数据交互等功能。
2.RTMP交互流程
RTMP 交互的过程总共可以分为以下几步:握手、建立网络对话、创建网络流、播放。
- 握手:
1). Client 端发送 C0(版本号),C1(随机字符串)到 Server,而 Server 收到 C0 或者 C1 后发送应答 S0,S1
2). 当 Client 收到 Server 端发来的应答数据 S0(版本号),S1(随机字符串)后,则发送 C2 数据到 Server。若此时 Server 同时收到 C0、C1 后,就开始发送 S2 到 Client
3). 当 Client 端和 Server 端分别收到 S2 和 C2 信号后,则代表握手完成。
- 建立网络会话:
1). Client 端发送 Connect 指令到 Server 端,这个指令的用处就是请求和 Server 端建立一个连接。
2). Server 端接收到 Client 端发送的指令后,接着发送窗口协议到 Client 端,并同时连接到应用程序。
3). Server 端发送设置带宽的协议到 Client 端
4). Client 端处理带宽协议后,发送确认窗口大小协议到 Server 端
5). Server 端发送用户控制信息指令“Stream Begin”到 Client 端
6). Server 端发送用户消息指令”_result”通知 Client 端连接状态
- 建立网络流(NetStream):
1). Client 端发送指令”createStream”到 Server 端
2). Server 端接收到”createStream”指令后,则成功创建网络流。S 并同时发送”_result”指令通知 Client 端.
- 播放流(Play):
1). Client 端发送指令”play”到 Server 端。
2). Server 端接收后,Server 端发送 ChunkSize 协议消息到 Client 端
3). Server 端发送控制指令”streambegin”给 Client 端
4). 若 3)步发送成功之后,Server 端会发送”响应状态” NetStream.Play.Start & NetStream.Play.reset 通知客户端“Play”指令成功。
3. RTMP消息协议(传输数据方式)
RTMP 的消息协议一般分为三种,分别是消息(Message)、消息块(Chunk)、消息分块(Msg)
消息就是传给流媒体服务器播放使用,消息块就是弱网时候把消息分为一块一块的传给流媒体,消息分块就是网络更不好时候把消息块再分为一块一块给流媒体播放。
六. FFMPEG 时间戳、时间基、时间转换
1.时间基
时间基(time_base):时间基也称之为时间基准,它代表的是每个刻度是多少秒。比方说:视频帧率是 25FPS,那它的时间刻度是{1,25}。相当于 1s 内划分出 25 个等分,也就是每隔 1/25 秒后显示一帧视频数据。
- 在视频时间基都是以帧率为单位,比方说 25 帧。FFMPEG 就以 AVRational video_timebase = {1, 25}来表示。
- 在音频时间基都是以采样率为单位,比方说音频采样率是 48000HZ。FFMPEG 就以 AVRational audio_timebase = {1, 48000}来表示。
2.时间戳(PTS)
时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。
比方说:
有一把尺子 pts = 25 个刻度,time_base = {1,25} 每一个刻度是 1/25 厘米
所以这把尺子的长度 = pts * time_base = 25* 1/25= 1 厘米
3.时间转换
就是在编码时候是一个时间标准,等到了流媒体播放器是另一个标准,所有需要转换过来。
- 时间转换API:void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
第一个参数:AVPacket 结构体指针
第二个参数:源时间基
第三个参数:目的时间基
上面这个 api 的用法是,把 AVPacket 的时间基 tb_src 转换成时间基 tb_dst。下面我们用 H264 和 AAC 时间基 TS 转换的例子来说明这个转换时间基的用法:
视频 H264 时间基转换成 MPEGTS 时间基:
**DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE
H264 {1,25} flv{1,1000}
pts = 1 pts = 40
pts = 2 av_packet_rescale_ts pts = 80
pts = 3 pts = 120
pts = 4 pts = 160
音频 AAC 时间基转换成 FLV 时间基:
**DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE
AAC {1,48000} FLV{1,1000}
pts =1024 pts ~= 21.3
pts =2048 av_packet_rescale_ts pts ~= 42.6
pts =3072 pts ~= 64
pts =4096 pts ~= 85.3
从上述推导的结果可以看出来,如果使用 av_packet_rescale_ts 的 API 对视频时间基进行转换,实际上是使用 DST_VIDEO_PTS= VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE 去计算推流的视频时间戳。