【项目教程】FFmpeg+SDL2实现视频播放器

news2025/1/10 12:58:12

一、前言

学习ffmpeg和sdl,并编写一个视频播放器,是一个很好的音视频开发项目。

虽然关于视频播放器的原理已经有很多人在博客中进行了讲解,但是很多人不提供视频和代码,这也是我写这篇博客的主要原因。

二、在视频播放器中,主要涉及以下几个基本原理:


2.1 视频文件解封装

视频文件通常将音频和视频数据进行封装,因此在处理视频文件时,首先需要进行解封装操作,将视频流和音频流的压缩编码数据分离。常见的封装格式有MP4、MKV、FLV、AVI、RMVB、TS等。例如,解封装FLV格式的文件可能会得到H.264编码的视频流和AAC编码的音频流。

在FFMPEG中,解封装的过程如下所示:

这一步最重要的是得到解封装器的上下文结构体"AVFormatContext *m_pFormatCtx", 以及接下来我们要解码的音视频流索引。

2.2 音视频解码

原始数据通常经过压缩编码,解码过程则是将H.264、AAC等压缩后的数据解码为非压缩的音频/视频原始数据,其中视频一般为YUV或RGB数据,音频一般为PCM采样数据。

解码的步骤如下:

2.3 使用SDL2播放视频数据

我们知道视频是由连续的一帧帧图像快速播放形成的动态效果,一般设置为每秒播放25帧图像。

在播放视频时,我们使用SDL2库。每个图像在SDL2中被表示为一个纹理,而纹理与SDL2的渲染器相关联。

在视频解码后,我们可以从avcodec_receive_frame函数中获取到一个AVFrame对象,该对象包含了一帧视频数据。我们的目标是将这一帧的数据渲染到SDL的渲染器中。总体流程如下所示:

首先,我们需要使用sws_scale函数对获取到的AVFrame数据进行大小和格式的转换。接下来,我们需要更新SDL2中的纹理(Texture)和渲染器(Render)。以下是相关的关键代码示例:

AVFrame *frame = m_videoFrameQueue.front();
m_videoFrameQueue.pop();

AVFrame *frameYUV = av_frame_alloc();
int ret = av_image_alloc(frameYUV->data, frameYUV->linesize, m_sdlRect.w, m_sdlRect.h, AV_PIX_FMT_YUV420P, 1);
//Convert image
if (m_imgConvertCtx)
{
sws_scale(m_imgConvertCtx, frame->data, frame->linesize, 0, m_videoCodecParams.height, frameYUV->data, frameYUV->linesize);
SDL_UpdateYUVTexture(m_sdlTexture, NULL, frameYUV->data[0], frameYUV->linesize[0], frameYUV->data[1], frameYUV->linesize[1], frameYUV->data[2], frameYUV->linesize[2]);
SDL_RenderClear(m_sdlRender);
SDL_RenderCopy(m_sdlRender, m_sdlTexture, NULL, &m_sdlRect);

// Present picture
SDL_RenderPresent(m_sdlRender);
}

2.4 使用SDL2播放音频数据

对于音频数据,在使用avcodec_receive_frame函数接收到AVFrame后,我们得到的是音频的PCM数据。

与视频数据不同,音频数据并不是以帧为单位表示的,它可能包含多个采样数据(samples)。 为了播放音频,我们同样需要对音频数据进行格式转换,以适应音频设备的播放要求。音频格式转换主要通过swr_convert函数来完成。转换后的音频数据可以存放在一个公共缓冲区中。

使用SDL_OpenAudio函数进行音频播放,该函数需要传入一个SDL_AudioSpec结构体来设置播放参数。其中需要设置一个回调函数(callback),用于在音频设备需要获取数据时执行。因此,我们需要在此回调函数中向音频设备提供数据,实现数据的"喂养":

SDL_AudioSpec m_sdlAudioSpec;
auto audioCtx = m_audioDecoder.GetCodecContext();

m_sdlAudioSpec.freq = audioCtx->sample_rate; //根据你录制的PCM采样率决定
m_sdlAudioSpec.format = AUDIO_S16SYS;
m_sdlAudioSpec.channels = audioCtx->channels;
m_sdlAudioSpec.silence = 0;
m_sdlAudioSpec.samples = SDL_AUDIO_BUFFER_SIZE;
m_sdlAudioSpec.callback = &SDLVideoPlayer::ReadAudioData;
m_sdlAudioSpec.userdata = NULL;

int re = SDL_OpenAudio(&m_sdlAudioSpec, NULL);
if (re < 0)
{
    std::cout << "can't open audio: " << GetErrorInfo(re);
}
else
{
    //Start play audio
    SDL_PauseAudio(0);
}

void SDLVideoPlayer::ReadAudioData(void *udata, Uint8 *stream, int len) {
	SDL_memset(stream, 0, len);
    //需要向stream中填充len长度的音频数据
    ...
    SDL_MixAudio(stream, m_audioPcmDataBuf, len, g_volum);
}

