FFMPEG推流器讲解

FFMPEG重要结构体的讲解

FFMPEG中有六个比较重要的结构体,分别是AVFormatContext、AVOutputFormat、 AVStream、AVCodec、AVCodecContext、AVPacket、AVFrame、AVIOContext结构体,这几个结构体是贯穿着整个FFMPEG核心功能。

AVFormatContext

这个结构体是统领全局的基本结构体,这个结构体最主要作用的是处理封装、解封装等核心功能。

 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:元数据

AVCodec * video_codec:视频编解码器

AVCodec * audio_codec:音频编解码器

 AVCodec *subtitle_codec:字幕编解码器

AVCodec *data_codec:数据编解码器

AVOutputFormat 

这个结构体的功能类似于COM接口,表示文件容器输出格式,这个结构体的特点是着重于函数的实现

 

2.2.1 const char *name;  //描述的名称

2.2.2. const char *long_name;//格式的描述性名称,易于阅读。

2.2.3. enum AVCodecID audio_codec; //默认的音频编解码器

2.2.4. enum AVCodecID video_codec; //默认的视频编解码器

2.2.5. enum AVCodecID subtitle_codec; //默认的字幕编解码器

2.2.6. struct AVOutputFormat *next; //链表NEXT

2.2.7. int (*write_header)(struct AVFormatContext *);  //写入数据头部

2.2.8. int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//写一个数据包。 如果在标志中设置AVFMT_ALLOW_FLUSH,则pkt可以为NULL。

2.2.9. int (*write_trailer)(struct AVFormatContext *); //写入数据尾部

2.2.10. int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush); //刷新AVPacket,并写入

2.2.11. int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允许从应用程序向设备发送消息。

2.2.12. int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,   AVFrame **frame, unsigned flags);//写一个未编码的AVFrame。

2.2.13. int (*init)(struct AVFormatContext *);//初始化格式。 可以在此处分配数据,并设置在发送数据包之前需要设置的任何AVFormatContext或AVStream参数。

2.2.14.void (*deinit)(struct AVFormatContext *);//取消初始化格式。

2.2.15. int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//设置任何必要的比特流过滤,并提取全局头部所需的任何额外数据

AVStream

AVStream包含了每一个视频/音频流信息的结构体

2.3.1. index/id:这个数字是自动生成的,根据index可以从streams表中找到该流

2.3.2.time_base:流的时间基,这是一个实数,该AVStream中流媒体数据的PTS和DTS都将会以这个时间基准。视频时间基准以帧率为基准,音频以采样率为时间基准

2.3.3.start_time:流的起始时间,以流的时间基准为单位,通常是该流的第一个PTS

2.3.4.duration:流的总时间,以流的时间基准为单位

2.3.5.need_parsing:对该流的parsing过程的控制

2.3.6.nb_frames:流内的帧数目

2.3.7.avg_frame_rate:平均帧率

2.3.8.codec:该流对应的AVCodecContext结构,调用avformat_open_input生成

2.3.9.parser:指向该流对应的AVCodecParserContext结构,调用av_find_stream_info生成

AVCodec结构体

AVCodec是ffmpeg的音视频编解码,它提供了个钟音视频的编码库和解码库,FFMPEG通过AVCODEC可以将音视频数据编码成对应的数据压缩包。

2.4.1. AVCodecID:AVCODECID编码器的ID号,这里的编码器ID包含了视频的编码器ID,如:AV_CODEC_ID_H264、AV_CODEC_ID_H265等等。音频的编码器ID:AV_CODEC_ID_AAC、AV_CODEC_ID_MP3等等。

2.4.2. AVMediaType:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。

2.4.3. AVRotational supported_framerates:支持的帧率,这个参数仅支持视频设置

2.4.4. enum AVPixelFormat * pix_fmts:支持的像素格式,这个参数支持视频设置

2.4.5. int * supported_samplerates:支持的采样率,这个参数支持音频设置

