FFmpeg入门:最简单的视频播放器
FFmpeg入门:最简单的视频播放器
FFmpeg入门第一篇,制作一个简单的MP4视频播放器。
整体流程
话不多说,直接上流程图
视频播放速率控制
这里可以直接看图中的帧率同步模块,可以分为如下几步
- 获取到当前帧的预期播放时间:根据时间基time_base和帧pts计算;time_base*pts;
- 解码渲染过程中,获取当前时间av_gettime,计算和预期播放时间的时间差;
- 使用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入门:最简单的视频播放器 FFmpeg入门第一篇,制作一个简单的MP4视频播放器。 整体流程 话不多说,直接上流程图 视频播放速率控制 这里可以直接看图中的帧率同步模块,可以分为如下几步 获取到当前帧的预期播放时间&…...

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

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

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

《每天搞懂一道Hard》之数独终结者(LeetCode 37)
📌《每天搞懂一道Hard》之数独终结者(LeetCode 37) 🔗原题链接:https://leetcode.com/problems/sudoku-solver/ 今天我们来解剖一个经典回溯算法问题——数独求解器!这道题在算法面试中出现频率高达35%&a…...
LangChain原理解析及开发实战指南(2025年最新版)
一、LangChain核心架构解析 1.1 框架设计理念 LangChain是基于提示工程(Prompt Engineering)构建的LLM应用开发框架,其核心思想是通过模块化组件实现大语言模型与业务系统的无缝对接。该框架采用分层设计: 接口层:统一对接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. 引言 在本篇博客中,我将带你逐步了解如何使用Postman测试和使用K3Cloud的OpenAPI。内容包括下载所需的SDK文件、配置文件、API调用及测试等步骤。让我们开始吧! 2. 下载所需的SDK文件 2.1 获取SDK 首先&…...

C语言408考研先行课第一课:数据类型
由于408要考数据结构……会有算法题…… 所以,需要C语言来进行一个预备…… 因为大一贪玩,C语言根本没学进去……谁能想到考研还用得到呢?【手动doge(bushi) 软件用的是Clion,可以自行搜索教程下载使用。…...
11天 -- Redis 中跳表的实现原理是什么?Redis 的 hash 是什么?Redis Zset 的实现原理是什么?
Redis 中跳表的实现原理是什么? Redis 中的跳表(Skip List)是一种基于有序链表的高效数据结构,通过在链表上增加多级索引来提高数据的查找效率。以下是 Redis 中跳表的实现原理: 1. 基本概念 节点结构:跳…...
单细胞分析(19)—— 单细胞转录组基因集评分方法
下面是每种基因集评分方法的原理介绍代码示例,适用于R语言和Python两种主流生信分析环境。可以直接应用于单细胞转录组(scRNA-seq)数据分析中。 🔬 单细胞转录组基因集评分方法(附代码示例) 在单细胞RNA测…...
010 rocketmq批量消息
文章目录 批量消息BatchProducer.javaBatchConsumer.java 批量消息 批量发送可以提⾼发送性能,但有⼀定的限制: topic 相同 waitStoreMsgOK 相同 (⾸先我们建设消息的iswaitstoremsgoktrue(默认为true), 如果没有异常,我们将始终收到"O…...

JavaWeb后端基础(3)
原打算把Mysql操作数据库的一些知识写进去,但是感觉没必要,要是现在会的都是简单的增删改查,所以,这一篇,我直接从java操作数据库开始写,所以这一篇大致就是记一下JDBC、MyBatis、以及SpringBoot的配置文件…...
Oracle数据库基础入门(三): DQL 深入解析与实践
在 Oracle 数据库的知识体系中,数据查询语言(DQL)无疑是最为常用且关键的部分之一。对于 Java 全栈开发者而言,熟练掌握 DQL 不仅能高效地从数据库中获取所需数据,更是构建强大后端应用的基石。通过 DQL,我…...
P9231 [蓝桥杯 2023 省 A] 平方差
P9231 [蓝桥杯 2023 省 A] 平方差 - 洛谷 题目描述 给定 L,R,问 L≤x≤R 中有多少个数 x 满足存在整数 y,z 使得 xy2−z2。 输入格式 输入一行包含两个整数 L,R,用一个空格分隔。 输出格式 输出一行包含一个整数满足题目给定条件的 x 的数量。 输…...

贪心算法 求解思路
贪心算法简介 贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时(看起来是)最佳的选择。这种启发式策略并不是总能产生出最优解,但它常常能给出最优解。 在实际设计贪心算法时࿰…...
2025/2/25,字节跳动后端开发一面面经
一、双方简单自我介绍 面试官先自我介绍,之后属于面试官看简历过程,基本不听。 二、实习中遇到最难的事情,怎么解决的 主要问的还是实习中做过的项目,项目难点在哪里(自己参与的地方),面对困难是怎么思考,怎么实际操作解决的。 三、项目实现细节 掌握自己项目的实…...

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

SpringBoot新闻推荐系统设计与实现
随着信息时代的快速发展,新闻推荐系统成为用户获取个性化内容的重要工具。本文将介绍一个幽络源的基于SpringBoot开发的新闻推荐系统,该系统功能全面,操作简便,能够满足管理员和用户的多种需求。 管理员模块 管理员模块为系统管…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...