基于FFmpeg 6.x与SDL2的音视频播放器开发全解析

多媒体播放器系列


基于FFmpeg 6.x与SDL2的音视频播放器开发全解析

  • 多媒体播放器系列
  • 基于FFmpeg 6.x与SDL2的音视频播放器开发全解析
    • 一、引言
    • 二、FFmpeg 6.x基础理论
      • (一)FFmpeg概述
      • (二)重要的数据结构
      • (三)音视频解码流程
    • 三、SDL2基础理论
      • (一)SDL2概述
      • (二)重要的模块与数据结构
      • (三)视频渲染与音频播放流程
    • 四、基于FFmpeg 6.x和SDL2的音视频播放器代码实践分析
      • (一)整体代码结构概述
      • (二)FFmpeg相关代码详细分析
      • (三)SDL2相关代码详细分析
      • (四)资源清理与程序退出
    • 五、完整代码
    • 六、优化与扩展方向
      • (一)性能优化
      • (二)功能扩展
    • 七、总结


基于FFmpeg 6.x与SDL2的音视频播放器开发全解析

一、引言

在当今数字化时代,音视频播放已经成为我们日常生活中不可或缺的一部分,无论是观看电影、收听音乐还是浏览各种多媒体内容,都离不开高效可靠的音视频播放器。而开发一个功能完善的音视频播放器,涉及到诸多复杂的技术和原理,其中FFmpeg和SDL2是两个非常关键的开源库。FFmpeg提供了强大的音视频编解码以及格式处理能力,SDL2则专注于跨平台的多媒体显示与音频输出等功能。本文将深入探讨如何基于FFmpeg 6.x和SDL2来开发一个音视频播放器,从相关理论基础到具体的代码实践,为读者呈现一个全面的开发指南。

二、FFmpeg 6.x基础理论

(一)FFmpeg概述

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了丰富的命令行工具以及库函数,能够处理几乎所有常见的音视频格式,涵盖了编码、解码、复用、解复用、滤镜处理等多个方面的功能。在我们开发音视频播放器的过程中,主要利用其强大的解码能力,将各种格式的音视频文件中的原始数据提取出来,以便后续的播放处理。

(二)重要的数据结构

  1. AVFormatContext
    这是FFmpeg中用于描述音视频文件格式上下文的结构体,它包含了关于输入或输出文件的全局信息,比如文件头信息、音视频流信息等。在打开一个音视频文件时,首先要初始化这个结构体,通过avformat_open_input函数来打开文件并填充相关的格式信息,之后再使用avformat_find_stream_info函数进一步获取流的详细信息,例如帧率、分辨率、采样率等。
  2. AVCodecContext
    对应着编解码器的上下文,它保存了特定音视频流在编码或解码过程中所需要的各种参数,比如视频的像素格式、宽高,音频的声道布局、采样格式、采样率等。在找到对应的音视频流后,需要从流的codecpar参数中提取信息并填充到AVCodecContext结构体中,再通过avcodec_open2函数打开对应的编解码器,使其准备好进行编解码操作。
  3. AVCodec
    代表具体的编解码器,FFmpeg中有大量内置的编解码器,通过avcodec_find_decoder函数可以根据给定的编解码器ID来查找对应的解码器,比如常见的H.264、AAC等格式对应的解码器都可以通过这种方式获取。
  4. AVPacket
    用于存储音视频数据的数据包,它是从文件中读取出来的原始数据单元,在解码过程中,会将从文件读取到的AVPacket发送给对应的解码器进行处理。一个AVPacket可能包含完整的一帧数据,也可能是一帧数据的一部分,这取决于具体的文件格式和编码方式。
  5. AVFrame
    代表经过解码后的一帧音视频数据,对于视频来说,它包含了像素数据(如不同平面的YUV数据等)以及相关的帧属性;对于音频来说,则包含了采样数据以及声道等信息。解码器将AVPacket解码后输出AVFrame,然后我们可以进一步对这些帧数据进行处理,比如视频的渲染、音频的播放等。