2.4.6. enum AVSampleFormat * sample_fmts:支持的采样格式,这个参数仅支持音频设置

2.4.7. uint64_t * channel_layouts:支持的声道数,这个参数仅支持音频设置

2.4.8. int private_data_size:私有数据的大小

 

AVCodecContext

AVCodecContext是FFMPEG编解码上下文的结构体,它内部包含了AVCodec编解码参数结构体。除了AVCodec结构体外,还有AVCodecInternal、AVRotational结构体,这包含了AVCodecID、AVMediaType、AVPixelFormat、AVSampleFormat等类型,其中包含视频的分辨率width、height、帧率framerate、码率bitrate等。

 AVMediaType codec_type:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。

AVMediaType具体定义:

enum AVMediaType {

    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA

    AVMEDIA_TYPE_VIDEO,

    AVMEDIA_TYPE_AUDIO,

    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous

    AVMEDIA_TYPE_SUBTITLE,

    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse

    AVMEDIA_TYPE_NB

};

2.5.2. AVMediaType codec_type:指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。

2.5.3. AVCodec * codec:指明相应的编解码器,如H264/H265等等

2.5.4. AVCodecID * codec_id:编解码器的ID,这个在上面有详细的说明

2.5.5. void * priv_data:指向相对应的编解码器

2.5.6. int bit_rate:编码的码流,这里包含了音频码流和视频码流码率的设置

2.5.7.thread_count:编解码时候线程的数量,这个由用户自己设置和CPU数量有关

2.5.8. AVRational time_base:根据该参数可以将pts转换为时间

2.5.9. int width, height:每一帧的宽和高

2.5.10. AVRational time_base:根据该参数可以将pts转换为时间

2.5.11. int gop_size:一组图片的数量,专门用于视频编码

2.5.12. enum AVPixelFormat pix_fmt:像素格式,编码的时候用户设置

2.5.13. int refs:参考帧的数量

2.5.14.enum AVColorSpace colorspace:YUV色彩空间类型

2.5.15.enum AVColorSpace color_range:MPEG JPEG YUV范围

2.5.16.int sample_rate:音频采样率

2.5.17.int channel:声道数(音频)

2.5.18. AVSampleFormat sample_fmt:采样格式

2.5.19.int frame_size:每个音频帧中每个声道的采样数量

2.5.20.int profile:配置类型

2.5.21.int level:级别

AVPacket

AVPacket是FFMPEG中一个非常重要的结构体,它保存了解复用之后,解码之前的数据,它存的是压缩数据的音视频数据。除了压缩数据后,它还包含了一些重要的参数信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长、流媒体索引等。

2.6.1. AVBufferRef * buf:对数据包所在的引用的计数缓冲区引用

2.6.2. int64_t pts:显示时间戳,它是来视频播放的时候用于控制视频显示顺序和控制速度。PTS是一个相对的时间戳,通常它以毫秒为单位,表示该帧在播放器的哪个时间节点进行显示

2.6.3. int64_t dts:解码时间戳,它是来指视频或者音频在解码时候的时间戳,用于控制解码器的解码顺序和速度。DTS也是一个相对的时间戳,通常它以毫秒为单位,表示该帧在解码器中哪个时间节点进行解码

2.6.4. uint8_t * data:具体的缓冲区数据,这里的数据可以是视频H264数据也可以是音频的AAC数据

2.6.5. int size:缓冲区长度,这里就是具体的码流数据的长度

2.6.6.int stream_index:流媒体索引值,假设同时有音频和视频。那stream_index = 0等于视频数据,stream_index = 1等于音频数据

2.6.7.flags:专门用在视频编码码流的标识符,默认都要添加AV_PKT_FLAG,这指的是每一帧都要添加一个关键帧,否则画面则无法正常解码出来。

2.6.8. AVPacketSideData * side_data:容器可以提供的额外数据包数据,包含多种类型的边信息。