2.5 音视频同步的设计

为了实现音视频同步,我们使用了两个线程分别播放音频和视频。音频可以直接通过设置回调函数来传递数据,而视频则需要我们自己控制播放速度,这涉及到统一两者播放速度的问题。 音视频同步的基本方式是确定一个主时钟作为同步基准。在播放过程中,我们不断检查当前流的播放时间与主时钟的差异,以调节自身的播放速度。根据不同类型的主时钟,可以分为以下几种方式:

  1. 音频同步到视频:使用视频时钟作为主时钟。
  2. 视频同步到音频:使用音频时钟作为主时钟。
  3. 音视频都同步到外部时钟。 由于音频播放通常会将大量数据发送到设备缓存中,并且音频对人的敏感度更高,因此以音频时钟作为主时钟是比较合理且简单的方法。具体实现如下:
  4. 在每次传递音频数据时,记录送入数据的起始pts时间戳,表示当前音频的播放进度。
  5. 每次刷新视频帧时,记录当前图片帧的pts时间戳。
  6. 在记录当前音频pts的同时,根据记录的图片pts,计算两者之间的延迟。
  7. 在刷新视频帧时,根据延迟值判断,如果当前视频比音频快,那么调整视频等待时间为正常两帧之间的间隔加上音视频之间的延迟,并将延迟值置为0;如果音频比视频快,那么直接丢弃当前的视频帧,直到音频和视频时间一致。

2.6 快进和快退

快进和快退,以及通过拖动进度条来实现播放跳转,其实现思路都是一样的,即通过使用av_seek_frame函数来实现:

 av_seek_frame(m_pFormatCtx, -1, pts * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);

因此关键就是获取要跳转的时间戳,这个在做音视频同步处理后,这个时间戳就很容易拿到。

2.7 SDL事件处理

对于窗口大小更改、暂停、快进快退等操作,都需要与用户进行交互,而这可以通过SDL的事件机制来实现。 监听事件:

SDL_Event event;
SDL_WaitEvent(&event);
if (event.type == SDL_WINDOWEVENT) {
	...
}
...

除了预定义的事件,比如窗口事件、鼠标事件、按键事件等,你也可以自己触发或定义新的事件:

SDL_Event event;
event.type = SFM_REFRESH_PIC_EVENT;
SDL_PushEvent(&event);

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

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

相关文章

『哈哥赠书 - 53期』-『深入浅出 Spring Boot 3.x』

⭐️ 《深入浅出 Spring Boot 3.x》 ⭐️ 学习Spring Boot的必读之书 在 Java 后端开发领域&#xff0c;功能强大的 Spring 开源框架不仅是首选&#xff0c;也是事实上的标准。但由于 Spring 存在配置烦琐、部署不易、依赖管理困难等问题&#xff0c;因此基于 Spring 的快速开…

进程互斥经典问题(读写者问题、理发店问题)

目录 读写者问题 问题描述 问题分析 进程互斥问题三部曲 读者写者算法实现 一、找进程——确定进程关系 二、找主营业务 三、找同步约束 a.互斥 b.资源 c.配额 理发店问题 问题描述 问题分析 进程互斥问题三部曲 理发店问题算法实现 一、找进程——确定进程…

微信小程序毕业设计-校车购票系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

高中生是否需要上电子阅览室

高中生是否需要上电子阅览室&#xff0c;取决于学生的学习需求和个人喜好。以下是一些考虑因素&#xff1a; 1. 便利性&#xff1a;电子阅览室通常提供电脑设备和网络连接&#xff0c;方便学生在线获取学习资源。对于家中没有电脑或者网络不稳定的学生&#xff0c;上电子阅览室…

“一带一路”六国国际拳王冠军赛特克斯站新闻发布会顺利举行

实习记者&#xff1a;喀兰姆罕 5月24日&#xff0c;“一带一路”六国国际拳王冠军赛特克斯站新闻发布会在特克斯县阿克塔斯姑娘峰景区举行。这次拳王冠军赛事由新疆广播电视台、特克斯镇人民政府&#xff0c;特克斯县文化体育广播电视和旅游局主办&#xff0c;将于6月15日在特…

mysql 01 linux 上安装mysql服务端

01.linux安装 MySQL的大部分安装包都包含了服务器程序和客户端程序&#xff0c;不过在Linux下使用RPM包时会有单独的服 务器RPM包和客户端RPM包&#xff0c;需要分别安装。 1.查看是否已经安装了MySQL rpm -qa | grep mysql如果什么都没有&#xff0c;就是还没有装过MySQL …

分支机构多,如何确保文件跨域传输安全可控?

随着企业全球化发展&#xff0c;分支机构的分布越来越广泛&#xff0c;跨域文件传输需求也随之增加。然而&#xff0c;跨域文件传输面临的数据安全和传输效率问题&#xff0c;使得构建一个安全、可控的文件交换系统成为迫切需求。FileLink跨网文件交换系统通过综合的技术手段和…

