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

FFmpeg入门:最简单的视频播放器

FFmpeg入门:最简单的视频播放器

FFmpeg入门第一篇,制作一个简单的MP4视频播放器。

整体流程

话不多说,直接上流程图在这里插入图片描述

视频播放速率控制

这里可以直接看图中的帧率同步模块,可以分为如下几步

  1. 获取到当前帧的预期播放时间:根据时间基time_base和帧pts计算;time_base*pts
  2. 解码渲染过程中,获取当前时间av_gettime,计算和预期播放时间的时间差
  3. 使用SDL_Delay函数延迟播放时间差。

源代码

tutorial01.h

//
//  tutorial02.h
//  tutorial02
//
//  Created by chenhuaiyi on 2025/2/13.
//#ifndef tutorial02_h
#define tutorial02_h#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>#include <SDL.h>
#include <SDL_thread.h>#include <stdio.h>/**宏定义*/
#define SFM_REFRESH_EVENT (SDL_USEREVENT+1)/**全局变量*/
extern int            thread_exit;/**方法声明*/
char* get_frame_type(AVFrame* frame);#endif /* tutorial02_h */

tutorial02.c

// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif#include "tutorial02.h"// 控制程序是否结束
int		thread_exit=0;/**获取帧类型*/
char* get_frame_type(AVFrame* frame) {switch (frame->pict_type) {case AV_PICTURE_TYPE_I:return "I";break;case AV_PICTURE_TYPE_P:return "P";break;case AV_PICTURE_TYPE_B:return "B";break;case AV_PICTURE_TYPE_S:return "S";break;case AV_PICTURE_TYPE_SI:return "SI";break;case AV_PICTURE_TYPE_SP:return "SP";break;case AV_PICTURE_TYPE_BI:return "BI";break;default:return "N";break;}
}int main(int argc, char *argv[]) {AVFormatContext*  pFormatCtx = NULL;int               i, videoStream;AVCodecContext*   pCodecCtx = avcodec_alloc_context3(NULL);;AVCodecParameters*  pCodecParam = NULL;const AVCodec*    pCodec = NULL;AVFrame*          pFrame = NULL;AVFrame*          pFrame2 = NULL;AVPacket          packet;AVDictionary*     optionsDict = NULL;struct SwsContext*  sws_ctx = NULL;SDL_Window*       window = NULL;SDL_Renderer*     renderer = NULL;SDL_Texture*      texture=NULL;SDL_Rect          rect;SDL_Event         event;if(argc < 2) {fprintf(stderr, "Usage: test <file>\n");exit(1);}// SDL组件创建if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());exit(1);}// 1. 打开视频文件,获取格式上下文if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)return -1; // Couldn't open file// 2. 对文件探测流信息if(avformat_find_stream_info(pFormatCtx, NULL)<0)return -1; // Couldn't find stream information// 打印信息av_dump_format(pFormatCtx, 0, argv[1], 0);// 3.找到对应的视频流videoStream=-1;for(i=0; i<pFormatCtx->nb_streams; i++)if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) {videoStream=i;break;}if(videoStream==-1){return -1; // Didn't find a video stream}// 4. 将视频流编码参数写入上下文pCodecParam = pFormatCtx->streams[videoStream]->codecpar;avcodec_parameters_to_context(pCodecCtx, pCodecParam);// 5. 查找流的编码器pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL) {fprintf(stderr, "Unsupported codec!\n");return -1; // Codec not found}// 6. 打开流的编解码器if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)return -1; // Could not open codec// 7.申请缩放颜色空间格式的上下文sws_ctx =sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR,NULL,NULL,NULL);// 8.帧分配pFrame=av_frame_alloc();pFrame2=av_frame_alloc();// 9.计算并分配frame帧所占内存空间int numBytes=av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);uint8_t* buffer=(uint8_t*)av_malloc(numBytes*sizeof(uint8_t));av_image_fill_arrays(pFrame2->data, pFrame2->linesize, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);i=0;Uint32 start_time = SDL_GetTicks();// SDL2的最新创建和渲染窗口方式window = SDL_CreateWindow("SDL2 window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_SHOWN);if (!window) {printf("SDL_CreateWindow Error: %s\n", SDL_GetError());SDL_Quit();return 1;}renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);if (!renderer) {printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());SDL_DestroyWindow(window);SDL_Quit();return 1;}texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);AVRational time_base = pFormatCtx->streams[videoStream]->time_base;int64_t av_start_time = av_gettime();								// 播放开始时间int64_t frame_delay = av_q2d(time_base) * AV_TIME_BASE;				// pts单位(ms*1000)int64_t frame_start_time = av_gettime();// 循环一:对文件上下文持续读取packetwhile(av_read_frame(pFormatCtx, &packet)>=0) {if(packet.stream_index==videoStream) {// 将packet写入编解码器int ret = avcodec_send_packet(pCodecCtx, &packet);if (ret < 0) {printf("packet resolve error!");break;}// 循环二:从解码器中不断读取帧while(!avcodec_receive_frame(pCodecCtx, pFrame)) { // 解码得到数据frame// 帧格式转化,转为YUV420Psws_scale(sws_ctx,(uint8_t const * const *)pFrame->data,pFrame->linesize,0,pCodecCtx->height,pFrame2->data,pFrame2->linesize);// 将AVFrame的数据写入到texture中,然后渲染后windows上rect.x = 0;rect.y = 0;rect.w = pCodecCtx->width;rect.h = pCodecCtx->height;// 更新纹理SDL_UpdateYUVTexture(texture, &rect,pFrame2->data[0], pFrame2->linesize[0],	// 	YpFrame2->data[1], pFrame2->linesize[1],	// 	UpFrame2->data[2], pFrame2->linesize[2]);	//  V// 渲染页面SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);int64_t pts = pFrame->pts;											// ptsint64_t actual_playback_time = av_start_time + pts * frame_delay;	// 实际播放时间int64_t current_time = av_gettime();if (actual_playback_time > current_time) {SDL_Delay((Uint32)(actual_playback_time-current_time)/1000);	// 延迟当前时间和实际播放时间}i++;printf("第%i帧 | 属于%s | pts为%d | 时长为%.2fms | 实际播放点为%.2fs | 预期播放点为%.2fs\n ",i,get_frame_type(pFrame),(int)pFrame->pts,(double)(av_gettime() - frame_start_time)/1000,(double)(av_gettime() - av_start_time)/AV_TIME_BASE,pFrame->pts * av_q2d(time_base));frame_start_time = av_gettime();}}// Free the packet that was allocated by av_read_frameav_packet_unref(&packet);SDL_PollEvent(&event);switch(event.type) {case SDL_QUIT:SDL_Quit();exit(0);break;default:break;}}Uint32 endTime = SDL_GetTicks();/**打印一些关键参数*/printf("格式: %s\n", pFormatCtx->iformat->name);printf("时长: %lld us\n", pFormatCtx->duration);printf("编码器: %s (%s)\n", pCodec->name, avcodec_get_name(pCodecCtx->codec_id));printf("分辨率: %dx%d\n", pCodecCtx->width, pCodecCtx->height);printf("帧率: %.2f\n", av_q2d(pFormatCtx->streams[videoStream]->avg_frame_rate));printf("帧数: %lld\n", pFormatCtx->streams[videoStream]->nb_frames);printf("比特率: %lld\n", pFormatCtx->bit_rate);printf("pts单位(ms*1000): %.2f\n", av_q2d(pFormatCtx->streams[videoStream]->time_base) * AV_TIME_BASE);printf("视频持续时长为 %d,视频帧总数为 %d\n", (endTime-start_time)/1000, i);// Free the YUV frameav_free(pFrame);av_free(pFrame2);// Close the codec paramavcodec_parameters_free(&pCodecParam);// Close the codecavcodec_free_context(&pCodecCtx);// Close the video fileavformat_close_input(&pFormatCtx);return 0;
}