2.6.9. int side_data_element:额外数据包的长度

2.6.10. int64_t duration:AVPacket的持续时间,它的时间以AVStream->time_base为单位作为计算,若未知则为0.

2.6.11.int64_t pos:字节的位置,这个很少用

AVFrame

AVFrame的结构体一般存储音视频的原始数据,若存储视频数据的话则存储YUV/RGB等数据;若存储音频数据的话,则会存储PCM数据,此外还包含了一些重要的相关信息。

2.8.1. unsigned char * buffer:缓存开始位置

2.8.2. int buffer_size:缓存大小

2.8.3. unsigned char * buf_end:缓存结束的位置

2.8.4. void * opaque:URLContext结构体,下面我们来看看URLContext结构体的详细

URLContext结构体成员变量:

typedef struct URLContext {

    const AVClass *av_class; ///< information for av_log(). Set by url_open().

    struct URLProtocol *prot;

    int flags;

    int is_streamed;  /**< true if streamed (no seek possible), default = false */

    int max_packet_size;  /**< if non zero, the stream is packetized with this max packet size */

    void *priv_data;

    char *filename; /**< specified URL */

    int is_connected;

    AVIOInterruptCB interrupt_callback;

} URLContext;

2.8.4.1.  AVClass:它是在FFMPEG中具体应用很多,它类似于C++中的虚基类的结构体,用于实现多继承的功能。它定义了一系列的函数指针,这些函数指针可以被子类所覆盖了,从而实现子类对父类的重载。

2.8.4.2.  URLProtocol:每个协议对应一个URLProtocol,这个结构体也不在FFMPEG提供的头文件中。我们具体来看看这个结构体的定义

typedef struct URLProtocol {

    const char *name;

    int (*url_open)(URLContext *h, const char *url, int flags);

    int (*url_read)(URLContext *h, unsigned char *buf, int size);

    int (*url_write)(URLContext *h, const unsigned char *buf, int size);

    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);

    int (*url_close)(URLContext *h);

    struct URLProtocol *next;

    int (*url_read_pause)(URLContext *h, int pause);

    int64_t (*url_read_seek)(URLContext *h, int stream_index,

        int64_t timestamp, int flags);

    int (*url_get_file_handle)(URLContext *h);

    int priv_data_size;

    const AVClass *priv_data_class;

    int flags;

    int (*url_check)(URLContext *h, int mask);

} URLProtocol;

2.8.4.2.1. url_open:打开对应的文件IO的url地址

2.8.4.2.2. url_read:读取对应的文件IO的url地址的数据内容

2.8.4.2.3. url_write:写入对应的文件IO的url地址的数据内容

2.8.4.2.4. url_seek:移动文件指针

2.8.4.2.5. url_close:关闭文件指针

 FFMPEG输出模块初始化

FFMPEG输出配置的框图

分配FFMPEG AVFormatContext输出的上下文结构体指针

第一个传输参数:AVFormatContext结构体指针的指针,是存储音视频封装格式中包含的信息的结构体,所有对文件的封装、编码都是从这个结构体开始。

第二个传输参数:AVOutputFormat的结构体指针,它主要存储复合流信息的常规配置,默认为设置NULL

第三个传输参数:format_name指的是复合流的格式,比方说:flv、ts、mp4等等