(三)音视频解码流程

  1. 打开文件并获取格式信息
    首先使用avformat_open_input函数打开指定的音视频文件,传入一个AVFormatContext指针,该函数会尝试解析文件头,获取基本的文件格式信息。如果打开成功,接着使用avformat_find_stream_info函数来进一步查找流信息,这个过程可能会涉及到读取文件中的部分数据来分析音视频流的详细参数,例如查找视频流的分辨率、帧率以及音频流的采样率、声道数等。
  2. 查找音视频流对应的解码器
    遍历AVFormatContext中的各个流,通过判断流的codecpar->codec_type属性(可以是AVMEDIA_TYPE_VIDEO表示视频流或者AVMEDIA_TYPE_AUDIO表示音频流)来确定视频流和音频流的索引,并使用avcodec_find_decoder函数根据流的编解码器ID查找对应的解码器,分别保存到video_codecaudio_codec变量中(在代码示例中就是这样的操作流程)。
  3. 初始化编解码器上下文并打开解码器
    对于找到的视频流和音频流,分别创建AVCodecContext结构体,通过avcodec_parameters_to_context函数将流中的参数信息复制到对应的编解码器上下文中,然后使用avcodec_open2函数打开解码器,使它们准备好接收数据包进行解码操作。
  4. 读取数据包并解码
    在主循环中,通过av_read_frame函数从文件中不断读取AVPacket数据包,根据数据包的stream_index判断是属于视频流还是音频流,然后分别将对应的数据包发送给相应的解码器(使用avcodec_send_packet函数),解码器接收到数据包后进行解码,并通过avcodec_receive_frame函数获取解码后的AVFrame数据帧,后续就可以对这些帧数据进行处理,比如视频渲染和音频播放等操作。

三、SDL2基础理论

(一)SDL2概述

SDL2(Simple DirectMedia Layer 2)是一个跨平台的多媒体开发库,它提供了统一的接口来访问音频、键盘、鼠标、游戏控制器等多种硬件设备,并且能方便地创建窗口、渲染图形以及播放音频等。在我们的音视频播放器开发中,主要利用它来创建显示视频的窗口、进行视频帧的渲染以及实现音频的输出播放功能,使得我们的播放器能够在不同的操作系统平台上具有一致的表现。

(二)重要的模块与数据结构

  1. 窗口与渲染相关
    • SDL_Window:用于创建一个显示窗口,通过SDL_CreateWindow函数可以指定窗口的标题、位置、大小以及显示模式等参数来创建一个窗口实例,比如在代码示例中创建了一个名为"Video Player"的窗口,其大小根据视频的分辨率来设置。
    • SDL_Renderer:与SDL_Window关联,负责在窗口上进行图形渲染,通过SDL_CreateRenderer函数创建,它可以选择不同的渲染驱动模式(如加速模式等)来提高渲染效率,在示例中使用了SDL_RENDERER_ACCELERATED模式来加速视频帧的渲染操作。
    • SDL_Texture:用于存储图像数据,在视频播放场景下,它用来保存从FFmpeg解码得到的视频帧数据,通过SDL_CreateTexture函数创建,并且指定纹理的像素格式、访问模式以及宽高尺寸等参数,与视频的AVCodecContext中的相关参数对应,以便后续将解码后的视频帧数据更新到纹理上进行渲染。
  2. 音频相关
    • SDL_AudioSpec:描述音频的参数规范,包括音频的采样频率(如常见的44100Hz等)、音频格式(如AUDIO_S16SYS表示16位有符号整数格式等)、声道数(单声道或立体声等)、音频缓冲区大小(以样本数量为单位,如示例中的1024个样本)以及音频播放的回调函数等。通过设置这个结构体的各项参数,并使用SDL_OpenAudio函数打开音频设备,就可以按照指定的参数进行音频播放了。
    • 音频回调函数:在SDL_AudioSpec结构体中指定的回调函数是音频播放的关键,它会在音频设备需要更多音频数据来播放时被自动调用。在回调函数中,需要将准备好的音频数据填充到给定的音频缓冲区中,示例中的audio_callback函数就是按照这样的逻辑来实现的,它从预先准备好的音频数据缓冲区中复制数据到SDL提供的音频输出缓冲区中,如果数据不足则填充静音数据,以保证音频播放的连续性。

(三)视频渲染与音频播放流程

  1. 视频渲染流程
    在获取到解码后的视频AVFrame数据后,首先需要通过SwsContext(用于视频像素格式转换等操作)将视频帧的像素数据转换为适合渲染的格式(在示例中是转换为AV_PIX_FMT_YUV420P格式),然后使用SDL_UpdateYUVTexture函数将转换后的视频帧数据更新到SDL_Texture纹理中,接着通过SDL_RenderClear函数清除渲染器上之前的内容,再使用SDL_RenderCopy函数将纹理复制到渲染器上,最后通过SDL_RenderPresent函数将渲染后的内容显示到窗口上,这样就完成了一帧视频的渲染显示,不断重复这个过程就能实现视频的流畅播放。
  2. 音频播放流程
    首先根据音频流的参数以及目标播放设备的要求,配置好SDL_AudioSpec结构体,设置好采样率、格式、声道数等参数,并指定音频播放的回调函数。然后通过SDL_OpenAudio函数打开音频设备,启动音频播放(通过SDL_PauseAudio(0)取消音频暂停状态)。在解码音频数据时,将解码后的音频帧数据经过重采样(使用SwrContext进行音频重采样,将音频数据转换为目标采样率、采样格式等)后,填充到音频数据缓冲区中,在音频回调函数被调用时,从这个缓冲区中取出数据提供给音频设备进行播放,并且要注意处理好数据缓冲区的管理,保证音频播放的连贯性和正确性。

