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

基于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函数发送给音频解码器,

http://www.kler.cn/a/500551.html

相关文章:

  • 3298. 统计重新排列后包含另一个字符串的子字符串数目 II
  • 相加交互效应函数发布—适用于逻辑回归、cox回归、glmm模型、gee模型
  • 数字孪生可视化在各个行业的应用场景
  • 数据结构·二叉树(2)
  • 【RabbitMQ】消息队列消息确认机制
  • 绘制三角形、正六边形、五角星、六角星
  • USRP X310 Windows 烧录镜像
  • 【C++】拷贝构造函数与运算符重载
  • 【Bluedroid】HFP连接流程源码分析(一)
  • 基于ResNet的CIFAR-10分类实现与分析
  • django基于Python的校园个人闲置物品换购平台
  • Android Framework WMS全面概述和知识要点
  • 浅谈云计算03 | 云计算的技术支撑(云使能技术)
  • 基于华为ENSP的OSPF-开放式最短路径优先协议保姆级别详解(1)
  • JAVA 嵌套列表初始化和转字符串
  • 十大排序简介
  • 新冠肺炎服务预约微信小程序的设计与实现ssm+论文源码调试讲解
  • 【Unity精品插件】Love/Hate:专注于 AI 行为与情感互动
  • 1F1B 非交错式调度模式与 GPipe 策略的内存节省优势
  • 【CSS】HTML页面定位CSS - position 属性 relative 、absolute、fixed 、sticky
  • 用JSONEncoder解决Object of type Color is not JSON serializable报错
  • 【数据结构-堆】2233. K 次增加后的最大乘积