第四个传输参数:filename是输出地址,输出地址可以是本地文件(如:xxx.mp4、xxx.ts等等)。也可以是网络流地址(如:rtmp://xxx.xxx.xxx.xxx:1935/live/01)

配置推流器编码参数和AVStream结构体

第一个传输参数:AVFormatContext的结构体指针

第二个传输参数:AVDictionary结构体指针的指针

返回值:AVStream结构体指针

设置对应的推流器编码器参数

第一个传输参数:传递参数AVCodecID

根据编码器ID分配AVCodecContext结构体

第一个参数:传递AVCodec结构体指针

avcodec_find_encoder的主要作用是通过codec_id(编码器id )找到对应的AVCodec结构体。在RV1126推流项目中codec_id我们使用两种,分别是AV_CODEC_ID_H264AV_CODEC_ID_H265并利用avcodec_alloc_context3去创建AVCodecContext上下文。

初始化完AVStream和编码上下文结构体之后,我们就需要对这些参数进行配置。重点:推流编码器参数和RV1126编码器的参数要完全一样,否则可能会出问题.

1920 * 1080编码器和FFMPEG推流器的配置

1280* 720编码器和FFMPEG推流器的配置

AV_CODEC_FLAG_GLOBAL_HEADER发送视频数据的时候都会在关键帧前面添加SPS/PPS,这个标识符在FFMPEG初始化的时候都需要添加。

设置完上述参数之后,拷贝参数到AVStream编解码器

拷贝参数到AVStream,我们封装到open_video自定义函数里面,要先调用avcodec_open2打开编码器,然后再调用avcodec_parameters_from_context把编码器参数传输到AVStream里面

 

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

这个函数的具体作用是,打开编解码器

第一个参数:AVCodecContext结构体指针

第二个参数:AVCodec结构体指针

第三个参数:AVDictionary二级指针

int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);

这个函数的具体作用是,把AVCodecContext的参数拷贝到AVCodecParameters里面。

第一个参数:AVCodecParameters结构体指针

第二个参数:AVCodecContext结构体指针

打开IO文件操作

使用avio_open打开对应的文件,注意这里的文件不仅是指本地的文件也指的是网络流媒体文件,下面是avio_open的定义。

int avio_open(AVIOContext **s, const char *url, int flags);

第一个参数:AVIOContext的结构体指针,它主要是管理数据输入输出的结构体