四、基于FFmpeg 6.x和SDL2的音视频播放器代码实践分析

(一)整体代码结构概述

整个代码示例是一个C++程序(虽然包含了部分C风格的extern "C"声明来调用FFmpeg的C函数库),其主要功能是实现一个简单的音视频播放器,能够打开指定的音视频文件(示例中指定了F:/QT/mp4_flv/x.mp4文件路径),并对其中的视频流和音频流进行解码、处理以及同步播放。代码整体按照功能模块可以大致分为以下几个部分:

  1. 头文件包含与FFmpeg、SDL2函数声明导入:引入了必要的C++标准库头文件(如<iostream><string>等)以及SDL2和FFmpeg相关的头文件,通过extern "C"声明使得可以在C++代码中调用FFmpeg的C函数接口,导入了诸如音视频编解码、格式处理、重采样等相关的函数声明,为后续的操作提供了函数支持。
  2. 错误处理函数定义:定义了handle_ffmpeg_error函数,用于在FFmpeg相关函数调用出现错误时,将错误码转换为可读的错误信息并输出到标准错误输出流,方便调试和排查问题。
  3. 音频数据结构体与音频回调函数定义:定义了AudioData结构体来存储音频缓冲区相关的数据(如缓冲区指针、缓冲区大小、当前缓冲区数据位置等),同时定义了audio_callback函数作为音频播放的回调函数,该函数负责在音频设备需要数据时将准备好的音频数据提供给音频设备,或者在没有足够音频数据时填充静音数据。
  4. 主函数部分:这是整个程序的核心逻辑所在,按照顺序依次完成了FFmpeg的初始化与音视频流相关参数获取、SDL2的初始化与视频音频播放相关设置、主循环中的音视频数据读取、解码、处理以及播放操作,最后在程序结束时进行了资源的释放和清理工作。

(二)FFmpeg相关代码详细分析

  1. 初始化与文件打开
    • main函数开头,首先定义了AVFormatContext* fmt_ctx = NULL,用于存储音视频文件的格式上下文信息。然后通过avformat_open_input函数尝试打开指定的文件路径(file_path变量指定的路径),如果返回值小于0,则表示打开文件失败,会输出错误信息并直接返回程序。接着使用avformat_find_stream_info函数进一步查找文件中流的详细信息,同样如果返回值小于0则说明获取流信息失败,会关闭已打开的文件输入并返回。这两步操作是整个音视频处理的基础,只有成功打开文件并获取到流信息后,才能继续后续的解码等操作。
    • 例如代码中的这部分:
if (avformat_open_input(&fmt_ctx, file_path.c_str(), NULL, NULL) < 0) {
   
    fprintf(stderr, "Could not open file.\n");
    return -1;
}

if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
   
    fprintf(stderr, "Could not find stream info.\n");
    avformat_close_input(&fmt_ctx);
    return -1;
}
  1. 音视频流查找与解码器初始化
    • 接下来通过遍历fmt_ctx->nb_streams个流,根据流的codecpar->codec_type属性来区分视频流和音频流,分别找到对应的流索引(video_stream_idxaudio_stream_idx),并使用avcodec_find_decoder函数查找对应的解码器(video_codecaudio_codec)。如果没有找到视频流或者对应的视频解码器,或者没有找到音频流或者对应的音频解码器,都会输出相应的错误信息并进行资源清理后返回程序。
    • 以下是相关代码片段展示:
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
   
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_stream_idx == -1) {
   
        video_stream_idx = i;
        video_codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);
    }
    else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream_idx == -1) {
   
        audio_stream_idx = i;
        audio_codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id);
    }
}

if (video_stream_idx == -1 ||!video_codec) {
   
    fprintf(stderr, "Video stream not found.\n");
    avformat_close_input(&fmt_ctx);
    return -1;
}

