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横板游戏中,实现一个角色的初始状态-闲置状态、移动状态、空中状态。并且是利用状态机进行实现的。 本系列是跟着视频教程走的,所写也是作者个人的学习记录笔记。如有错…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
