Beginning SDL 2.0(5) 基于MFC和SDL的YuvPlayer
本文是在“Beginning SDL 2.0(4) YUV加载及渲染”(以下简称BS4)基础上做的功能完善,如果你对之间介绍的内容了解不多,麻烦先阅读之前的内容。
本文主要介绍如何完成一个基于MFC和SDL 2.0的YUV播放器,基本思路是使用Windows的WM_TIMER消息,定期刷新画面。(正规的播放器通常使用一个独立的线程用于做固定帧率的刷新,这里为了简单期间使用系统提供的定时器实现。)
工程创建
使用vs10创建mfc基于对话框的工程,2_sdl_yuv_player,配置好SDL包含路径,同时包含BS4中提供的YuvRender类。如果不想处理unicode字符,建议将工程属性的字符集设置为多字节编码。
并在主对话框中编辑出如下几个控件:一个Static用于YUV视频显示,一个播放按钮用于选择yuv路径,并开启播放,三个输入框分别用于输入视频宽、高及帧率。效果如下:

YuvRender类更新
由于BS4中的YuvRender是读取本地文件目录下的yuv图像,然后显示视频的,这里需要修改下,以支持动态的YUV画面渲染。
具体接口如下:
#pragma once
#include "sdlvideorender.h" class YuvRender :public SDLVideoRender
{
public:
YuvRender(void);
~YuvRender(void); // Init use parent impl
//bool Init(HWND show_wnd, RECT show_rect);
void Deinit(); // width x height resolution
// data[] for Y\U\V, stride is linesize of each raw
void Update(int width, int height, unsigned char *data[], int stride[]);
bool Render(); private:
bool CreateTexture(int width, int height);
void FillTexture(unsigned char *data[], int stride[]); private:
// texture size
int m_in_width, m_in_height;
SDL_Texture * m_show_texture;
};
相比之前的版本这里最大的区别是Update函数不再是空实现,添加了CreateTexture函数,主要考虑我们事先是不知道需要创建Texture的分辨率。
这里Init函数功能,完全可以直接使用父类提供的实现。
下面是Deinit函数实现代码
void YuvRender::Deinit()
{
if (nullptr != m_show_texture)
{
SDL_DestroyTexture(m_show_texture);
m_show_texture = NULL;
} SDLVideoRender::Deinit();
}
Update函数会调用CreateTexture和FillTexture两个函数,用于创建和填充纹理,其实现代码如下:
bool YuvRender::CreateTexture(int width, int height)
{
if (m_in_height == height && m_in_width == width &&
nullptr != m_show_texture)
{
return true;
} ASSERT(width > && width < );
ASSERT(height > && height < ); m_show_texture = SDL_CreateTexture(m_sdl_renderer, SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING, width, height);
if (nullptr != m_show_texture)
{
m_in_width = width;
m_in_height = height;
} return NULL != m_show_texture;
}
void YuvRender::FillTexture(unsigned char *data[], int stride[])
{
void * pixel = NULL;
int pitch = ;
if( == SDL_LockTexture(m_show_texture, NULL, &pixel, &pitch))
{
// for Y
int h = m_in_height;
int w = m_in_width;
unsigned char * dst = reinterpret_cast<unsigned char *>(pixel);
unsigned char * src = data[];
for (int i = ; i < h; ++i)
{
memcpy(dst, src, w);
dst += pitch;
src += stride[];
} h >>= ;
w >>= ;
pitch >>= ;
// for U
for (int i = ; i < h; ++i)
{
memcpy(dst, src, w);
dst += pitch;
src += stride[];
} // for V
for (int i = ; i < h; ++i)
{
memcpy(dst, src, w);
dst += pitch;
src += stride[];
}
SDL_UnlockTexture(m_show_texture);
}
} // width x height resolution
// data[] for Y\U\V, stride is linesize of each raw
void YuvRender::Update(int width, int height, unsigned char *data[], int stride[])
{
if (nullptr == m_show_texture)
{
CreateTexture(width, height);
} if (nullptr != m_show_texture)
{
FillTexture(data, stride);
}
}
最后一个函数是Render,实现相对简单,直接将texture复制并提交到显存中。
bool YuvRender::Render()
{
if (NULL != m_show_texture)
{
SDL_RenderCopy(m_sdl_renderer, m_show_texture, NULL, &m_show_rect);
SDL_RenderPresent(m_sdl_renderer);
} return true;
}
主程序中的修改
主要修改位于CMy2_sdl_yuv_playerDlg中,依次添加OnBnClickedButtonPlay、WM_TIMER、WM_DESTORY的消息处理函数,并添加三个输入框的关联变量,m_width、m_height、m_fps。同时定义m_yuv_render用于显示yuv数据。我们将需要的数据通过文件指针的形式保存,每次读取一帧YUV数据。
首先看一下OnBnClickedButtonPlay的功能,需要调用打开对话框,选择指定的yuv,分配资源,启动定时器,相关实现如下:
enum{
DFT_WIDTH = ,
DFT_HEIGHT = ,
DFT_FPS = ,
SHOW_TIMER_ID = WM_USER + ,
};
bool CMy2_sdl_yuv_playerDlg::InitRender(CString file_path)
{
UpdateData(TRUE);
m_plane_size = (m_width * m_height) >> ;
m_frame_length = m_plane_size * ;
m_frame_data = new unsigned char[m_frame_length];
if (nullptr == m_frame_data)
{
return false;
}
m_plane_size <<= ;
m_in_file = nullptr;
if ( != fopen_s(&m_in_file, (LPCTSTR)file_path, "rb"))
{
CString strMsg;
strMsg.Format("open failed! %s", file_path);
AfxMessageBox(strMsg);
return false;
}
CRect rect;
CStatic * pStatic = (CStatic *)GetDlgItem(IDC_STATIC_VIDEO);
pStatic->GetClientRect(&rect);
// 因为SDL_DestoryWindow会调用ShowWindow使窗口隐藏
// 为了实现重复使用播放窗口的目的,这里直接将其显示出来
pStatic->ShowWindow(SW_SHOW);
m_yuv_render.Init(pStatic->GetSafeHwnd(), rect);
ASSERT( != m_fps);
int interval = / m_fps;
SetTimer(SHOW_TIMER_ID, interval, NULL);
return true;
}
void CMy2_sdl_yuv_playerDlg::DeinitRender()
{
if (nullptr != m_in_file)
{
KillTimer(SHOW_TIMER_ID);
fclose(m_in_file);
m_in_file = nullptr;
}
m_yuv_render.Deinit();
if (nullptr != m_frame_data)
{
delete [] m_frame_data;
m_frame_data = nullptr;
}
m_plane_size = ;
}
void CMy2_sdl_yuv_playerDlg::OnBnClickedButtonPlay()
{
CString file_name = _T("");
CFileDialog fd(TRUE, NULL,
file_name,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR,
NULL, NULL);
if (fd.DoModal() == IDOK)
{
DeinitRender();
InitRender(fd.GetPathName());
}
}
注意这里额外调用了ShowWindow函数,你可以尝试下看看这个到底有什么功能。相关修改是参考SDL2.0的源码中SDL_DestroyWindow实现。
定时消息处理函数的基本功能是读取一帧yuv,渲染,如果文件到头,重置文件指针。
void CMy2_sdl_yuv_playerDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == SHOW_TIMER_ID && nullptr != m_in_file)
{
size_t read_size = fread(m_frame_data, , m_frame_length, m_in_file);
if(read_size == m_frame_length)
{
unsigned char *src[] = {NULL}; //Y、U、V数据首地址
src[] = m_frame_data;
src[] = src[] + m_plane_size;
src[] = src[] + (m_plane_size>>);
int stride[] = {m_width, m_width/, m_width/};
m_yuv_render.Update(m_width, m_height, src, stride);
m_yuv_render.Render();
}
else
{
// 循环播放
fseek(m_in_file, , SEEK_SET);
}
} CDialogEx::OnTimer(nIDEvent);
}
WM_DESTROY函数主要做必要的退出处理,并清理SDL的资源。
void CMy2_sdl_yuv_playerDlg::OnDestroy()
{
CDialogEx::OnDestroy(); DeinitRender(); if (SDL_WasInit())SDL_Quit();
}
最终程序运行效果如下图:

总结
在BS4的基础上实现YUV播放器相对比较简单,整理这篇文章主要目的在于梳理SDL中视频渲染机制,同时提供尽可能直接的YUV渲染方法。
相关代码可以从我的git下载,url如下:https://git.oschina.net/Tocy/SampleCode.git,位于TocySDL2VisualTutorial目录下。
Beginning SDL 2.0(5) 基于MFC和SDL的YuvPlayer的更多相关文章
- Beginning SDL 2.0(4) YUV加载及渲染
本文主要内容是基于的“Beginning SDL 2.0(3) SDL介绍及BMP渲染”(以下简称BS3)基础上,将BMP加载及渲染修改为YUV420或I420的原始视频格式.阅读完本部分内容相信你可 ...
- Beginning SDL 2.0(3) SDL介绍及BMP渲染
SDL是一个跨平台的多媒体库.为了实现跨平台,SDL提供了一个简单的界面库抽象,比如提供了SDL_Window用于表示窗口句柄,SDL_Surface.SDL_Texture.SDL_Renderer ...
- Beginning SDL 2.0(2) TwinklebearDev SDL 2.0 Tutorial
本文整理并简要介绍了TwinklebearDev SDL 2.0 Tutorial相关内容(以下简称TDSDLTutorial). 这是作为我学习并了解SDL2.0功能一篇学习总结. TDSDLTut ...
- 基于MFC和opencv的FFT
在网上折腾了一阵子,终于把这个程序写好了,程序是基于MFC的,图像显示的部分和获取图像的像素点是用到了opencv的一些函数,不过FFT算法没有用opencv的(呵呵,老师不让),网上的二维的FFT程 ...
- 基于MFC的socket编程(异步非阻塞通信)
对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手.许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清, ...
- SDL 开发实战(二):SDL 2.0 核心 API 解析
在上一篇文章 SDL 开发实战(一):SDL介绍及开发环境配置 中,我们配置好了SDL的开发环境,并成功运行了SDL的Hello World 代码.但是可能大部分人还是读不太明白具体Hello Wol ...
- 基于MFC开发的指纹识别系统.
MFC-FingerPrint 基于MFC开发的指纹识别系统. 效果图如下: 在第12步特征入库中,会对当前指纹的mdl数据与databases中所有的mdl进行对比,然后返回识别结果. 一.载入图像 ...
- 最全的基于MFC的ActiveX控件开发教程
浏览器插件之ActiveX开发(一) 一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题.在有特殊需求(如涉及数据安全的金融业务 ...
- 基于MFC的ActiveX控件开发教程------------浏览器插件之ActiveX开发
浏览器插件之ActiveX开发(一) 一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题.在有特殊需求(如涉及数据安全的金融业务 ...
随机推荐
- 系统服务中没有Windows Installer服务怎么办
在安装软件时,发现安装不了,提示没有Windows Installer服务,到系统服务中一看,果真没有这一项,这是什么问题呢? 出现这种情况,多为与Windows Installer服务相关的文件丢失 ...
- 【svn】解析subversion的使用
目录结构: contents structure [-] 安装客户端 安装服务端 创建仓库 启动仓库 创建客户端与仓库取得联系 使用svn服务 SVN密码管理 SVN的仓库布局和常规命令 分支.合并和 ...
- Linux下面安装和配置MySQL
如何从MySQL官方Yum仓库安装MySQL5.6 首先我们需要从MySQL开发者网站下载Yum仓库文件 Download MySQL Yum Repository 从上面的连接地址下载:Red Ha ...
- STL 的string类怎么啦?
前言 上个周末在和我的同学爬香山闲聊时,同学说到STL中的string类曾经让他备受折磨,几年前他开发一个系统前对string类还比较清楚,然后随着程序的复杂度的加深,到了后期,他几乎对strin ...
- 【转】dubbo各种协议
原文地址:http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-协议参考手册 协议参考手册 (+) (#) 推荐使用Dubbo协议 性能测试报告各协议的性能情况 ...
- ThinkPHP 3.2 性能优化,实现高性能API开发
需求分析 目前的业务全站使用ThinkPHP 3.2.3,前台.后台.Cli.Api等.目前的业务API访问量数千万,后端7台PHP 5.6,平均CPU使用率20%. 测试数据 真实业务 php5.6 ...
- Nginx错误提示:504 Gateway Time-out解决方法
朋友说504 Gateway Time-out的错误提示与nginx本身是没有任何关系的我们可以通过fastcgi配置参数的调整进行解 决. 修改 php-fpm 配置文件: 1.把 max_chil ...
- java环境变量设置与java查看安装路径
把jdk安装到计算机中之后,我们来进行设置使java环境能够使用. 首先右键点我的电脑.打开属性.然后选择“高级”里面的“环境变量”,在新的打开界面中的系统变量需要设置三个属性“JAVA_HOME”. ...
- es5 温故而知新 简单继承示例
// 矩形(构造器/父类) function Rectangle (height, width) { this.height = height; this.width = width; } // 获取 ...
- C#基础第五天-作业-用DataTable制作名片集
1.用DataTable集合去实现名片集.(增加,修改,删除,查询,查询全部)需求:根据人名去(删除/查询).指定列:姓名,年龄,性别,爱好,电话. 本系列教程: C#基础总结之八面向对象知识点总结- ...