1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #pragma once #include "xtools.h" #include <mutex> struct AVFormatContext ;struct AVCodecParameters ;struct AVPacket ;struct AVCodecContext ;struct XRational { int num; int den; }; class XFormat { public : bool CopyPara (int stream_index, AVCodecParameters *dst) ; bool CopyPara (int stream_index, AVCodecContext *dts) ; std::shared_ptr<XPara> CopyVideoPara () ; std::shared_ptr<XPara> CopyAudioPara () ; void set_c (AVFormatContext *c) ; int audio_index () { return audio_index_; } int video_index () { return video_index_; } XRational video_time_base () { return video_time_base_; } XRational audio_time_base () { return audio_time_base_; } bool RescaleTime (AVPacket *pkt, long long offset_pts, XRational time_base) ; bool RescaleTime (AVPacket *pkt, long long offset_pts, AVRational* time_base) ; long long RescaleToMs (long long pts, int index) ; int video_codec_id () { return video_codec_id_; } bool IsTimeout () { if (NowMs () - last_time_ > time_out_ms_) { last_time_ = NowMs (); is_connected_ = false ; return true ; } return false ; } void set_time_out_ms (int ms) ; bool is_connected () { return is_connected_; } protected : int time_out_ms_ = 0 ; long long last_time_ = 0 ; bool is_connected_ = false ; AVFormatContext *c_ = nullptr ; std::mutex mux_; int video_index_ = 0 ; int audio_index_ = 1 ; XRational video_time_base_ = {1 ,25 }; XRational audio_time_base_ = {1 , 9000 }; int video_codec_id_ = 0 ; };
参数说明:
1 2 3 4 5 6 7 8 9 10 11 12 protected : int time_out_ms_ = 0 ; long long last_time_ = 0 ; bool is_connected_ = false ; AVFormatContext *c_ = nullptr ; std::mutex mux_; int video_index_ = 0 ; int audio_index_ = 1 ; XRational video_time_base_ = {1 ,25 }; XRational audio_time_base_ = {1 , 9000 }; int video_codec_id_ = 0 ;
IsTimeout()
1 2 3 4 5 6 7 8 9 10 11 12 bool IsTimeout () { if (NowMs () - last_time_ > time_out_ms_) { last_time_ = NowMs (); is_connected_ = false ; return true ; } return false ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 #include "xformat.h" #include <iostream> #include <thread> #include "xtools.h" using namespace std;extern "C" {#include <libavformat\avformat.h> #include <libavcodec\avcodec.h> } #pragma comment(lib, "avformat.lib" ) #pragma comment(lib, "avutil.lib" ) static int TimeoutCallBack (void *para) { auto xf = (XFormat*)para; if (xf->IsTimeout ()) return 1 ; return 0 ; } void XFormat::set_c (AVFormatContext *c) { unique_lock<mutex> lock (mux_) ; if (c_) { if (c_->oformat) { if (c_->pb) avio_closep (&c_->pb); avformat_free_context (c_); } else if (c_->iformat) { avformat_close_input (&c_); } else { avformat_free_context (c_); } } c_ = c; if (!c_) { is_connected_ = false ; return ; } is_connected_ = true ; last_time_ = NowMs (); if (time_out_ms_ > 0 ) { AVIOInterruptCB cb = { TimeoutCallBack , this }; c_->interrupt_callback = cb; } audio_index_ = -1 ; video_index_ = -1 ; for (int i = 0 ; i < c->nb_streams; i++) { if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_index_ = i; audio_time_base_.den = c->streams[i]->time_base.den; audio_time_base_.num = c->streams[i]->time_base.num; } else if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_index_ = i; video_time_base_.den = c->streams[i]->time_base.den; video_time_base_.num = c->streams[i]->time_base.num; video_codec_id_ = c->streams[i]->codecpar->codec_id; } } } std::shared_ptr<XPara> XFormat::CopyAudioPara () { unique_lock<mutex> lock (mux_) ; int index = audio_index (); shared_ptr<XPara> re; if (index < 0 || !c_) return re; re.reset (XPara::Create ()); *re->time_base = c_->streams[index]->time_base; avcodec_parameters_copy (re->para, c_->streams[index]->codecpar); return re; } std::shared_ptr<XPara> XFormat::CopyVideoPara () { unique_lock<mutex> lock (mux_) ; int index = video_index (); shared_ptr<XPara> re; if (index < 0 || !c_) return re; re.reset (XPara::Create ()); *re->time_base = c_->streams[index]->time_base; avcodec_parameters_copy (re->para, c_->streams[index]->codecpar); return re; } bool XFormat::CopyPara (int stream_index, AVCodecParameters *dst) { unique_lock<mutex> lock (mux_) ; if (!c_) { return false ; } if (stream_index < 0 || stream_index > c_->nb_streams) return false ; auto re = avcodec_parameters_copy (dst, c_->streams[stream_index]->codecpar); if (re < 0 ) return false ; return true ; } bool XFormat::CopyPara (int stream_index, AVCodecContext *dts) { unique_lock<mutex> lock (mux_) ; if (!c_) { return false ; } if (stream_index < 0 || stream_index > c_->nb_streams) return false ; auto re = avcodec_parameters_to_context (dts, c_->streams[stream_index]->codecpar); if (re < 0 ) return false ; return true ; } bool XFormat::RescaleTime (AVPacket *pkt, long long offset_pts, AVRational* time_base) { if (!pkt || !time_base) return false ; unique_lock<mutex> lock (mux_) ; if (!c_) return false ; auto out_stream = c_->streams[pkt->stream_index]; pkt->pts = av_rescale_q_rnd (pkt->pts - offset_pts, *time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) ); pkt->dts = av_rescale_q_rnd (pkt->dts - offset_pts, *time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) ); pkt->duration = av_rescale_q (pkt->duration, *time_base, out_stream->time_base ); pkt->pos = -1 ; return true ; } bool XFormat::RescaleTime (AVPacket *pkt, long long offset_pts, XRational time_base) { AVRational in_time_base; in_time_base.num = time_base.num; in_time_base.den = time_base.den; return RescaleTime (pkt, offset_pts, &in_time_base); } long long XFormat::RescaleToMs (long long pts, int index) { unique_lock<mutex> lock (mux_) ; if (!c_ || index < 0 || index > c_->nb_streams) return 0 ; auto in_timebase = c_->streams[index]->time_base; AVRational out_timebase = { 1 , 1000 }; return av_rescale_q (pts, in_timebase, out_timebase); } void XFormat::set_time_out_ms (int ms) { unique_lock<mutex> lock (mux_) ; this ->time_out_ms_ = ms; if (c_) { AVIOInterruptCB cb = { TimeoutCallBack , this }; c_->interrupt_callback = cb; } }
TimeoutCallBack()
作用:一个用于超时检测的回调函数,通过检查 XFormat
对象的 IsTimeout
方法来确定是否超时,并返回相应的结果,用于中断或继续执行阻塞操作。
1 2 3 4 5 6 7 static int TimeoutCallBack (void *para) { auto xf = (XFormat*)para; if (xf->IsTimeout ()) return 1 ; return 0 ; }
set_c()
作用:函数的主要作用是设置 AVFormatContext
对象,并进行一系列的初始化和清理操作,以确保新的 AVFormatContext
对象能够正确使用。具体步骤包括清理旧的上下文、设置新的上下文、初始化音视频流索引、设定超时回调函数和更新连接状态等。这些操作确保了在多线程环境下安全且有效地管理音视频流的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 void XFormat::set_c (AVFormatContext *c) { unique_lock<mutex> lock (mux_) ; if (c_) { if (c_->oformat) { if (c_->pb) avio_closep (&c_->pb); avformat_free_context (c_); } else if (c_->iformat) { avformat_close_input (&c_); } else { avformat_free_context (c_); } } c_ = c; if (!c_) { is_connected_ = false ; return ; } is_connected_ = true ; last_time_ = NowMs (); if (time_out_ms_ > 0 ) { AVIOInterruptCB cb = { TimeoutCallBack , this }; c_->interrupt_callback = cb; } audio_index_ = -1 ; video_index_ = -1 ; for (int i = 0 ; i < c->nb_streams; i++) { if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_index_ = i; audio_time_base_.den = c->streams[i]->time_base.den; audio_time_base_.num = c->streams[i]->time_base.num; } else if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_index_ = i; video_time_base_.den = c->streams[i]->time_base.den; video_time_base_.num = c->streams[i]->time_base.num; video_codec_id_ = c->streams[i]->codecpar->codec_id; } } }
CopyAudioPara()
作用:复制音频参数并将其封装在一个智能指针 std::shared_ptr<XPara>
中返回
XPara的介绍在XTool中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 std::shared_ptr<XPara> XFormat::CopyAudioPara () { unique_lock<mutex> lock (mux_) ; int index = audio_index (); shared_ptr<XPara> re; if (index < 0 || !c_) return re; re.reset (XPara::Create ()); *re->time_base = c_->streams[index]->time_base; avcodec_parameters_copy (re->para, c_->streams[index]->codecpar); return re; }
CopyVideoPara()
作用:复制视频参数并将其封装在一个智能指针 std::shared_ptr<XPara>
中返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 std::shared_ptr<XPara> XFormat::CopyVideoPara () { unique_lock<mutex> lock (mux_) ; int index = video_index (); shared_ptr<XPara> re; if (index < 0 || !c_) return re; re.reset (XPara::Create ()); *re->time_base = c_->streams[index]->time_base; avcodec_parameters_copy (re->para, c_->streams[index]->codecpar); return re; }
CopyPara()
作用:用于复制特定流的编解码参数到目标 AVCodecParameters
结构体中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 bool XFormat::CopyPara (int stream_index, AVCodecParameters *dst) { unique_lock<mutex> lock (mux_) ; if (!c_) { return false ; } if (stream_index < 0 || stream_index > c_->nb_streams) return false ; auto re = avcodec_parameters_copy (dst, c_->streams[stream_index]->codecpar); if (re < 0 ) return false ; return true ; }
CopyPara()
作用:当前代码的 CopyPara
函数将特定流的编解码参数从 AVFormatContext
中的 AVCodecParameters
复制到目标 AVCodecContext
结构体 dts
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool XFormat::CopyPara (int stream_index, AVCodecContext *dts) { unique_lock<mutex> lock (mux_) ; if (!c_) { return false ; } if (stream_index < 0 || stream_index > c_->nb_streams) return false ; auto re = avcodec_parameters_to_context (dts, c_->streams[stream_index]->codecpar); if (re < 0 ) return false ; return true ; }
两个CopyPara()的区别
目标对象不同 :
使用的函数不同 :
总结
这两段代码的主要区别在于复制的目标对象和使用的函数不同。第一段代码将参数复制到 AVCodecParameters
结构体中,而第二段代码将参数复制到 AVCodecContext
结构体中。这种设计是为了满足不同的需求:当需要简单的参数复制时,使用 AVCodecParameters
;当需要完整的编解码上下文时,使用 AVCodecContext
。
RescaleTime()
作用:用于对给定的 AVPacket
进行时间基准的重新缩放。具体来说,它将 AVPacket
的时间戳(PTS 和 DTS)和持续时间根据新的时间基准进行重新计算,并应用一个偏移量。
使用场景
在处理音视频数据时,可能需要对不同时间基准的时间戳进行转换,以便在不同的媒体流之间进行同步或处理。
该函数确保 AVPacket
的时间戳和持续时间与目标流的时间基准一致,从而便于后续的处理或传输。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 bool XFormat::RescaleTime (AVPacket *pkt, long long offset_pts, AVRational* time_base) { if (!pkt || !time_base) return false ; unique_lock<mutex> lock (mux_) ; if (!c_) return false ; auto out_stream = c_->streams[pkt->stream_index]; pkt->pts = av_rescale_q_rnd (pkt->pts - offset_pts, *time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) ); pkt->dts = av_rescale_q_rnd (pkt->dts - offset_pts, *time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) ); pkt->duration = av_rescale_q (pkt->duration, *time_base, out_stream->time_base ); pkt->pos = -1 ; return true ; }
RescaleTime()
作用:调用另一个 RescaleTime
函数,该函数接受一个 AVRational
类型的时间基准指针。传递参数 pkt
、offset_pts
和转换后的 in_time_base
,并返回调用结果。
使用场景
当外部代码使用 XRational
类型的时间基准时,可以调用这个函数来简化类型转换的过程,并确保时间基准可以被正确处理。
提供了对 XRational
类型的时间基准的支持,使得代码更加灵活,可以处理多种时间基准表示。
1 2 3 4 5 6 7 8 bool XFormat::RescaleTime (AVPacket *pkt, long long offset_pts, XRational time_base) { AVRational in_time_base; in_time_base.num = time_base.num; in_time_base.den = time_base.den; return RescaleTime (pkt, offset_pts, &in_time_base); }
RescaleToMs()
1 2 3 4 5 6 7 8 9 10 long long XFormat::RescaleToMs (long long pts, int index) { unique_lock<mutex> lock (mux_) ; if (!c_ || index < 0 || index > c_->nb_streams) return 0 ; auto in_timebase = c_->streams[index]->time_base; AVRational out_timebase = { 1 , 1000 }; return av_rescale_q (pts, in_timebase, out_timebase); }
时间戳(PTS、DTS)
DTS
(解码时间戳)和 PTS
(显示时间戳)是音视频处理中的两个重要概念。DTS
确保解码顺序正确,而 PTS
确保播放顺序正确。在实际应用中,这两个时间戳共同作用,确保音视频流的同步和正确播放。理解并正确处理 DTS
和 PTS
对于开发高效的音视频处理应用至关重要。
av_rescale_q(pts, in_timebase, out_timebase);
FFmpeg 库中的一个函数,用于在不同时间基准(timebase)之间转换时间戳,既可以计算PTS,也可以计算DTS 。其原型定义如下:
1 int64_t av_rescale_q (int64_t a, AVRational bq, AVRational cq) ;
其中:
a
是要转换的时间戳值。
bq
是输入时间基准(in_timebase),以 AVRational
类型表示。
cq
是输出时间基准(out_timebase),以 AVRational
类型表示。
AVRational
是一个表示有理数的结构,定义如下:
1 2 3 4 typedef struct AVRational { int num; int den; } AVRational;
该函数的作用是将时间戳从 bq
基准转换到 cq
基准,其计算公式为:
$\displaystyle result = a \times \frac{cq.num}{cq.den} ÷ \frac{dq.num}{dq.den}$
【例子 】
假设你有一个时间戳 pts = 1000
,输入时间基准是 1/1000
(每秒 1000 帧),输出时间基准是 1/1000000
(每秒 1000000 微秒),你可以使用 av_rescale_q
函数进行转换。
$\displaystyle 1000\times \frac{1}{100000}÷\frac{1}{1000}=1000000$
转换结果为:rescaled_pts
将从 1000 转换为 1000000
I、P、B帧介绍
PTS
转换 PTS
(Presentation Timestamp,显示时间戳)在音视频处理中的作用非常重要,主要包括以下几个方面:
时间同步
不同的媒体流(例如视频流和音频流)可能有不同的时间基准(timebase)。为了确保不同媒体流在播放时能够同步,需要将这些流的时间戳转换到同一个时间基准。例如,在视频编辑或播放时,需要确保视频和音频在时间上准确对齐。
格式转换
在进行视频格式转换时,输入和输出格式可能有不同的时间基准。为了正确地转换和显示时间戳,必须将时间戳从输入格式的时间基准转换为输出格式的时间基准。
帧率变化
当视频的帧率发生变化时,例如从 30fps 转换到 60fps,需要重新计算每一帧的显示时间戳。这种情况下,转换 PTS
有助于确保视频在新的帧率下能够正确播放。
精确定位
在视频剪辑或处理过程中,精确定位某一帧或某一时刻需要准确的时间戳转换。例如,剪辑工具需要将用户指定的时间点转换为实际的视频帧时间戳,以便于精确地剪切视频。
示例场景
假设你有一个音视频播放器,需要处理不同来源的媒体文件,这些文件的时间基准可能不同。为了使播放器能够正确地解码和播放这些文件,你需要使用 av_rescale_q
将不同时间基准的时间戳统一转换到播放器使用的时间基准。
DTS
在音视频处理领域,除了 PTS
(显示时间戳,Presentation Timestamp),还有一个重要的概念是 DTS
(解码时间戳,Decoding Timestamp)。理解 DTS
的作用有助于更好地处理和同步音视频数据。
什么是 DTS?
DTS (Decoding Timestamp)是指解码时间戳,它标识了视频帧或音频样本在解码过程中的顺序。
与 PTS
不同,PTS
决定了帧或样本的播放顺序,而 DTS
决定了解码顺序。
为什么需要 DTS?
顺序解码 :在一些视频编码格式中(例如H.264),帧的显示顺序和解码顺序可能不同。B帧(双向预测帧)需要参考前后的帧才能解码,因此需要 DTS
来确保解码器按照正确的顺序进行解码。
处理延迟 :一些视频处理操作(例如去交错、降噪等)需要按照解码顺序来进行处理,然后按照 PTS
顺序来显示。如果没有 DTS
,解码器无法正确处理这些操作。
同步播放 :在多媒体播放中,需要同步解码和播放多个音视频流。DTS
确保解码顺序正确,而 PTS
确保播放顺序正确。
DTS 和 PTS 的关系
顺序不同 :在一些情况下,PTS
和 DTS
可能是相同的,但对于涉及B帧的视频,PTS
和 DTS
会不同。通常,DTS
先于 PTS
。
计算顺序 :解码器首先使用 DTS
进行解码,然后使用 PTS
进行播放。因此,音视频数据在解码和播放之间可能存在缓冲区。
示例场景
假设你有一个视频流,其中包含I帧(关键帧)、P帧(预测帧)和B帧(双向预测帧)。这些帧的解码顺序和显示顺序可能如下:
1 2 3 帧顺序: I1, B1, P1 解码顺序(DTS):I1, P1, B1 显示顺序(PTS):I1, B1, P1
在这个示例中,DTS 确保帧按照正确的顺序解码,而 PTS 确保帧按照正确的顺序显示,DTS中的B帧需要I帧、P帧解码完毕后,才能进行解码
DTS 的计算与使用
在 FFmpeg 中,DTS
和 PTS
的计算和使用类似,通常通过解码器和复用器自动处理。然而,在一些高级应用中,开发者可能需要手动处理这些时间戳以确保正确的解码和同步。
I、P、B帧
I帧(关键帧,Intra Frame)
定义 :I帧是独立编码的帧,它不依赖其他帧的信息进行解码。
特点
:
自包含 :I帧包含完整的图像信息,可以独立解码。
高质量 :由于包含完整的图像信息,I帧通常质量较高,但也因此占用较大的存储空间。
随机访问 :由于不依赖其他帧,可以作为随机访问点,非常适合用于快进、快退等操作。
应用 :I帧通常出现在视频序列的开头或者定期插入视频流中,以便于解码器重新同步和快速跳转。
P帧(预测帧,Predicted Frame)
定义 :P帧是基于前面的I帧或P帧进行预测编码的帧。
特点
:
差值编码 :P帧只存储与前一帧(参考帧)的差异部分,节省存储空间。
依赖性 :P帧的解码依赖于前面的I帧或P帧,需要解码器按顺序解码。
压缩效率 :由于只存储变化部分,P帧压缩效率较高。
应用 :P帧广泛用于视频流中,以降低数据量,提高压缩效率。
B帧(双向预测帧,Bi-directional Predicted Frame)
定义 :B帧是基于前面的I帧或P帧和后面的I帧或P帧进行双向预测编码的帧。
特点
:
双向预测 :B帧可以利用前后两帧的信息进行差值编码,进一步提高压缩效率。
更高压缩率 :由于利用了更多的预测信息,B帧通常能达到更高的压缩率。
解码顺序 :B帧的解码依赖于前后的参考帧,需要解码器有足够的缓冲区来处理帧顺序。
应用 :B帧用于进一步提高视频流的压缩效率,通常出现在P帧之间。
在视频编码中,帧的排列和依赖关系如下:
I帧 :独立存在,不依赖其他帧。
P帧 :依赖前面的I帧或P帧。
B帧 :依赖前后的I帧或P帧。
I帧、P帧和B帧在视频编码中各自有不同的作用和特点。I帧提供独立的解码点,P帧通过前向预测减少数据量,而B帧通过双向预测进一步提高压缩效率。理解这些帧的特点和作用,有助于更好地进行视频编码、解码和处理。
set_time_out_ms()
作用:set_time_out_ms
函数用于设置超时时间,并在 AVFormatContext
对象存在时,配置一个回调函数 TimeoutCallBack
来处理超时退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 void XFormat::set_time_out_ms (int ms) { unique_lock<mutex> lock (mux_) ; this ->time_out_ms_ = ms; if (c_) { AVIOInterruptCB cb = { TimeoutCallBack , this }; c_->interrupt_callback = cb; } }