文章最后,感谢音视频领域的大神@雷霄骅,雷神永垂不朽!!!

相关文章:

FFmpeg入门:最简单的视频播放器

FFmpeg入门&#xff1a;最简单的视频播放器 FFmpeg入门第一篇&#xff0c;制作一个简单的MP4视频播放器。 整体流程 话不多说&#xff0c;直接上流程图 视频播放速率控制 这里可以直接看图中的帧率同步模块&#xff0c;可以分为如下几步 获取到当前帧的预期播放时间&…...

GitHub 语析 - 基于大模型的知识库与知识图谱问答平台

语析 - 基于大模型的知识库与知识图谱问答平台 GitHub 地址&#xff1a;https://github.com/xerrors/Yuxi-Know &#x1f4dd; 项目概述 语析是一个强大的问答平台&#xff0c;结合了大模型 RAG 知识库与知识图谱技术&#xff0c;基于 Llamaindex VueJS FastAPI Neo4j 构…...

Linux《基础开发工具(上)》

在之前的篇章当中我们已经了解了Linux当中基本的指令以及相关的知识&#xff0c;那么接下来在本篇当中就开始学基本的开发工具&#xff0c;在此我们一共要了解6大开发工具&#xff0c;在此将这些工具的学习分为上中下篇&#xff0c;在本篇当中我们首先要来学习的是yun以及vim,一…...

