SDL2绘制ffmpeg解析的mp4文件
文章目录
- 1.FFMPEG利用命令行将mp4转yuv420
- 2.ffmpeg将mp4解析为yuv数据
- 2.1 核心api:
- 3.SDL2进行yuv绘制到屏幕
- 3.1 核心api
- 4.完整代码
- 5.效果展示
- 6.SDL2事件响应补充
- 6.1 处理方式-01
- 6.2 处理方式-02
本项目采用生产者消费者模型,生产者线程:使用ffmpeg将mp4格式数据解析为yuv的帧,消费者线程:利用sdl2将解析的yuv的帧进行消费,绘制到屏幕上。未完成的部分:1.解析音频数据,并与视频数据同步。2.增加界面:暂停,播放按钮,支持视频前进和后退。
学习音视频的参考资料与项目:
playdemo_github
雷神的csdn博客
1.FFMPEG利用命令行将mp4转yuv420
ffmpeg -i input.mp4 -c:v rawvideo -pix_fmt yuv420p output.yuv
2.ffmpeg将mp4解析为yuv数据
2.1 核心api:
- av_read_frame:读取一帧数据
- avcodec_send_packet:将数据包发送给解码器
- avcodec_receive_frame:将数据包从解码器中取
- sws_scale:格式转换,将解码后的帧数据转为yuv数据,存储在data[0],data[1],data[2]中
void readFrame()
{AVPacket* avPacket = av_packet_alloc();AVFrame* frame = av_frame_alloc();FILE* fp = fopen("F:/VS_Project/ffmpeg_demo/yuv.data","wb+") ;while (av_read_frame(formatContext, avPacket) >= 0 && fp){if (avPacket->stream_index == videoStreamIndex){if (avcodec_send_packet(codecContext, avPacket) < 0) {std::cerr << "发送数据包到解码器失败" << std::endl;break;}/*解码*/int ret = avcodec_receive_frame(codecContext, frame);printf("ret:%d\n", ret);if (ret >= 0){ret = sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, yuvFrame->data, yuvFrame->linesize);printf("sws_scale ret=%d\n", ret);std::lock_guard<std::mutex>lck(mtx);isFinished = false;memcpy(yuvBuf, yuvFrame->data[0], yuvFrame->width * yuvFrame->height);memcpy(yuvBuf + yuvFrame->width * yuvFrame->height, yuvFrame->data[1], yuvFrame->width * yuvFrame->height / 4);memcpy(yuvBuf + yuvFrame->width * yuvFrame->height*5/4, yuvFrame->data[2], yuvFrame->width * yuvFrame->height / 4);isFinished = true;condvar.notify_one();//保存y分量//fwrite(yuvFrame->data[0], 1, yuvFrame->width * yuvFrame->height, fp);//保存uv分量//fwrite(yuvFrame->data[1], 1, yuvFrame->width * yuvFrame->height/4, fp);//fwrite(yuvFrame->data[2], 1, yuvFrame->width * yuvFrame->height / 4, fp);}}}fclose(fp);av_frame_unref(yuvFrame);av_packet_free(&avPacket);av_frame_unref(frame);
}
3.SDL2进行yuv绘制到屏幕
3.1 核心api
- SDL_Init
- SDL_CreateWindow
- SDL_CreateRenderer
- SDL_CreateTexture
- SDL_UpdateTexture
- SDL_RenderCopy
- SDL_RenderPresent
- SDL_Delay:控制帧率
int sdl_display()
{if (SDL_Init(SDL_INIT_VIDEO)) {printf("sdl init failed\n");return -1;}SDL_Window* window = SDL_CreateWindow("sdl_demo", 200, 200, codecContext->width, codecContext->height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!window) {SDL_Quit();return -1;}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){SDL_DestroyWindow(window);SDL_Quit();return -1;}SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);SDL_RenderClear(renderer);Uint32 pixformat = SDL_PIXELFORMAT_IYUV;SDL_Texture* sdlTexture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, codecContext->width, codecContext->height);//FILE* fp = fopen("F:/VS_Project/ffmpeg_demo/yuv.data", "rb+");while (1) {//int ret = fread(yuvBuf, 1, yuvlen, fp);//if (ret <= 0) {// break;//}std::unique_lock<std::mutex>lck(mtx);if (condvar.wait_for(lck, std::chrono::seconds(1), [] {return isFinished;})){isFinished = false;SDL_UpdateTexture(sdlTexture, NULL, yuvBuf, codecContext->width);SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);SDL_RenderPresent(renderer);//控制帧率25fpsSDL_Delay(40);}else {printf("sdl thread exit!\n");break;}}SDL_Quit();return 0;
}
4.完整代码
-使用两个线程,生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdexcept>
#include <iostream>
#include <string>
#include <thread>
#include <fstream>#include <mutex>
#include <condition_variable>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>#include <SDL.h>
}
#undef main
#pragma warning(disable:4996)
AVFormatContext* formatContext = nullptr;
AVCodecContext* codecContext = nullptr;
SwsContext* swsContext = nullptr;
int videoStreamIndex = -1;
AVFrame* yuvFrame;
unsigned char* yuvBuf;
bool isReady = false;
bool isFinished = false;std::mutex mtx;
std::condition_variable condvar;
void readFrame()
{AVPacket* avPacket = av_packet_alloc();AVFrame* frame = av_frame_alloc();FILE* fp = fopen("F:/VS_Project/ffmpeg_demo/yuv.data","wb+") ;while (av_read_frame(formatContext, avPacket) >= 0 && fp){if (avPacket->stream_index == videoStreamIndex){if (avcodec_send_packet(codecContext, avPacket) < 0) {std::cerr << "发送数据包到解码器失败" << std::endl;break;}/*解码*/int ret = avcodec_receive_frame(codecContext, frame);printf("ret:%d\n", ret);if (ret >= 0){ret = sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, yuvFrame->data, yuvFrame->linesize);printf("sws_scale ret=%d\n", ret);std::lock_guard<std::mutex>lck(mtx);isFinished = false;memcpy(yuvBuf, yuvFrame->data[0], yuvFrame->width * yuvFrame->height);memcpy(yuvBuf + yuvFrame->width * yuvFrame->height, yuvFrame->data[1], yuvFrame->width * yuvFrame->height / 4);memcpy(yuvBuf + yuvFrame->width * yuvFrame->height*5/4, yuvFrame->data[2], yuvFrame->width * yuvFrame->height / 4);isFinished = true;condvar.notify_one();//保存y分量//fwrite(yuvFrame->data[0], 1, yuvFrame->width * yuvFrame->height, fp);//保存uv分量//fwrite(yuvFrame->data[1], 1, yuvFrame->width * yuvFrame->height/4, fp);//fwrite(yuvFrame->data[2], 1, yuvFrame->width * yuvFrame->height / 4, fp);}}}fclose(fp);av_frame_unref(yuvFrame);av_packet_free(&avPacket);av_frame_unref(frame);
}int sdl_display()
{if (SDL_Init(SDL_INIT_VIDEO)) {printf("sdl init failed\n");return -1;}SDL_Window* window = SDL_CreateWindow("sdl_demo", 200, 200, codecContext->width, codecContext->height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!window) {SDL_Quit();return -1;}SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);if (!renderer){SDL_DestroyWindow(window);SDL_Quit();return -1;}SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);SDL_RenderClear(renderer);Uint32 pixformat = SDL_PIXELFORMAT_IYUV;SDL_Texture* sdlTexture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, codecContext->width, codecContext->height);//FILE* fp = fopen("F:/VS_Project/ffmpeg_demo/yuv.data", "rb+");while (1) {//int ret = fread(yuvBuf, 1, yuvlen, fp);//if (ret <= 0) {// break;//}std::unique_lock<std::mutex>lck(mtx);if (condvar.wait_for(lck, std::chrono::seconds(1), [] {return isFinished;})){isFinished = false;SDL_UpdateTexture(sdlTexture, NULL, yuvBuf, codecContext->width);SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);SDL_RenderPresent(renderer);//控制帧率25fpsSDL_Delay(40);}else {printf("sdl thread exit!\n");break;}}SDL_Quit();return 0;
}/*ffmpeg -i input.mp4 -c:v rawvideo -pix_fmt yuv420p output.yuv*/
int main(int argc, char* argv[])
{/*if (argc != 2) {std::cerr << "文件名未指定" << std::endl;return -1;}*/std::string filename = "F:/VS_Project/ffmpeg_demo/1.mkv";if (avformat_open_input(&formatContext, filename.c_str(), nullptr, nullptr) != 0){std::cerr << "无法打开文件" << std::endl;return -1;}if (avformat_find_stream_info(formatContext, nullptr) < 0) {std::cerr << "无法找到视频流" << std::endl;return -1;}for (int i = 0; i < formatContext->nb_streams; i++){enum AVMediaType type = AVMEDIA_TYPE_VIDEO;AVStream* st = formatContext->streams[i];AVCodecParameters* codecpar = st->codecpar;if (codecpar->codec_type == type){videoStreamIndex = i;const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);codecContext = avcodec_alloc_context3(codec);avcodec_parameters_to_context(codecContext, codecpar);avcodec_open2(codecContext, codec, nullptr);swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,codecContext->width, codecContext->height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, nullptr, nullptr, nullptr);std::cout << "w:" << codecpar->width << std::endl;std::cout << "h:" << codecpar->height << std::endl;}}yuvFrame = av_frame_alloc();yuvFrame->width = codecContext->width;yuvFrame->height = codecContext->height;yuvFrame->format = AV_PIX_FMT_YUV420P;int yuvlen = codecContext->width * codecContext->height * 3 / 2;yuvBuf = new unsigned char[yuvlen];int ret = av_frame_get_buffer(yuvFrame, 0);if (ret < 0) {printf("分配缓冲区失败\n");return -1;}//sdl_init();std::thread th1(readFrame);std::thread th2(sdl_display);th1.join();th2.join();delete[]yuvBuf;return 0;
}
5.效果展示
6.SDL2事件响应补充
6.1 处理方式-01
起一个refresh_video线程,用于产生一个自定义更新video的事件:REFRESH_EVENT,并且每40ms更新一次。while循环中持续等待事件的到来:SDL_WaitEvent,当收到事件后更新一帧画面。同时当监测到窗口改变的事件,SDL_GetWindowSize对窗口进行调整。
int refresh_video(void *opaque){thread_exit=0;while (thread_exit==0) {SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}thread_exit=0;//BreakSDL_Event event;event.type = BREAK_EVENT;SDL_PushEvent(&event);return 0;
}
SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video,NULL,NULL);SDL_Event event;while(1){//WaitSDL_WaitEvent(&event);if(event.type==REFRESH_EVENT){if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){// Loopfseek(fp, 0, SEEK_SET);fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);}SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w); //FIX: If window is resizesdlRect.x = 0; sdlRect.y = 0; sdlRect.w = screen_w; sdlRect.h = screen_h; SDL_RenderClear( sdlRenderer ); SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect); SDL_RenderPresent( sdlRenderer ); }else if(event.type==SDL_WINDOWEVENT){//If ResizeSDL_GetWindowSize(screen,&screen_w,&screen_h);}else if(event.type==SDL_QUIT){thread_exit=1;}else if(event.type==BREAK_EVENT){break;}}SDL_Quit();
6.2 处理方式-02
起一个事件循环线程,SDL_PeepEvents 从事件队列中取出一个事件,然后更新事件队列,并进行绘制操作。
//播放控制循环
void VideoCtl::LoopThread(VideoState *cur_stream)
{SDL_Event event;double incr, pos, frac;m_bPlayLoop = true;while (m_bPlayLoop){double x;refresh_loop_wait_event(cur_stream, &event);switch (event.type) {case SDL_KEYDOWN:switch (event.key.keysym.sym) {case SDLK_s: // S: Step to next framestep_to_next_frame(cur_stream);break;case SDLK_a:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);break;case SDLK_v:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);break;case SDLK_c:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);break;case SDLK_t:stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);break;default:break;}break;case SDL_WINDOWEVENT://窗口大小改变事件qDebug()<<"SDL_WINDOWEVENT "<<endl;switch (event.window.event) {case SDL_WINDOWEVENT_RESIZED:screen_width = cur_stream->width = event.window.data1;screen_height = cur_stream->height = event.window.data2;case SDL_WINDOWEVENT_EXPOSED:cur_stream->force_refresh = 1;}break;case SDL_QUIT:case FF_QUIT_EVENT:do_exit(cur_stream);break;default:break;}}do_exit(m_CurStream);//m_CurStream = nullptr;}
相关文章:

SDL2绘制ffmpeg解析的mp4文件
文章目录 1.FFMPEG利用命令行将mp4转yuv4202.ffmpeg将mp4解析为yuv数据2.1 核心api: 3.SDL2进行yuv绘制到屏幕3.1 核心api 4.完整代码5.效果展示6.SDL2事件响应补充6.1 处理方式-016.2 处理方式-02 本项目采用生产者消费者模型,生产者线程:使用ffmpeg将m…...

决策树C4.5算法的技术深度剖析、实战解读
目录 一、简介决策树(Decision Tree)例子: 信息熵(Information Entropy)与信息增益(Information Gain)例子: 信息增益比(Gain Ratio)例子: 二、算…...

LLMs Python解释器程序辅助语言模型(PAL)Program-aided language models (PAL)
正如您在本课程早期看到的,LLM执行算术和其他数学运算的能力是有限的。虽然您可以尝试使用链式思维提示来克服这一问题,但它只能帮助您走得更远。即使模型正确地通过了问题的推理,对于较大的数字或复杂的运算,它仍可能在个别数学操…...

【12】c++设计模式——>单例模式练习(任务队列)
属性: (1)存储任务的容器,这个容器可以选择使用STL中的队列(queue) (2)互斥锁,多线程访问的时候用于保护任务队列中的数据 方法:主要是对任务队列中的任务进行操作 &…...

Python之函数、模块、包库
函数、模块、包库基础概念和作用 A、函数 减少代码重复 将复杂问题代码分解成简单模块 提高代码可读性 复用老代码 """ 函数 """# 定义一个函数 def my_fuvtion():# 函数执行部分print(这是一个函数)# 定义带有参数的函数 def say_hello(n…...
SQL创建与删除索引
索引创建、删除与使用: 1.1 create方式创建索引:CREATE [UNIQUE – 唯一索引 | FULLTEXT – 全文索引 ] INDEX index_name ON table_name – 不指定唯一或全文时默认普通索引 (column1[(length) [DESC|ASC]] [,column2,…]) – 可以对多列建立组合索引 …...

网络协议--链路层
2.1 引言 从图1-4中可以看出,在TCP/IP协议族中,链路层主要有三个目的: (1)为IP模块发送和接收IP数据报; (2)为ARP模块发送ARP请求和接收ARP应答; (3…...
HDLbits: Count clock
目前写过最长的verilog代码,用了将近三个小时,编写12h显示的时钟,改来改去,估计只有我自己看得懂(吐血) module top_module(input clk,input reset,input ena,output pm,output [7:0] hh,output [7:0] mm,…...
【1day】用友移动管理系统任意文件上传漏洞学习
注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞描述 二、影响版本 三、资产测绘 四、漏洞复现...
【c++】向webrtc学习容器操作
std::map的key为std::pair 时的查找 std::map<RemoteAndLocalNetworkId, size_t> in_flight_bytes_RTC_GUARDED_BY(&lock_);private:using RemoteAndLocalNetworkId = std::pair<uint16_t, uint16_t...

SpringBoot+Vue3外卖项目构思
SpringBoot的学习: SpringBoot的学习_明里灰的博客-CSDN博客 实现功能 前台 用户注册,邮箱登录,地址管理,历史订单,菜品规格,购物车,下单,菜品浏览,评价,…...

【AI视野·今日NLP 自然语言处理论文速览 第四十七期】Wed, 4 Oct 2023
AI视野今日CS.NLP 自然语言处理论文速览 Wed, 4 Oct 2023 Totally 73 papers 👉上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Contrastive Post-training Large Language Models on Data Curriculum Authors Canwen Xu, Corby Rosset, Luc…...
c++的lambda表达式
文章目录 1 lambda表达式2 捕捉列表 vs 参数列表3 lambda表达式的传递3.1 函数作为形参3.2 场景1:条件表达式3.3 场景2:线程的运行表达式 1 lambda表达式 lambda表达式可以理解为匿名函数,也就是没有名字的函数,既然是函数&#…...

电梯安全监测丨S271W无线水浸传感器用于电梯机房/电梯基坑水浸监测
城市化进程中,电梯与我们的生活息息相关。高层住宅、医院、商场、学校、车站等各种商业体建筑、公共建筑中电梯为我们生活工作提供了诸多便利。 保障电梯系统的安全至关重要!特别是电梯机房和电梯基坑可通过智能化改造提高其安全性和稳定性。例如在暴风…...
Java异常:基本概念、分类和处理
Java异常:基本概念、分类和处理 在Java编程中,异常处理是一个非常重要的部分。了解如何识别、处理和避免异常对于编写健壮、可维护的代码至关重要。本文将介绍Java异常的基本概念、分类和处理方法,并通过简单的代码示例进行说明。 一、什么…...

小谈设计模式(19)—备忘录模式
小谈设计模式(19)—备忘录模式 专栏介绍专栏地址专栏介绍 备忘录模式主要角色发起人(Originator)备忘录(Memento)管理者(Caretaker) 应用场景结构实现步骤Java程序实现首先ÿ…...
《数据库系统概论》王珊版课后习题
第一章 绪论 1.数据、数据库、数据库管理系统、数据库系统的概念 (1)数据(Data):数据是数据库中存储的基本对象,是描述事物的符号记录。数据有多种表现形式,它们都可以经过数字化后存入计算机…...
MariaDB 修改用户远程登录
今天修改MariaDB数据库用户的Host时出现错误: ERROR 1356 (HY000): View ‘mysql.user’ references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them 我的步骤如下: 1.登陆 2.use mysql; 3.执行…...
Elasticsearch使用mapping映射定义以及基本的数据类型
1、说明 Elasticsearch的映射相当于数据库的数据字典,它定义了每个字段的名称和能够保存的数据类型,并且内置了20多种字段类型用于支持多种多样的结构化数据,这里仅介绍几种常用的字段类型,如需要了解全部的类型,请参…...

【unity】制作一个角色的初始状态(左右跳二段跳)【2D横板动作游戏】
前言 hi~ 大家好!欢迎大家来到我的全新unity学习记录系列。现在我想在2d横板游戏中,实现一个角色的初始状态-闲置状态、移动状态、空中状态。并且是利用状态机进行实现的。 本系列是跟着视频教程走的,所写也是作者个人的学习记录笔记。如有错…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...