当前位置: 首页 > news >正文

宁阳网站定制加拿大购物网站排名

宁阳网站定制,加拿大购物网站排名,做网站知道访客ip,2核4g 1m做网站系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程#xff08;一#xff09;#xff1a;FFMPEG Conan 环境集成基于 FFmpeg 的跨平台视频播放器简明教程#xff08;二#xff09;#xff1a;基础知识和解封装#xff08;demux#xff09;基于 FFmpeg 的跨平台视频…系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程一FFMPEG Conan 环境集成基于 FFmpeg 的跨平台视频播放器简明教程二基础知识和解封装demux基于 FFmpeg 的跨平台视频播放器简明教程三视频解码基于 FFmpeg 的跨平台视频播放器简明教程四像素格式与格式转换基于 FFmpeg 的跨平台视频播放器简明教程五使用 SDL 播放视频基于 FFmpeg 的跨平台视频播放器简明教程六使用 SDL 播放音频和视频基于 FFmpeg 的跨平台视频播放器简明教程七使用多线程解码视频和音频 文章目录 系列文章目录前言I 帧P 帧B帧PTS 与 DTSTimebase时间基Timebase 转换 音画同步精确地纪录和获取时间纪录时间的时机音画同步具体算法 总结参考 前言 在上篇文章 基于 FFmpeg 的跨平台视频播放器简明教程七使用多线程解码视频和音频 中我们使用多个线程来做不同的事情让整个播放器更加的模块化。 我们的播放器现在能够同时视频和音频了但还不够你会发现视频画面和音频会对不上这是因为我们没有做音画同步。在现有的代码中我们每隔 30ms 去播放一帧画面而音频则是由系统的音频线程来负责驱动调用。这等于说视频和音频各播各的画面与声音失去了同步。因此本章将讨论如何进行音画同步。 本文参考文章来自 An ffmpeg and SDL Tutorial - Tutorial 05: Synching Video 和 An ffmpeg and SDL Tutorial - Tutorial 06: Synching Audio。这个系列对新手较为友好但 2015 后就不再更新了以至于文章中的 ffmpeg api 已经被弃用了。幸运的是有人对该教程的代码进行重写使用了较新的 api你可以在 rambodrahmani/ffmpeg-video-player 找到这些代码。 本文的代码在 ffmpeg_video_player_tutorial-my_tutorial05_01_clock.cpp。 I 帧P 帧B帧 I 帧关键帧也被称为内插帧。每一帧都是独立的图像也就是说它不依赖于其他任何帧的图像信息。I帧类似于完整的图像编解码器只需要本帧数据就可以完成解码。在视频播放时I帧也是快进、快退、拖动的定位点通常情况下视频中的第一帧就是I帧。 P 帧预测帧P帧的数据包含与前一个I帧或P帧的差别在编码时仅考虑了前向预测也就是只与前一帧比较来找出两帧之间的区别然后只记录下这个差别。P帧依赖于前面的I帧或P帧。 B 帧双向预测帧B帧记录的是该帧与前后帧的差别即考虑了双向的预测可以理解为B帧被插在I、P帧之间通过前后帧进行预测、记录、解压。B帧依赖于前后的I帧或P帧。 需要所有这些帧的主要原因是为了压缩视频。I帧需要的数据量最多但是不可能所有帧都为I帧这样压缩率就很低所以P帧和B帧诞生了P帧和B帧只记录和参考帧的差异信息所以数据量小压缩率高缺点是如果参考帧丢失那么将导致无法解压出真实图像。总的来说所有这些帧的组合能够使视频数据得以有效的压缩同时保持视频质量。 PTS 与 DTS 假设现在有 4 帧画面且没有 B 帧的情况一种最常见的编码情况是 I P0 P1 P0在 “I P P P” 这种编码模式下视频帧的解码过程较为简单播放顺序和解码顺序是一样的因为这种情况下没有B帧存在P帧只依赖前面的I帧或P帧。 具体来说解码过程如下 首先解码第一帧I帧I帧是关键帧可以独立解码不依赖任何其他帧。然后解码第二帧P帧此P帧只依赖于前一帧前一帧是I帧已经解码。接着解码第三帧P帧此P帧也只依赖于前一帧前一帧是P帧已经解码。最后解码第四帧P帧此P帧只依赖于前一帧前一帧是P帧已经解码。 所以解码顺序和播放顺序一致均为I P P P。 如果有 B 帧呢一种最常见的编码情况是 I B0 B1 P0在 “I B B P” 这种编码模式下视频帧的解码顺序和播放顺序也是不一样的。B帧依赖前面和后面的帧因此需要等待后一个参考帧I或P帧解析完成后才能开始 B 帧的解码。 首先解码第一帧I帧I帧是关键帧可以独立解码不依赖任何其他帧。然后解码第四帧P帧P帧只依赖于之前的I帧或P帧这一步可以顺利进行。解码完第四帧P帧后才能开始解码第二帧和第三帧B帧。B帧需要依赖前一帧和后一帧也就是需要依赖第一帧I帧和第四帧P帧。 所以解码顺序为I P B B。然而播放顺序仍然为I B B P。 因此类似的在包含B帧的情况下解码顺序和播放顺序是有所不同的这也是为什么在视频解码过程中需要对帧进行重新排序的原因。 我们使用 DTSDecoding Time Stamp解码时间戳来表示解码的顺序PTSPresentation Time Stamp呈现时间戳来表示播放的顺序。 在 FFmpeg 中解封装得到 AVPacket 后每个 AVPacket 都有个成员变量叫做 dts解码得到 AVFrame 后每个 AVFrame 都有一个成员变量叫 pts。这两个变量对应着 DTS 和 PTS 的概念。 仍然以上面的 4 帧画面为例在没有 B 帧的情况经过编码后它的码流应该是 Stream: I P0 P1 P2解码顺序与播放顺序一致因此 PTS: 1 2 3 4DTS: 1 2 3 4Stream: I P0 P1 P2在有 B 帧的情况经过编码后码流为 Stream: I P B0 B1解码顺序是 DTS: 1 2 3 4播放顺序是 PTS: 1 4 2 3因此 PTS: 1 4 2 3DTS: 1 2 3 4Stream: I P B0 B1Timebase时间基 DTS 和 PTS 都是时间戳那么时间的单位是什么是秒s还是毫秒ms呢其实都不是DTS 和 PTS 是以时间基(timebase)为单位的时间基是由编码格式决定的通常它是一个如1/90000这样的分数。 为什么要引入 timebase而不是固定使用 秒s或者毫秒ms 呢引入timebase主要是为了提供更为灵活和精细的时间表示方法。虽然可以选择只用秒或毫秒作为单位但这可能会在一些特殊的情况下引入不必要的误差或者损失精度。 在多媒体处理中每种编码格式或者媒体流可能有其特定的帧率或者时钟频率用一个固定的时间单位如秒或毫秒可能难以精确地表示这些特定的时间点或时长。比如有些视频可能是24帧/秒有的可能是30帧/秒有的可能是29.97帧/秒等。 而timebase是一个分数形式的时间单位它的分子和分母都可以根据具体的编码格式或者媒体流来自由设定可以精确适配不同的帧率和时钟频率使得时间表示既可以非常精细也可以非常灵活。例如对于一个帧率为29.97fps的视频我们可以设置timebase为1/30000这样就能够非常精确地表示每一帧的时间。所以引入timebase主要是为了在多媒体处理中提供一个既灵活又精确的时间表示方法。 Timebase 不如常见的秒或者毫秒那样直观。对于绝大多数人来说1/30000秒涉及到的分数运算可能会比较难以理解相比之下33.33毫秒可能会更直观一些。 但是在多媒体编程中由于需要处理各种不同的编码格式和媒体流每种可能有其特定的帧率或者时钟频率这就需要一个更灵活和精细的时间表示方法来适应这些差异这就是引入timebase的原因。 对于程序来说处理timebase只不过是一些简单的乘法和除法运算而对于熟悉多媒体编程的开发者来说他们也已经习惯了timebase这样的时间表示方法。 在FFmpeg中timebase是一个分数表示的是时间单位用于进行时间戳与实际时间的转换。它由一个分子和一个分母构成。分子通常为1分母通常等于媒体的帧率或者时钟频率。 分子(numerator)通常为1在方程中根据需要设定分母(denominator)通常将帧率或者时钟频率设定为分母 以数字视频为例如果一个视频的帧率是每秒30帧那么它的timebase就是1/30这个值表示的是每帧的时间长度。换算成秒就是约0.0333秒。如果你有一个时间戳值例如100那么这个时间戳对应的实际时间就是100 * 1/30 约3.33秒。 再举一个数字音频的例子如果音频的采样率是44100Hz表示每秒钟有44100个音频样本那么它的timebase就是1/44100。如果你有一个时间戳值例如22050那么这个时间戳对应的实际时间就是22050 * 1/44100 0.5秒。 需要注意的是不同的流可能有不同的time_base。例如一个包含视频和音频的媒体文件它的视频流的time_base可能是1/30对应30fps的帧率而音频流的time_base可能是1/44100对应44100Hz的采样率。所以在计算时间戳对应的实际时间时需要使用正确的time_base。 或者换一种理解在FFmpeg中timebase以一个分数形式存在分母表示将1秒拆分成的份额而分子表示每一帧或采样或其他计数单位占用的份额。例如如果一个时间基准timebase是7/30那么这意味着1秒被拆分成了30份每一帧占用了7份。所以每一帧对应的时间就是7 * 1/30 0.2333秒。 在大多数的实际应用中timebase的分子通常为1这是因为在大多数媒体格式中每一帧或其他的计数单位通常都对应于一个单位的时间。这样的话如1/24、1/30、1/60这样的timebase也就表示了每帧的时间直接是1/24秒、1/30秒、1/60秒。 Timebase 转换 由于视频流和音频流使用的 timebase 是不同的为了能够进行音画同步我们首先要做的是将视频帧的 PTS 与音频帧的 PTS 都转换到同一个 Timebase 上以便我们进行时间上的比较。 在FFmpeg中AV_TIME_BASE是一个宏定义其值为1000000。这是作为FFmpeg内部处理时间戳时的一个共同参考用于统一不同的时间基准。 这个宏定义的原理是以微秒作为基础时间单位。这样你可以用这个AV_TIME_BASE来将时间戳从微秒转换到FFmpeg内部使用的时间单位反之亦然。例如如果你有一个以秒为单位的时间量你可以乘以AV_TIME_BASE将其转换为基于FFmpeg的时间单位。 例如1秒 1 * AV_TIME_BASE0.5秒 0.5 * AV_TIME_BASE。多了解这一点可以帮助你更好地理解FFmpeg在处理时间戳时的一些机制和单位转换等问题。 需要注意的是虽然FFmpeg内部用AV_TIME_BASE作为统一的时间处理标准但是不同的流还是可能有各自的时间基准这需要我们在处理时对时间戳进行相应的转换。 假设我们有一个视频流帧率是30fps所以视频流的timebase是1/30我们还有一个音频流样本率是44100Hz所以音频流的timebase是1/44100。 假设我们现在有一个视频帧和一个音频样本它们的PTS显示时间戳分别是v_pts和a_pts。首先我们需要把v_pts和a_pts转换到以AV_TIME_BASE为单位的时间戳也就是以微秒为单位的时间戳 v_pts_us v_pts * (1 / 30) * AV_TIME_BASE a_pts_us a_pts * (1 / 44100) * AV_TIME_BASE然后我们就可以直接比较v_pts_us和a_pts_us这样就能知道音频和视频哪个应该先播放。 如果v_pts_us a_pts_us那么这个视频帧应该先播放 如果a_pts_us v_pts_us那么这个音频样本应该先播放。这样就实现了音画同步。当然这是一个简化的示例在真实的应用中可能还需要更复杂的逻辑来处理AV同步例如处理拖动播放条引起的seek操作处理音频和视频的解码延迟等。 在 FFmpeg API 中转换时间戳的函数是 av_rescale_q。该函数通过给定的 AVRational 结构体表示时间基数的分数结构体来重新缩放时间戳。你可以像下面这样使用它 int64_t v_pts_us av_rescale_q(v_pts, video_stream-time_base, AV_TIME_BASE_Q);这样 av_rescale_q 就会将 v_pts 从 video_stream-time_base 视频流的时间基数转换为 AV_TIME_BASE_QAV_TIME_BASE的时间基数值为1/1000000。同样的你可以用这个函数转换音频时间戳 int64_t a_pts_us av_rescale_q(a_pts, audio_stream-time_base, AV_TIME_BASE_Q);音画同步 有了前面的知识铺垫相信你已经对 FFmpeg 中关于时间概念有所了解这是进行音画同步编程的前提。 就目前的程序而言视频和音频正在愉快地运行根本不需要同步。如果一切正常我们就不必担心这个问题。但你的电脑并不完美很多视频文件也不完美。因此我们有三种选择将音频同步到视频、将视频同步到音频或者将两者同步到外部时钟如电脑。现在我们介绍的是将视频同步到音频。 精确地纪录和获取时间 我们首先要解决的第一个问题是如何精确的纪录当前视频/音频流的时间。只有拿到了精确的时间我们才能知道视频与音频之间的快慢关系。 在 ffmpeg_video_player_tutorial-my_tutorial05_01_clock.cpp 中我们封装了一个叫 Clock 的类它负责纪录时间。 class Clock { public:std::atomicdouble pts{0}; // clock base, secondsstd::atomicdouble last_updated{0}; // last pts updated timestd::atomicdouble pre_pts{0};std::atomicdouble pre_frame_delay{0}; };pts当前流的播放时间单位 slast_updated最近 pts 更新的时间单位 spre_pts上次的 ptspre_frame_delay上次帧的延迟。在某些情况我们将使用这个变量作为当前帧的延迟。 使用下面这些函数来更新/获取时钟 void setClockAt(Clock clock, double pts, double time) {clock.pts pts;clock.last_updated time; }void setClock(Clock clock, double pts) {setClockAt(clock, pts, (double)av_gettime() / 1000000.0); }double getClock(const Clock c) const {double time (double)av_gettime() / 1000000.0;return c.pts time - c.last_updated; }setClock(Clock clock, double pts) 更新当前 clock 的 pts同时更新 last_updated 值last_updated 也就是调用该函数时的系统时间。为什么需要 last_updated getClock 给出了答案。 getClock 获取当前流的播放时间。下图解释了 getClock 的计算逻辑 pts30 pts60 pts90 pts120 last_updated1000 last_updated1030 last_updated1090 last_updated1120 Thread0: |--------------------|--------------------|--------------------| | || | Thread1: |----------|---------------------------------------------|-----| time1015 time1110t030(1015-1000) t190(1110-1090)45 110 想象有两个线程Thread 0视频播放线程和Thread 1。Thread 0定期播放视频帧并更新视频流的时间。Thread 1在任何时间都可能访问视频流时钟以获取当前播放时间。 在时间点 t0系统时间 time 为 1015视频流时钟的 pts30last_updated1000经过的时间是 (time - last_updated) 15这表明更新 pts 距现在过去了15个时间单位所以当前播放时间是 pts 15 45。 在时间点 t1系统时间 time 为 1110视频流时钟的 pts90last_updated1090经过的时间是 (time - last_updated) 20这表明更新 pts 距现在过去了20个时间单位所以当前播放时间是 pts 20 110。 纪录时间的时机 在代码中我们使用两个 clock 分别纪录视频和音频的时间 Clock audio_clock_t; // 纪录音频时间 Clock video_clock_t; // 纪录视频时间那么应该在何时更新时钟的时间呢 在视频流中在渲染当前帧时我们更新 video_clock_t 时钟具体的在 videoRefreshTimer 函数使用 ctx-videoSync 进行时钟更新 void videoRefreshTimer(void *userdata) {// ...auto real_delay ctx-videoSync(video_frame);// ... }在音量流中在播放当前音频帧时我们更新 audio_clock_t 时钟具体的在 audioCallback 函数中使用 ctx-setClock 进行时钟更新 void audioCallback(void *userdata, Uint8 *stream, int len){// ...play_ctx-setClock(play_ctx-audio_clock_t,frame-pts * av_q2d(audio_stream-time_base));// ... }当然注意我们设置时钟时需要将 pts 转换到以秒s为单位确保视频时钟和音频时钟时间单位一致。 音画同步具体算法 本文主要讲解如何将视频同步到音频因此在音频线程中我们只需更新音频时钟而无需进行任何同步操作。所有的音频视频同步操作都会在视频播放线程中进行。 简单来说音视频同步的原理并不复杂。以每秒 25 帧即每 40 毫秒刷新一帧的视频为例。如果视频播放速度超过了音频我们可以适当地延长下一帧的刷新时间使视频播放速度慢一些给音频一些“追赶”的时间比如延长到 45 毫秒刷新一帧。相反如果音频的播放速度快于视频我们可以缩短下一帧的刷新时间使视频播放快一些以便能跟得上音频的速度。 具体计算视频下一帧刷新时间的代码在 videoSync 函数中看代码 int videoSync(AVFrame *video_frame) {auto video_timebase_d av_q2d(decode_ctx-video_stream-time_base);auto pts video_frame-pts * video_timebase_d;setClock(video_clock_t, pts);auto pts_delay pts - video_clock_t.pre_pts;printf(PTS Delay:\t\t\t\t%lf\n, pts_delay);// if the obtained delay is incorrectif (pts_delay 0 || pts_delay 1.0) {// use the previously calculated delaypts_delay video_clock_t.pre_frame_delay;}printf(Corrected PTS Delay:\t%f\n, pts_delay);// save delay information for the next timevideo_clock_t.pre_pts pts;video_clock_t.pre_frame_delay pts_delay;auto audio_ref_clock getAudioClock();auto video_clock getVideoClock();auto diff video_clock - audio_ref_clock;printf(Audio Ref Clock:\t\t%lf\n, audio_ref_clock);printf(Audio Video Delay:\t\t%lf\n, diff);auto sync_threshold std::max(pts_delay, AV_SYNC_THRESHOLD);printf(Sync Threshold:\t\t\t%lf\n, sync_threshold);if (fabs(diff) AV_NOSYNC_THRESHOLD) {if (diff -sync_threshold) {pts_delay 0;} else if (diff sync_threshold) {pts_delay 2 * pts_delay; // [2]}}printf(Corrected PTS delay:\t%lf\n, pts_delay);return (int)std::round(pts_delay * 1000);} };上述代码片段是一个函数用于视频同步。用于使用音频时钟作为参考来调整视频帧的展示时间从而实现音画同步。 第一部分计算出当前视频帧的表现时间 pts。影片序数pts是一个表示时间基数的特殊单位表示在给定的时间标度time base下的时间。这里av_q2d 函数将 decode_ctx-video_stream-time_base 转化为双精度浮点数。将 pts设为当前音频时钟的时间。第二部分计算 pts_delay它表示当前帧和上一帧之间的延迟。如果这被认为是无效的即小于等于0或大于等于1.0则会使用先前的帧延迟。这些信息存储起来供下一次使用。第三部分获取音频时钟 audio_ref_clock和视频时钟 video_clock并计算出二者之间的差值diff这表示音频和视频帧之间的延迟。第四部分计算出调整阈值 sync_threshold当差值 diff 在阈值范围内时不调整 pts_delay音频和视频同步。若差值音频和视频延时过大则根据其正负使视频速度加快或放慢实现音视频同步。最后返回 pts_delay 的值以毫秒为单位这个返回值将被用作等待时间来决定何时展示下一帧。 在视频播放线程中使用 videoSync 计算出下一帧的刷新等待时间后使用 scheduleRefresh(ctx, real_delay); 设置一个定时器让定时器在 real_delay 后发送一个事件让 SDL 去渲染下一帧画面。就这样我们完成了音画同步。 总结 本文介绍了如何实现播放器的音画同步首先介绍了 I/P/B 帧的区别引出了 PTS 和 DTS 的概念接着介绍了在 FFmpeg 中的 timebase 的概念让读者了解 FFmpeg 是如何描述时间的然后我们详细的描述了音画同步实施的具体要点包括如何精确的纪录不同流的当前时间在什么时间节点来更新时钟以及音画同步的具体算法。 参考 An ffmpeg and SDL Tutorial - Tutorial 05: Synching VideoAn ffmpeg and SDL Tutorial - Tutorial 06: Synching AudioFFmpeg 音视频DTS / PTS ffmpeg_video_player_tutorial-my_tutorial05_01_clock.cpp。
http://www.w-s-a.com/news/510601/