【考试大纲】初级信息系统运行管理员考试大纲

目录 引言一、考试要求1、 考试说明2、 考试要求3、 本考试设置的科目包括:二、考试范围考试科目1:信息系统基础知识(初级)考试科目2:信息系统运行管理(应用技术)引言 最新的信息系统运行管理员考试大纲出版于 2018 年 9 月,本考试大纲基于此版本整理。 一、考试要求…...

《每天搞懂一道Hard》之数独终结者(LeetCode 37)

&#x1f4cc;《每天搞懂一道Hard》之数独终结者&#xff08;LeetCode 37&#xff09; &#x1f517;原题链接&#xff1a;https://leetcode.com/problems/sudoku-solver/ 今天我们来解剖一个经典回溯算法问题——数独求解器&#xff01;这道题在算法面试中出现频率高达35%&a…...

LangChain原理解析及开发实战指南(2025年最新版)

一、LangChain核心架构解析 1.1 框架设计理念 LangChain是基于提示工程(Prompt Engineering)构建的LLM应用开发框架&#xff0c;其核心思想是通过模块化组件实现大语言模型与业务系统的无缝对接。该框架采用分层设计&#xff1a; 接口层&#xff1a;统一对接OpenAI、DeepSee…...

YoloV8改进策略:Block改进|CBlock,Transformer式的卷积结构|即插即用

摘要 论文标题: SparseViT: Nonsemantics-Centered, Parameter-Efficient Image Manipulation Localization through Spare-Coding Transformer 论文链接: https://arxiv.org/pdf/2412.14598 官方GitHub: https://github.com/scu-zjz/SparseViT 这段代码出自SparseViT ,代码如…...

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_open_file

ngx_open_file 定义在src/os/unix/ngx_files.h #define ngx_open_file(name, mode, create, access) \open((const char *) name, mode|create, access)#define NGX_FILE_RDONLY O_RDONLY #define NGX_FILE_WRONLY O_WRONLY #de…...

测试金蝶云的OpenAPI

如何使用Postman测试K3Cloud的OpenAPI 1. 引言 在本篇博客中&#xff0c;我将带你逐步了解如何使用Postman测试和使用K3Cloud的OpenAPI。内容包括下载所需的SDK文件、配置文件、API调用及测试等步骤。让我们开始吧&#xff01; 2. 下载所需的SDK文件 2.1 获取SDK 首先&…...

C语言408考研先行课第一课:数据类型

由于408要考数据结构……会有算法题…… 所以&#xff0c;需要C语言来进行一个预备…… 因为大一贪玩&#xff0c;C语言根本没学进去……谁能想到考研还用得到呢&#xff1f;【手动doge&#xff08;bushi&#xff09; 软件用的是Clion&#xff0c;可以自行搜索教程下载使用。…...

11天 -- Redis 中跳表的实现原理是什么?Redis 的 hash 是什么?Redis Zset 的实现原理是什么?

Redis 中跳表的实现原理是什么&#xff1f; Redis 中的跳表&#xff08;Skip List&#xff09;是一种基于有序链表的高效数据结构&#xff0c;通过在链表上增加多级索引来提高数据的查找效率。以下是 Redis 中跳表的实现原理&#xff1a; 1. 基本概念 节点结构&#xff1a;跳…...