Jenkins常用插件与应用详解

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Jenkins是一个平台我们通过安装插件来解决我们想要完成的任务 1、Jenkins常用插件 Allure&#…

香橙派 AIpro初体验

香橙派&#xff08;Orange Pi&#xff09;AI Pro开发板是一款高性能的AI开发板&#xff0c;由香橙派联合华为精心打造。香橙派&#xff08;Orange Pi&#xff09;&#xff0c;作为深圳市迅龙软件有限公司倾力打造的开源产品品牌&#xff0c;致力于向全球个人及企业用户提供卓越…

QT学习(20):QStyle和自定义样式

QStyle 样式&#xff08;继承自QStyle类&#xff09;代表控件的绘制并封装GUI的外观。QStyle是一个封装了GUI外观的抽象基类。Qt使用QStyle去执行几乎所有的内置控件的绘制&#xff0c;确保控件外观和原生控件风格风格相同。 class Q_WIDGETS_EXPORT QStyle : public QObject{…

HBase分布式数据库入门到精通

文章目录 HBase分布式数据库入门到精通 一、简单介绍 二、HBase数据模型 三、HBase的架构 四、HBase写操作流程 五、HBase读操作流程 六、HBase minor小合并和major大合并 七、HBase目标表meta表 八、HBase特点 九、HBase的使用场景 HBase分布式数据库入门到精通 一、…

stream-流的效率

背景 使用流批量处理数据的时候&#xff0c;我们最关注的肯定是效率问题数据批量处理分为4类 原始的for循环处理基本类型基本类型包装流原始的for循环处理包装类型原始的依稀那个流Stream.of(1,2,3,4) 对比&#xff08;单线程&#xff09; 任何是时候&#xff0c;原始的for循环…

go select

select 是与 switch 相似的控制结构&#xff0c;与 switch 不同的是&#xff0c;select 中虽然也有多个 case&#xff0c;但是这些 case 中的表达式必须都是 channel 的收发操作。 select 能够让 goroutine 同时等待多个 channel 可读或者可写&#xff0c;在多个 channel 状态改…

Swift 属性

属性 一、存储属性1、常量结构体实例的存储属性2、延时加载存储属性3、存储属性和实例变量 二、计算属性1、简化 Setter 声明2、简化 Getter 声明3、只读计算属性 三、属性观察器四、属性包装器1、设置被包装属性的初始值2、从属性包装器中呈现一个值 五、全局变量和局部变量六…

离大模型落地应用最近的工程化技术(RAG)

虽然大规模语言模型&#xff08;LLM&#xff09;在自然语言处理&#xff08;NLP&#xff09;方面表现出了其强大的文本生成和理解能力&#xff0c;但是它们在实际应用中仍然面临一些挑战&#xff0c;如处理大规模知识库和实时获取最新信息的能力&#xff0c;并且会产生幻觉。为…

新书速览|Golang+Vue.js商城项目实战

架构师一步一步教你做项目&#xff0c;从架构设计到技术实现完整解析 本书内容 《GolangVue.js商城项目实战》以Gin和Vue.js为核心框架&#xff0c;以全栈商城项目开发为主线&#xff0c;详尽介绍前后端分离架构开发Web网站项目的关键阶段和技术细节。全书共9章&#xff0c;第…

四川景源畅信:新人做抖店的成本很高吗?

随着社交媒体的兴起&#xff0c;抖音成为了一个新兴的电商平台——抖店。不少创业者和商家看中了其庞大的用户基础&#xff0c;想要通过开设抖店来拓展销路。然而&#xff0c;对于刚入行的新手来说&#xff0c;成本问题总是让人犹豫不决。究竟新人做抖店的成本高不高?本文将围…

Qt项目使用pato mqtt C

一,下载pato mqtt C 源码 git 地址:https://github.com/eclipse/paho.mqtt.c.git git 地址可能下载不下来,提供我的gitee地址 gitee地址:https://gitee.com/chaojidahuaidan2021/paho.mqtt.c.git 二,编译共享库 clone下来后,将项目导入到Qt工程中,此时这是一个cmke工程…

java.lang.NumberFormatException: For input string:

创建SpringBoot&#xff0c;Mybatis的项目时候&#xff0c;Service层调用Mapper层时候爆出了一个错误 发现报错是一个类型转换错误&#xff0c;经过排查后发现是因为mapper接收的实体类中没有写空参构造

Debug-011-ES6中的链判断运算符(?.)

这个问题的来源是&#xff1a; 前端在请求后端接口&#xff0c;拿到的数据&#xff0c;有可能是这样的&#xff1a; data:{a:{b:{c:{d:""}}} } 我们前端小伙伴联调时需要取到d的值&#xff0c;我以前是这样写的&#xff1a; // 错误的写法 const value data.a.b.c…