第二个参数: url地址,这个URL地址既包括本地文件如(xxx.ts、xxx.mp4),也可以是网络流媒体地址,如(rtmp://192.168.22.22:1935/live/01)等

第三个参数:flags标识符

#define AVIO_FLAG_READ  1                                      /**< read-only */

#define AVIO_FLAG_WRITE 2                                      /**< write-only */

#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  /**< read-write pseudo flag */

 

avformat_write_header对头部进行初始化输出模块头部进行初始化

int avformat_write_header(AVFormatContext *s, AVDictionary **options);

第一个参数:传递AVFormatContext结构体指针

第二个参数:传递AVDictionary结构体指针的指针

FLV流数据

FLV流媒体协议是美国Adobe公司推出来的一种流媒体协议。FLV流媒体格式的特点是封装过后的音视频数据非常小、并且封装的规范相对更加简单,所以FLV流媒体格式非常适合网络传输。但是FLV格式是Adobe公司的私有协议,所以它支持的网络传输协议比较有限:如RTMP、HTTP-FLV。

FLV流媒体格式

FLV流媒体封装格式一般由两个部分组成,一个是FLV Header、另外一个是FLV Body。其中,FLV Header长度固定式9个字节,FLV BODY则是由一组组的Previous Tags Size + Tag组成。Previous Tag Size一般在整个Tag的前面,它一般记录前一个Tag的大小。Tag的类型一般分为三种、分别是脚本数据帧类型、视频数据类型、音频数据类型。

FLV Header

FLV BODY

FLV BODY一般由FLV Tag Header和Tag Data组成。如果是视频的TAG DATA,则FLV Tag Header+ Video Tag Data。而如果是音频的TAG DATA,则是FLV Tag Header + Audio Tag Data。但无论是视频的Tag Data还是音频的Tag Data它们的FLV Tag Header都是相同的,下面我们来看看公共部分的FLV Tag Header

 

FLV Script Tag

FLV Script Tag也是由FLV Tag Header + Script Data Tag组成。Script Tag的类型一般被称为MedtaData Tag,它一般会存储一些关于FLV音视频的参数信息,比方说:分辨率(width、height)、duration,通常来说Script Tag Data是第一个出现的Tag,并且有且只有一个。

Script Tag是由两个AMF包组合起来(AMF 包 = 数据类型(看下图) + 数据长度 + 数据)。AMF1的第一个字节表示包类型、默认0x02。第2-3个字节代表的是字符串的长度,默认0X00A。 而后面的字节是具体的字符串(“onMetaData”)用十六进制表示:(6f、6e、4d、65、74、61、44、61、74、61

AMF数据类型

而第二个AMF包,第一个字节是0x08表示数组类型。第2-5个字节表示的是数组元素的个数、而后面的数组则是每个数组的键值对。

AMF2数组对应的键值对

 FLV VIDEO TAG

FLV VIDEO TAG是由两部分组成,FLV Tag HEADER + VIDEO DATA TAG。

FLV AUDIO TAG

FLV AUDIO TAG是由两部分组成,FLV Tag HEADER + VIDEO DATA TAG。

RTMP协议

RTMP协议是实时消息传输协议的缩写(Real Time Messaging Protocol)的缩写。RTMP协议是基于TCP协议开发的消息传输协议,它是Adobe公司开发的一种私有传输协议。它主要的用途是音视频推流、实时交互语音和数据交互等功能。

RTMP的交互流程

RTMP交互的过程总共可以分为以下几步:握手、建立网络对话、创建网络流、播放。握手,RTMP的握手主要是TCP和RTMP协议之间的握手,它是整个交互过程的开始。建立网络对话,当握手成功之后,就要建立起Client和Server端的连同关系。建立网络流,就是当网络会话建立成功后,建立起发送多媒体数据的通道。播放,指的是Client和Server传输音视频数据的过程。下面是一个大体的RTMP 客户端和服务端交互的过程。

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”指令成功。

RTMP消息协议

RTMP的消息协议一般分为三种,分别是消息(Message)、消息块(Chunk)、消息分块(Msg)

 

消息(Messgae)

Message是RTMP传输协议的最基本单元。在Messgae中,不同类型的Messgae含有不同的Message Type Id。在RTMP协议中,一共有十几种Message Type,比方说Message Type ID区间在1-6则代表此消息协议是控制协议。

Message Type ID为8代表的是此消息传输音频数据,若Message Type ID 为9则代表的是此消息传输的是视频数据。Message Type ID区间在15到20则用于发送AMF编码指令(AMF指的是Flash和服务端常见的编码方式),比方说播放、暂停、Client和Server端进行交互等。

说完Message Type Id后,我们来介绍一下RTMP的Message的首部信息(Rtmp Header),Rtmp Header是由三元组组成,包括:timestamp delta、mesage_length、type_id。

timestamp delta:和上一个chunk的时间差,若这个值大于等于16777215,该字段必须等于16777215

timestamp :时间戳,每一个Messgae生成的时间戳。

BodySize:指的是message的长度

Typeid:指的是message 类型

消息块(Chunk)

由于在网络传输中,若网络环境不好的情况下,一个消息单元需要拆分成数据量更小的数据块才能够适应网络的传输。在RTMP协议中规定,每个消息都需要被拆分才能够正常传输。拆分的消息分块(Chunk)由Chunk Header 和 Chunk Data组成。Chunk Header由三部分组成:Chunk Basic Header(块基本头)、Chunk Message Header(块消息头)、Extended TimeStamp(扩展时间戳),

 

Chunk Basic Header这个字段包含流ID(chunk stream id)和分块类型(fmt),流id一般用csid来表示,chunk type决定了Message Header的格式,Chunk Type长度固定为2bit。若Basic Header长度等于1Byte,则cs id取值范围在[0,64]。

而CSID: 0, 1是由协议保存的特殊协议:0代表的是整个Chunk Basic Header要占用两个字节,CSID的取值范围是[64,319]; 1代表的还是占用三个字节,CSID取值范围[64,65599]。

Chunk Basic Header:1 Byte (8 bit)

Chunk Basic Header2 Byte (16bit),csid = 0

上面这个图是chunk basic header长度为2byte的情况,这种chunk header的特点是type所在的位置全部设置成0。而第三个字节用CSID----64表现出来,CSID取值范围是[64,319]。

Chunk Basic Header3 Byte (24bit),csid = 1

上面这个图是chunk basic header长度为3byte的情况,这种情况的chunk header的特点是,第一个字节的位置全部设置为1。而剩余的CSID取值的范围是[64,65599]。

Chunk Message HeaderChunk Message Header一般分成四种类型的消息头,分别是0, 1 , 2 , 3。Message Header主要包含了要发送的具体信息。

Chunk Type(fmt) = 0,占用11bytes(88bit)、

当fmt = 0的时候,整个Message Header占用了11个字节,下面我们来分析每一个数据单元的作用:

TimeStamp(时间戳):表示的是时间戳,占用三个字节。它代表的是解析时候的实时时间戳,它最大的取值范围是0xffffff,当时间戳超过这个最大值的时候,所有字节位设置为1。

Message Length(消息数据的长度):Message Length也和TimeStamp一样占用3个字节,它代表的是实际发送的数据信息,比方说:音频数据长度、视频数据长度。这里要注意的是,Message Length是整个Chunk的总长度,并不是Chunk Data的长度

Message type id(消息类型id)消息类型id占用1个字节,它代表的是实际发送数据的类型id,比方说: 8代表的是音频数据、9代表的是视频数据。

Message stream id(消息流id)消息流id占用4个字节,它代表的是chunk所在的流id。

FMPEG时间戳时间基时间转换

FFMPEG 时间基

时间基(time_base)时间基也称之为时间基准,它代表的是每个刻度是多少秒。比方说:视频帧率是25FPS那它的时间刻度是{1,25}。相当于1s内划分出25个等分,也就是每隔1/25秒后显示一帧视频数据。具体的如下图所示:

时间戳(PTS、DTS) 

首先时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。

比方说:

有一把尺子pts = 25个刻度,time_base = {1,25} 每一个刻度是1/25厘米

所以这把尺子的长度 = pts * time_base = 25* 1/25= 1厘米

PTS全称是Presentation Time Stamp(显示时间戳),它主要的作用是度量解码后的视频帧什么时候显示出来。

视频PTS计算:n为第n帧视频帧,timebase是{1framerate},fpsframerate

pts = n *(( 1 / timebase) / fps):

pts = pts++;

举例子:n = 1, pts = 1

        n = 2, pts = 2

        n =3, pts = 3

音频PTS计算:n为第n帧音频帧,nb_samples指的是采样个数(AAC默认1024),timebase是{1,samplerate},samplerate是采样率

Samplerate = 48000, nb_sample=1024, timebase = {1,48000}

num_pkt = samplerate/nb_samples

pts = n * ( ( 1/ timebase) / num_pkt)

pts = pts+1024

举例子:n = 1, pts = 1024

        n = 2, pts = 2048

        n = 3, pts = 3072

DTS表示的是压缩解码的时间戳,在没有B帧的情况下PTS 等于 DTS。假设编码的里面引入了B帧,则还要计算B帧的时间。

没有B帧:dts = pts

存在B帧:dts = pts + b_time

时间转换的原理

在FFMPEG中由于不同的复合流,时间基是不同的,比方说:flv的时间基time_base= {1,1000},假设一个视频time_base = {1,25},我们需要合成mpegts文件,它就需要把time_base = {1,25}占的格子转换成time_base = {1,1000}占的格子。

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去计算推流的视频时间戳。

同理用av_packet_rescale_ts对音频时间基进行转换,实际上是使用DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE去计算我们真实推流的音频时间戳。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404144.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nginx基础配置

1. https配置 首先在nginx.conf中配置https 2. 重定向 rewrite ^/(.*)$ https://www.sxl1.com/$1 permanent;3. 自动索引 autoindex on;4. 缓存 Nginx expire缓存配置: 缓存可以降低网站带宽&#xff0c;加速用户访问location ~ .*\.(gif|jpg|png)$ {expires 365d;roo…

react最好用的swiper插件和拖动插件 react-tiny-slider react-draggable

react移动端项目&#xff0c;其实有挺多的ui框架的&#xff0c;但是我们公司的项目&#xff0c;都是自己封装的ui库&#xff0c;又不可能为了一个轮播图就去再安装一个ui库 所以找了很多的轮播插件&#xff0c;都是不能满足需求 最后找到了它&#xff0c;react-tiny-slider&…

Windows Microsoft Edge 浏览器 配置【密码】

在浏览 Web 时&#xff0c;Microsoft Edge 可以轻松保存密码。 在桌面或移动设备上的 Edge 浏览器中输入新密码时&#xff0c;Microsoft Edge 会询问你是否要记住用户名和密码。 下次访问该网站时&#xff0c;浏览器将完成帐户信息的填写。 如果使用 Microsoft 帐户登录到 Edg…

redis集合若干记录

无序集合 redis通常使用字典结构保存集合数据&#xff0c;字典健存储集合元素&#xff0c;字典值为空。如果一个集合全为整数&#xff0c;使用字典就有点浪费了&#xff0c;redis使用intset保存。 插入元素到intset中 获取插入元素编码&#xff0c;如果插入元素编码级别高于int…

机器学习 之 sklearn的使用介绍和如何找到API

scikit-learn&#xff08;简称 sklearn&#xff09;是基于python语言的一个第三方机器学习库&#xff0c;它提供了简单而有效的工具来进行数据分析和建模。建立在numpy pandas SciPy和Malpotlib库上&#xff0c;下面是对如何使用 sklearn 以及如何找到其 API 的一个基本介绍&am…

仿RabbiteMq实现简易消息队列正式篇(需求分析)

TOC 目录 MQ的实现方法 RabbitMq中的相关概念 消息队列系统模块划分 总体划分 服务端模块 数据管理模块 虚拟机数据管理模块 交换机路由模块 消费者管理模块 信道&#xff08;通信&#xff09;管理模块 连接管理模块 服务端BrokerServer模块 客户端模块 消费者管…

MySQL-MVCC举例说明

在数据库系统中&#xff0c;多版本并发控制&#xff08;MVCC, Multi-Version Concurrency Control&#xff09; 是一种用于提高并发性能的机制&#xff0c;它允许多个事务同时读取和写入数据&#xff0c;而不会产生锁等待和阻塞的问题。MySQL 的 InnoDB 存储引擎广泛使用了 MVC…

Keepalived 高可用集群详解和配置

Keepalived 高可用集群 集群类型 1、LB&#xff08;Load Balance&#xff09;&#xff1a;负载均衡 LVS&#xff1a;四层负载均衡 HAProxy&#xff1a;七层/四层 负载均衡 nginx&#xff1a;七层负载均衡 (http/upstream,stream/upstream) 2、HA&#xff08;High Availa bili…

C++ 设计模式——工厂方法模式

工厂方法模式 工厂方法模式主要组成部分代码实现工厂方法模式模式的 UML 图工厂方法模式 UML 图解析优点和缺点适用场景 工厂方法模式 工厂方法模式是一种创建型设计模式&#xff0c;它通过定义一个接口用于创建对象&#xff0c;但由子类决定实例化哪个类。与简单工厂模式不同…

Spring项目:文字花园(三)

一.实现博客详情 1.后端逻辑代码 controller层添加方法&#xff08;根据博客id查看博客详情&#xff09; RequestMapping("/getBlogDetail")public Result<BlogInfo> getBlogDetail(Integer blogId){log.info("getBlogDetail, blogId: {}", blogId…

vue 后台管理 之 状态管理 vuex 的使用

幸福是一种能力 文章目录 一、数据驱动视图二、VueX 数据公共池 一、数据驱动视图 我们都知道 vue 之所以好用&#xff0c;是因为官方帮我们做了数据驱动视图初始化时将数据和视图进行绑定&#xff0c;通过 watcher 来监听数据的变化&#xff0c;当数据变化时&#xff0c;会触…

这三大创意神器,一个不用就是错过了一个小目标!

【导语】在这个充满无限可能的数字时代&#xff0c;创意成为了推动我们前行的强大动力。但你是否曾因视频背景杂乱无章而苦恼&#xff1f;是否渴望将静态图片瞬间赋予生命&#xff1f;又或是对模糊照片中的珍贵瞬间束手无策&#xff1f;今天&#xff0c;就让我们揭秘三大变态且…

Linux搭建环境:从零开始掌握基础操作(二)

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 软件测试第一步就是搭建测试环境&#xff0c;如何搭建好测试环境&#xff0c;需要具备两项的基础知识&#xff1a; 1、Linux 命令: 软件测试第一个任务, 一般都需要进行环境搭建, 一部分&#xff0c;环境搭建内容是在服…

Your local changes would be overwritten by merge git

方法二 直接覆盖本地的代码&#xff0c;放弃自己本地的改动&#xff0c;只保留服务器端代码 直接回退到上一个版本&#xff0c;再进行pull。 【步骤】 直接 VCS -> Git -> Reset HEAD… 选择需要的reset模式&#xff1a;hard&#xff08;即放弃本地代码&#xff0c;新修…

JavaScript基础——闭包

闭包简介 闭包的作用 闭包可以保留变量的状态 闭包可以让变量私有化 闭包的缺点 闭包简介 在JavaScript中&#xff0c;重复声明同一个变量会导致变量冲突&#xff0c;在这个时候可以使用闭包创建独立的执行环境。 在JavaScript中&#xff0c;闭包是指封闭的执行环境&#xff…

【图像去噪】论文精读:Toward Convolutional Blind Denoising of Real Photographs(CBDNet)

文章目录 前言Abstract1. Introduction2. Related Work2.1. Deep CNN Denoisers2.2. Image Noise Modeling2.3. Blind Denoising of Real Images 3. Proposed Method3.1. Realistic Noise Model3.2. Network Architecture3.3. Asymmetric Loss and Model Objective3.4. Trainin…

Spring IoCDI(下)—DI的尾声

我们之前学习了控制反转IoC&#xff0c;接下来就开始学习依赖注入DI的细节。 依赖注入是一个过程&#xff0c;是指IoC容器在创建Bean时&#xff0c;去提供运行时所依赖的资源&#xff0c;而资源指的就是对象。我们使用 Autowired 注解&#xff0c;完成依赖注入的操作。简单来说…

使用docker compose一键部署redis服务

使用docker compose一键部署redis服务 1、创建安装目录 mkdir /data/redis/ -p && cd /data/redis2、创建docker-compose.yml文件 version: 3 services:redis:image: registry.cn-hangzhou.aliyuncs.com/xiaopangpang/redis:7.0.5container_name: redisrestart: al…

【STM32项目】在FreeRtos背景下的实战项目的实现过程(二)

个人主页~ 实战项目的实现过程&#xff08;一&#xff09;~ 实战项目的实现过程 二、初步了解各个外设硬件1、OLED模块2、GPS模块3、MPU6050模块4、超声测距模块5、温度测控模块6、语音模块7、SIM模块8、按键模块 三、查阅资料1、查看手册2、查找例程 四、研究硬件功能1、OLED…

js使用run编码计算region的交集并集差集

所有shape都转为run编码 转为run编码后再运算可以节约大量内存 subtractIntervals 函数的逻辑:目前的实现假设了所有的 subIntervals 都会与 intervals 完全重叠,这可能导致计算不准确。应该将 subIntervals 从 intervals 中去除时,考虑到可能的部分重叠。 差集计算:sub…