单细胞分析(19)—— 单细胞转录组基因集评分方法

下面是每种基因集评分方法的原理介绍代码示例&#xff0c;适用于R语言和Python两种主流生信分析环境。可以直接应用于单细胞转录组&#xff08;scRNA-seq&#xff09;数据分析中。 &#x1f52c; 单细胞转录组基因集评分方法&#xff08;附代码示例&#xff09; 在单细胞RNA测…...

010 rocketmq批量消息

文章目录 批量消息BatchProducer.javaBatchConsumer.java 批量消息 批量发送可以提⾼发送性能&#xff0c;但有⼀定的限制&#xff1a; topic 相同 waitStoreMsgOK 相同 &#xff08;⾸先我们建设消息的iswaitstoremsgoktrue(默认为true), 如果没有异常,我们将始终收到"O…...

JavaWeb后端基础(3)

原打算把Mysql操作数据库的一些知识写进去&#xff0c;但是感觉没必要&#xff0c;要是现在会的都是简单的增删改查&#xff0c;所以&#xff0c;这一篇&#xff0c;我直接从java操作数据库开始写&#xff0c;所以这一篇大致就是记一下JDBC、MyBatis、以及SpringBoot的配置文件…...

Oracle数据库基础入门(三): DQL 深入解析与实践

在 Oracle 数据库的知识体系中&#xff0c;数据查询语言&#xff08;DQL&#xff09;无疑是最为常用且关键的部分之一。对于 Java 全栈开发者而言&#xff0c;熟练掌握 DQL 不仅能高效地从数据库中获取所需数据&#xff0c;更是构建强大后端应用的基石。通过 DQL&#xff0c;我…...

P9231 [蓝桥杯 2023 省 A] 平方差

P9231 [蓝桥杯 2023 省 A] 平方差 - 洛谷 题目描述 给定 L,R&#xff0c;问 L≤x≤R 中有多少个数 x 满足存在整数 y,z 使得 xy2−z2。 输入格式 输入一行包含两个整数 L,R&#xff0c;用一个空格分隔。 输出格式 输出一行包含一个整数满足题目给定条件的 x 的数量。 输…...

贪心算法 求解思路

贪心算法简介 贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点&#xff0c;做一个当时&#xff08;看起来是&#xff09;最佳的选择。这种启发式策略并不是总能产生出最优解&#xff0c;但它常常能给出最优解。 在实际设计贪心算法时&#xff0…...

2025/2/25,字节跳动后端开发一面面经

一、双方简单自我介绍 面试官先自我介绍,之后属于面试官看简历过程,基本不听。 二、实习中遇到最难的事情,怎么解决的 主要问的还是实习中做过的项目,项目难点在哪里(自己参与的地方),面对困难是怎么思考,怎么实际操作解决的。 三、项目实现细节 掌握自己项目的实…...

Buildroot 添加自定义模块-内置文件到文件系统

目录 概述实现步骤1. 创建包目录和文件结构2. 配置 Config.in3. 定义 cp_bin_files.mk4. 添加源文件install.shmy.conf 5. 配置与编译 概述 Buildroot 是一个高度可定制和模块化的嵌入式 Linux 构建系统&#xff0c;适用于从简单到复杂的各种嵌入式项目. buildroot的源码中bui…...

SpringBoot新闻推荐系统设计与实现

随着信息时代的快速发展&#xff0c;新闻推荐系统成为用户获取个性化内容的重要工具。本文将介绍一个幽络源的基于SpringBoot开发的新闻推荐系统&#xff0c;该系统功能全面&#xff0c;操作简便&#xff0c;能够满足管理员和用户的多种需求。 管理员模块 管理员模块为系统管…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

aardio 自动识别验证码输入

技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”&#xff0c;于是尝试整合图像识别与网页自动化技术&#xff0c;完成了这套模拟登录流程。核心思路是&#xff1a;截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

【Java多线程从青铜到王者】单例设计模式(八)

wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本&#xff0c;sleep也是可以指定时间的&#xff0c;也就是说时间一到就会解除阻塞&#xff0c;继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒)&#xff0c;wait能被notify提前唤醒&#xf…...