相关文章:

  • wordpress证书关闭重庆seo优化效果好
  • 直播网站建设模板网站活动怎么做的
  • 医院网站建设网站网站开发工资高嘛
  • 个人网站备案内容写什么做网站是不是涉及很多语言职
  • 怎么用手机做一个网站门户网站建设工作的自查报告
  • 网站搭建怎么收费浙江建设集团网站
  • 建网站怎么赚钱免费引流软件下载
  • 自建网站服务器备案做基础销量的网站
  • 淘宝 网站建设 发货音乐网站首页设计
  • 丽水做网站杭州建电商网站多少钱
  • 建设网站能解决什么问题wordpress 模板 中文
  • 平台型网站建设预算表友情链接中有个网站域名过期了会影响
  • 漯河网站开发运营seo是什么意思
  • 网站建设的征求意见稿iis 网站 红
  • 网站搭建教室企业网站开发实训心得
  • 阿克苏建设网站佛山app定制
  • 做淘宝网站要求与想法大型网站建设推荐
  • 在百度做网站赚钱吗跨境电商网站开发
  • 酒店网站建设策划方案南昌网站建设南昌
  • 临沂罗庄做网站房产cms
  • 五合一网站做优化好用吗网站设计的专业流程
  • 毕业设计论文网站开发需要多少网站seo建设方案
  • h5页面用什么做杭州优化外包哪里好
  • 许昌网站建设百姓国货app下载
  • 什么是建站装修公司做宣传在哪个网站
  • 阿里云虚拟主机多个网站吗大庆油田建设集团网站
  • 坂田公司做网站公司有网站域名后如何建网站
  • 自媒体网站程序淘宝网站维护
  • 凡科网站建设网站wordpress 七牛oss
  • 搬瓦工的主机可以用来做网站吗分类信息网站开发需求方案