网站建设验收合同/一键生成网站

网站建设验收合同,一键生成网站,网站怎么做市场分析,wordpress 建立分类系列文章目录 嵌入式音视频开发(零)移植ffmpeg及推流测试 嵌入式音视频开发(一)ffmpeg框架及内核解析 嵌入式音视频开发(二)ffmpeg音视频同步 嵌入式音视频开发(三)直播协议及编码器…

系列文章目录

嵌入式音视频开发(零)移植ffmpeg及推流测试
嵌入式音视频开发(一)ffmpeg框架及内核解析
嵌入式音视频开发(二)ffmpeg音视频同步
嵌入式音视频开发(三)直播协议及编码器


文章目录

  • 系列文章目录
  • 前言
  • 一、音视频同步
    • 1.1 基础概念
    • 1.2 三种同步方法
  • 二、音视频同步的实现
    • 2.1 时间基的转换问题
    • 2.2 音频为基准
      • 2.2.1 实现思路
      • 2.2.2 代码大纲
    • 2.3 外部时钟同步
      • 2.3.1 实现思路
      • 2.3.2 代码大纲


前言

  前文中已经讲述了音视频处理的流程,需要我们将音频数据和视频数据分开处理,这个时候我们就需要音视频同步操作。

一、音视频同步

  我们平常看视频的时候最烦恼的就是各种音画不同步,例如音频是100ms延时而视频需要150ms延时才能到达,这其中我们就需要进行音视频同步来解决这个问题。 音视频同步是多媒体处理中的一个关键问题,常用方法包括三种不同的同步策略:以视频为基准、以音频为基准和以外部时钟为基准。