if (audio_stream_idx == -1 ||!audio_codec) {
   
    fprintf(stderr, "Audio stream not found.\n");
    avformat_close_input(&fmt_ctx);
    return -1;
}
- 在找到对应的解码器后,分别为视频流和音频流创建`AVCodecContext`结构体,通过`avcodec_parameters_to_context`函数将流中的参数复制到编解码器上下文中,再使用`avcodec_open2`函数打开解码器,使它们能够进行后续的解码操作。例如视频编解码器上下文的初始化代码如下:
AVCodecContext* video_ctx = avcodec_alloc_context3(video_codec);
avcodec_parameters_to_context(video_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);
avcodec_open2(video_ctx, video_codec, NULL);

音频编解码器上下文的初始化代码与之类似,只是使用的是audio_stream_idxaudio_codec等相关变量。

  1. 主循环中的音视频解码操作
    • 在主循环中(while (av_read_frame(fmt_ctx, &pkt) >= 0)),不断通过av_read_frame函数从文件中读取AVPacket数据包,然后根据数据包的stream_index判断是视频流还是音频流,分别进行不同的处理。
    • 对于视频流数据包(pkt.stream_index == video_stream_idx),首先使用avcodec_send_packet函数将数据包发送给视频解码器,如果发送成功,接着通过avcodec_receive_frame函数接收解码后的视频AVFrame数据,一旦获取到有效的视频帧,就进行视频渲染相关的操作。具体的视频渲染操作会在后续结合SDL2的部分详细介绍,但这里核心就是将从FFmpeg解码得到的视频帧数据传递到SDL2的渲染流程中去。
    • 对于音频流数据包(pkt.stream_index == audio_stream_idx),同样先使用avcodec_send_packet函数发送给音频解码器,

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ASP.NET学习】ASP.NET MVC基本编程

文章目录 ASP.NET MVCMVC 编程模式ASP.NET MVC - Internet 应用程序创建MVC web应用程序应用程序信息应用程序文件配置文件 用新建的ASP.NET MVC程序做一个简单计算器1. **修改视图文件**2. **修改控制器文件** 用新建的ASP.NET MVC程序做一个复杂计算器1.创建模型&#xff08;…

蓝桥云客第 5 场 算法季度赛

题目&#xff1a; 2.开赛主题曲【算法赛】 - 蓝桥云课 问题描述 蓝桥杯组委会创作了一首气势磅礴的开赛主题曲&#xff0c;其歌词可用一个仅包含小写字母的字符串 S 表示。S 中的每个字符对应一个音高&#xff0c;音高由字母表顺序决定&#xff1a;a1,b2,...,z26。字母越靠后…

计算机网络 (37)TCP的流量控制

前言 计算机网络中的TCP&#xff08;传输控制协议&#xff09;流量控制是一种重要机制&#xff0c;用于确保数据在发送方和接收方之间的传输既高效又稳定。 一、目的 TCP流量控制的主要目的是防止发送方发送数据过快&#xff0c;导致接收方无法及时处理&#xff0c;从而引起数据…

【Elasticsearch7.11】postman批量导入少量数据

JSON 文件内的数据格式&#xff0c;json文件数据条数不要过多&#xff0c;会请求参数过大&#xff0c;最好控制再10000以内。 {"index":{"_id":"baec07466732902d22a24ba01ff09751"}} {"uuid":"baec07466732902d22a24ba01ff0975…

Spring Boot 支持哪些日志框架

Spring Boot 支持多种日志框架&#xff0c;主要包括以下几种&#xff1a; SLF4J (Simple Logging Facade for Java) Logback&#xff08;默认&#xff09;Log4j 2Java Util Logging (JUL) 其中&#xff0c;Spring Boot 默认使用 SLF4J 和 Logback 作为日志框架。如果你需要使…

AIDD - 人工智能药物设计 -深度学习赋能脂质纳米颗粒设计,实现高效肺部基因递送

Nat. Biotechnol. | 深度学习赋能脂质纳米颗粒设计&#xff0c;实现高效肺部基因递送 今天为大家介绍的是来自美国麻省理工和爱荷华大学卡弗医学院团队的一篇论文。可离子化脂质&#xff08;ionizable lipids&#xff09;是脂质纳米颗粒&#xff08;lipid nanoparticles&#…

【SVN】版本发布快捷操作

摘要&#xff1a;因为每次发版都需要制作一份相同的文件夹&#xff0c;而大部分的包都不需要变更&#xff0c;但是文件又非常大&#xff0c;记录自己的操作经验。 首先在SVN Repository Browser 界面把上一次的版本复制一份&#xff0c;复制的时候重命名为新的版本号 右击要复…