1.1 基础概念

  在FFmpeg进行音视频解码时,PTS (Presentation Time Stamp) 是一个非常重要的概念,它表示每一帧数据(音频帧或视频帧)的展示时间,即该帧应该在播放设备上显示的精确时间。
  时间基(Timebase)是一个分数,表示每秒的时间单位。它用于将 PTS和 DTS(从基于时钟滴答的计数转换为实际的时间(秒)。常见的表示形式为 1/fps 或 1/sample_rate例如,假设视频流的时间基准是1/90000,那么每个时间单位代表1/90000秒。因此,PTS值为90000时,相当于1秒。实际上ffmpeg内部存在多种时间基,在不同的阶段(结构体)中,对应的时间基的值都不相同。

表示方法结构体描述作用
time_baseAVStream流的时间基用于将 PTS 和 DTS 转换为实际时间
time_baseAVCodecContext编码器或解码器的时间基用于内部处理和同步
video_codec_timebase audio_codec_timebaseAVFormatContext格式上下文的时间基用于整体管理和同步

  值得注意的是,虽然 AVPacket 和 AVFrame 本身没有直接的时间基字段,但它们的时间戳(PTS 和 DTS)是基于其所属流的时间基来解释的。

  时间戳可以简单理解为计时器,用于记录或设置对应时间点的操作。在 FFmpeg 中,时间戳用于同步音视频帧的播放时间。时间戳的计算公式如下:

  • timestamp(ffmpeg 内部时间戳) = PTS * 时间基
  • time(秒) = PTS * 时间基

  例如,假设我们有一个视频流,其时间基为 1/90000,若某帧的 PTS 值为 90000,则该帧的实际展示时间为time(秒) = PTS * 时间基 = 90000 * (1/90000) = 1 秒

1.2 三种同步方法

  这里先简单举个例子,例如下图所示,原本的视频应在0.080秒有一帧,但是现在出现了掉帧,此时对应音频就需要加速播放或者也相应丢一帧。简单来说就是,以谁为基准就由谁来维护时间轴
在这里插入图片描述
  (1)以视频为基准:视频被视为主要的同步标准,音频的播放时间会根据视频帧的时间戳来进行调整。如果音频的播放时间比视频快,系统会延迟音频的播放,为避免过多积压可能会丢弃部分音频帧;如果音频播放落后于视频,系统会通过延时音频的播放来保证同步。
  (2)以音频为基准:以音频为基准时,视频会根据音频的时间戳进行调整。如果视频的播放时间比音频快,系统会延迟视频的播放,直到音频达到相应的时间点;而视频播放落后于音频,系统会加速视频的播放,丢掉部分视频帧,从而保证音视频同步。
  (3) 以外部时钟为基准:外部时钟同步是一种更为综合的方式,它使用一个外部时钟(例如系统时钟或硬件时钟)来同时控制音频和视频的播放。外部时钟会提供一个精确的时间基准视频和音频都需要根据这个时钟进行调整

二、音视频同步的实现

2.1 时间基的转换问题

  前面提到了ffmpeg内部存在多种时间基,在不同的阶段(结构体)中,对应的时间基的值都不相同,此外视频流的时间基和音频流的时间基也不同。通常情况下需要使用av_q2d()函数将AVRational 类型的时间基(Timebase)转换为双精度浮点数(double)。AVRational 是一个表示分数的结构体,通常用于表示时间基、帧率等需要精确表示的比率。

typedef struct AVRational {int num; ///< 分子 (numerator)int den; ///< 分母 (denominator)
} AVRational;// 通过 av_q2d 函数将时间基转换为浮点数后,可以将其乘以 PTS 或 DTS 来得到实际时间
double av_q2d(AVRational q) {return q.num / (double)q.den;
}

2.2 音频为基准

  音频为基准和视频为基准在实现逻辑上差不多,这里以音频为例。

2.2.1 实现思路

  以音频为基准进行同步的基本思路是:

  1. 选择音频流作为同步基准
  2. 解码音频数据并更新当前音频时间戳
  3. 解码视频数据并根据音频时间戳调整视频帧的显示时间,确保音视频同步
  4. 通过适当的缓冲控制,确保播放的流畅性和稳定性

2.2.2 代码大纲

int main{// 初始化 FFmpeg 库av_register_all();AVFormatContext *fmt_ctx = NULL;// 打开输入文件并获取流信息if (open_input_file(&fmt_ctx, "input.mp4") < 0) {return -1;}// 查找音视频流并初始化解码器int audio_stream_idx = find_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO);int video_stream_idx = find_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO);AVCodecContext *audio_dec_ctx = init_decoder(fmt_ctx, audio_stream_idx);AVCodecContext *video_dec_ctx = init_decoder(fmt_ctx, video_stream_idx);// 循环读取数据包并同步播放AVPacket pkt;while (read_packet(fmt_ctx, &pkt) >= 0) {if (pkt.stream_index == audio_stream_idx) {process_audio_packet(&pkt, audio_dec_ctx);} else if (pkt.stream_index == video_stream_idx) {process_video_packet(&pkt, video_dec_ctx, audio_dec_ctx->time_base);}av_packet_unref(&pkt);}// 清理资源cleanup(fmt_ctx, audio_dec_ctx, video_dec_ctx);
}// 解码音频数据包并更新当前音频时间戳
void process_audio_packet(AVPacket *pkt, AVCodecContext *dec_ctx) {int ret = avcodec_send_packet(dec_ctx, pkt);if (ret < 0) {fprintf(stderr, "Error sending a packet for decoding\n");return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");break;}// 更新当前音频时间戳update_current_audio_pts(frame->pts, dec_ctx->time_base);}
}void update_current_audio_pts(int64_t pts, AVRational time_base) {double pts_in_seconds = pts * av_q2d(time_base);current_audio_pts = pts_in_seconds;
}void process_video_packet(AVPacket *pkt, AVCodecContext *dec_ctx, AVRational audio_time_base) {int ret = avcodec_send_packet(dec_ctx, pkt);if (ret < 0) {fprintf(stderr, "Error sending a packet for decoding\n");return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");break;}// 获取视频帧的 PTS 并转换为秒double video_pts_in_seconds = frame->pts * av_q2d(dec_ctx->time_base);// 根据音频时间戳调整视频帧的显示时间sync_video_to_audio(video_pts_in_seconds, audio_time_base);}
}void sync_video_to_audio(double video_pts, AVRational audio_time_base) {while (video_pts > current_audio_pts) {usleep(1000); // 简单的等待机制// 更新当前音频时间戳current_audio_pts = get_current_audio_pts(audio_time_base);// 其他操作}
}double get_current_audio_pts(AVRational audio_time_base) {// 这里应该实现一个函数来获取最新的音频时间戳// 例如通过解码更多的音频帧或使用其他方法return current_audio_pts;
}

2.3 外部时钟同步

2.3.1 实现思路

  以外部时钟为基准进行同步的基本思路是:

  1. 使用外部时钟(如系统时钟)作为基准
  2. 解码音频数据包,根据外部时钟调整音频播放时间
  3. 解码视频数据包,根据外部时钟调整视频帧的显示时间
  4. 通过适当的缓冲控制,确保播放的流畅性和稳定性

2.3.2 代码大纲

  这里的代码和上文差不多,只有调整部分的逻辑不太一样:

// 获取当前外部时钟时间(秒)
double get_external_clock() {struct timespec now;clock_gettime(CLOCK_MONOTONIC, &now); // 使用单调递增的时钟避免系统时间变化的影响double elapsed = (now.tv_sec - start_time.tv_sec) + (now.tv_nsec - start_time.tv_nsec) / 1e9;return elapsed;
}// 解码音频数据包并根据外部时钟调整音频播放时间
void process_audio_packet(AVPacket *pkt, AVCodecContext *dec_ctx) {int ret = avcodec_send_packet(dec_ctx, pkt);if (ret < 0) {fprintf(stderr, "Error sending a packet for decoding\n");return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");break;}// 将音频帧的时间戳转换为秒double audio_pts_in_seconds = frame->pts * av_q2d(dec_ctx->time_base);// 根据外部时钟调整音频帧的播放时间sync_audio_to_external_clock(audio_pts_in_seconds, dec_ctx->time_base);}
}void sync_audio_to_external_clock(double audio_pts, AVRational time_base) {double external_clock_time = get_external_clock(); // 获取外部时钟时间(秒)// 等待直到音频帧应该播放的时间while (audio_pts > external_clock_time) {usleep(1000); // 简单的等待机制external_clock_time = get_external_clock();}// 其他操作
}void process_video_packet(AVPacket *pkt, AVCodecContext *dec_ctx, AVRational audio_time_base) {int ret = avcodec_send_packet(dec_ctx, pkt);if (ret < 0) {fprintf(stderr, "Error sending a packet for decoding\n");return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");break;}// 获取视频帧的 PTS 并转换为秒double video_pts_in_seconds = frame->pts * av_q2d(dec_ctx->time_base);// 根据外部时钟调整视频帧的显示时间sync_video_to_external_clock(video_pts_in_seconds, dec_ctx->time_base);}
}void sync_video_to_external_clock(double video_pts, AVRational video_time_base) {double external_clock_time = get_external_clock(); // 获取外部时钟时间(秒)// 等待直到视频帧应该显示的时间while (video_pts > external_clock_time) {usleep(1000); // 简单的等待机制external_clock_time = get_external_clock();}// 其他操作
}

免责声明:本文参考了网上公开的部分资料,仅供学习参考使用,若有侵权或勘误请联系笔者

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

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

相关文章

iOS App的启动与优化

App的启动流程 App启动分为冷启动和热启动 冷启动&#xff1a;从0开始启动App热启动&#xff1a;App已经在内存中&#xff0c;但是后台还挂着&#xff0c;再次点击图标启动App。 一般对App启动的优化都是针对冷启动。 App冷启动可分为三个阶段&#xff1a; dyld&#xff1a…

DDoS技术解析

这里是Themberfue 今天我们不聊别的&#xff0c;我们聊聊著名的网络攻击手段之一的 DDoS&#xff0c;看看其背后的技术细节。 DoS 了解 DDoS 前&#xff0c;先来讲讲 DoS 是什么&#xff0c;此 DoS 而不是 DOS 操作系统啊。1996年9月6日&#xff0c;世界第三古老的网络服务提供…

docker安装kafka,并通过springboot快速集成kafka

目录 一、docker安装和配置Kafka 1.拉取 Zookeeper 的 Docker 镜像 2.运行 Zookeeper 容器 3.拉取 Kafka 的 Docker 镜像 4.运行 Kafka 容器 5.下载 Kafdrop 6.运行 Kafdrop 7.如果docker pull wurstmeister/zookeeper或docker pull wurstmeister/kafka下载很慢&#x…

C++ 与 Java 的对比分析:除法运算中的错误处理

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: Java 文章目录 &#x1f4af;前言&#x1f4af;C中的除法错误处理&#x1f4af;Java中的除法错误处理&#x1f4af;C与Java错误处理的对比&#x1f4af;错误处理的优化和实践&#x1f4af;小结 &#x1f4af;前言 在…

LLM之循环神经网络(RNN)

在人工智能的领域中&#xff0c;神经网络是推动技术发展的核心力量。今天&#xff0c;让我们深入探讨循环神经网络&#xff08;RNN&#xff09; 一、神经网络基础 &#xff08;1&#xff09;什么是神经网络 神经网络&#xff0c;又称人工神经网络&#xff0c;其设计灵感源于人…

SQL sever数据导入导出实验

1.创建数据库TCP-H &#xff08;1&#xff09;右键“数据库”&#xff0c;点击“新建数据库”即可 &#xff08;2&#xff09;用sql语言创建&#xff0c;此处以创建数据库DB_test为例&#xff0c;代码如下&#xff1a; use master;go--检查在当前服务器系统中的所有数据里面…

让编程变成一种享受-明基RD320U显示器

引言 作为一名有着多年JAVA开发经验的从业者&#xff0c;在工作过程中&#xff0c;显示器的重要性不言而喻。它不仅是我们与代码交互的窗口&#xff0c;更是影响工作效率和体验的关键因素。在多年的编程生涯中&#xff0c;我遇到过各种各样的问题。比如&#xff0c;在进行代码…

计算机网络(涵盖OSI,TCP/IP,交换机,路由器,局域网)

一、网络通信基础 &#xff08;一&#xff09;网络通信的概念 网络通信是指终端设备之间通过计算机网络进行的信息传递与交流。它类似于现实生活中的物品传递过程&#xff1a;数据&#xff08;物品&#xff09;被封装成报文&#xff08;包裹&#xff09;&#xff0c;通过网络…

✨2.快速了解HTML5的标签类型

✨✨HTML5 的标签类型丰富多样&#xff0c;每种类型都有其独特的功能和用途&#xff0c;以下是一些常见的 HTML5 标签类型介绍&#xff1a; &#x1f98b;结构标签 &#x1faad;<html>&#xff1a;它是 HTML 文档的根标签&#xff0c;所有其他标签都包含在这个标签内&am…

eNSP防火墙综合实验

一、实验拓扑 二、ip和安全区域配置 1、防火墙ip和安全区域配置 新建两个安全区域 ip配置 Client1 Client2 电信DNS 百度web-1 联通DNS 百度web-2 R2 R1 三、DNS透明代理相关配置 1、导入运营商地址库 2、新建链路接口 3、配置真实DNS服务器 4、创建虚拟DNS服务器 5、配置D…

Linux 配置交换空间(Swap)解决内存不足

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; GitCode︱ Gitee ︱ Github &#x1f496; 欢迎点赞 &#x1f44d; 收藏 ⭐评论 …

个人shell脚本分享

在周一到周五做增量备份&#xff0c;在周六周日做完全备份 #!/bin/bash定义变量 SRC“/path/to/source” # 源目录 BKUP“/backup” # 备份主目录 FUL“KaTeX parse error: Expected EOF, got # at position 22: …ull" #̲ 完全备份目录 INC"BKUP/inc” # 增量备份…

Django 5 实用指南(一)安装与配置

1.1 Django5的背景与发展 Django 自从2005年由Adrian Holovaty和Simon Willison在 Lawrence Journal-World 新闻网站上首次发布以来&#xff0c;Django 一直是 Web 开发领域最受欢迎的框架之一。Django 框架经历了多个版本的演进&#xff0c;每次版本更新都引入了新功能、改进了…

百度搜索融合 DeepSeek 满血版,开启智能搜索新篇

百度搜索融合 DeepSeek 满血版&#xff0c;开启智能搜索新篇 &#x1f680; &#x1f539; 一、百度搜索全量接入 DeepSeek &#x1f539; 百度搜索迎来重要升级&#xff0c;DeepSeek 满血版全面上线&#xff01;&#x1f389; 用户在百度 APP 搜索后&#xff0c;点击「AI」即…

RabbitMQ服务异步通信

消息队列在使用过程中&#xff0c;面临着很多实际问题需要思考&#xff1a; 1. 消息可靠性 消息从发送&#xff0c;到消费者接收&#xff0c;会经理多个过程&#xff1a; 其中的每一步都可能导致消息丢失&#xff0c;常见的丢失原因包括&#xff1a; 发送时丢失&#xff1a; 生…

【教程】MySQL数据库学习笔记(七)——多表操作(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 第四章 《数据操…

胶囊网络动态路由算法:突破CNN空间局限性的数学原理与工程实践

一、CNN的空间局限性痛点解析 传统CNN的瓶颈&#xff1a; 池化操作导致空间信息丢失&#xff08;最大池化丢弃85%激活值&#xff09;无法建模层次空间关系&#xff08;旋转/平移等变换不敏感&#xff09;局部感受野限制全局特征整合 示例对比&#xff1a; # CNN最大池化示例…

#渗透测试#批量漏洞挖掘#Apache Log4j反序列化命令执行漏洞

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 Apache Log4j反序列化命令执行漏洞 一、…

深入剖析Spring MVC

一、Spring MVC 概述 1. 什么是 Spring MVC&#xff1f; Spring MVC 是基于 Spring 框架的 Web 框架&#xff0c;它实现了 MVC 设计模式&#xff0c;将应用程序分为三个核心部分&#xff1a; Model&#xff1a;封装应用程序的数据和业务逻辑。 View&#xff1a;负责渲染数据…

【AI】mac 本地部署 Dify 实现智能体

下载 Ollama 访问 Ollama 下载页&#xff0c;下载对应系统 Ollama 客户端。或者参考文章【实战AI】macbook M1 本地ollama运行deepseek_m1 max可以跑deepseek吗-CSDN博客 dify 开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力&am…