AR 眼镜之-拍照/录像动效切换-实现方案

目录 &#x1f4c2; 前言 AR 眼镜系统版本 拍照/录像动效切换 1. &#x1f531; 技术方案 1.1 技术方案概述 1.2 实现方案 1&#xff09;第一阶段动效 2&#xff09;第二阶段动效 2. &#x1f4a0; 默认代码配置 2.1 XML 初始布局 2.2 监听滑动对 View 改变 3. ⚛️…

HTML5实现好看的端午节网页源码

HTML5实现好看的端午节网页源码 前言一、设计来源1.1 网站首页界面1.2 登录注册界面1.3 端午节由来界面1.4 端午节习俗界面1.5 端午节文化界面1.6 端午节美食界面1.7 端午节故事界面1.8 端午节民谣界面1.9 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 H…

Android使用系统消息与定时器实现霓虹灯效果

演示效果: 界面设计: 在帧布局FrameLayout中添加6个TextView 依次设置这6个TextView的宽&#xff0c;高&#xff0c;权重 也可在XML中直接设置 添加自定义颜色 关联自定义颜色到数组变量 关联6个TextView控件到数组变量 处理自定义系统消息 Handler _sysHandler new Han…

多活架构的实现原理与应用场景解析

一、多活架构为何如此重要? 企业的业务运营与各类线上服务紧密相连,从日常的购物消费、社交娱乐,到金融交易、在线教育等关键领域,无一不依赖于稳定可靠的信息系统。多活架构的重要性愈发凸显,它宛如一位忠诚的卫士,为业务的平稳运行保驾护航。 回想那些因系统故障引发的…

【JVM-2.2】使用JConsole监控和管理Java应用程序:从入门到精通

在Java应用程序的开发和运维过程中&#xff0c;监控和管理应用程序的性能和资源使用情况是非常重要的。JConsole是Java Development Kit&#xff08;JDK&#xff09;自带的一款图形化监控工具&#xff0c;它可以帮助开发者实时监控Java应用程序的内存、线程、类加载以及垃圾回收…

《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识

目录 2.1 几何学 向量的内积和外积 旋转矩阵 旋转向量 四元数 李群和李代数 SO(3)上的 BCH 线性近似式 2.2 运动学 李群视角下的运动学 SO(3) t 上的运动学 线速度和加速度 扰动模型和雅可比矩阵 典型算例&#xff1a;对向量进行旋转 典型算例&#xff1a;旋转的复合 2.3 …

如何使用高性能内存数据库Redis

一、详细介绍 1.1、Redis概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。Redis支持多种类型的数据结构&#xff0c;如字符串&#xff08;strings&#xff09;、哈希&am…

C++ vtordisp的应用场景

文章目录 问题代码1. 基本概念回顾2. 应用场景虚继承与虚函数并存的类层次结构 3. 编译器相关考虑 问题代码 #include <iostream> using namespace std;class base { public:base() {}virtual void show() { cout << "base:: show"<<endl; } priv…

数据安全与隐私:Facebook在技术创新中的新挑战

在数字化高速发展的今天&#xff0c;数据安全与隐私保护成为社会关注的核心议题之一。作为全球最大的社交媒体平台之一&#xff0c;Facebook&#xff08;现为Meta&#xff09;在技术创新和用户体验优化的同时&#xff0c;也面临着前所未有的数据安全挑战。​ 技术创新中的数据…

SQL从入门到实战-2

高级语句 窗口函数 排序窗口函数 例题二十九 select yr,party,votes, rank() over (PARTITION BY yr ORDER BY votes desc) as pson from ge where constituency S14000021 order by party,yr 偏移分析函数 例题三十 select name,date_format(whn,%Y-%m-%d) data, confi…

爬虫基础之爬取歌曲宝歌曲批量下载

声明&#xff1a;本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…

django基于Python对西安市旅游景点的分析与研究

基于Django框架和Python语言对西安市旅游景点进行的分析与研究&#xff0c;是一个结合现代Web技术和数据分析能力的综合性项目。 一、项目背景与意义 随着旅游业的快速发展&#xff0c;对旅游景点的深入分析和研究变得越来越重要。西安市作为中国历史文化名城&#xff0c;拥有…

spring boot 集成 knife4j

1、knife4j介绍以及环境介绍 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!其底层是对Springfox的封装&#xff0c;使用方式也和Springfox一致&#xff0c;只是对接口…