本文是在“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的更多相关文章

  1. Beginning SDL 2.0(4) YUV加载及渲染

    本文主要内容是基于的“Beginning SDL 2.0(3) SDL介绍及BMP渲染”(以下简称BS3)基础上,将BMP加载及渲染修改为YUV420或I420的原始视频格式.阅读完本部分内容相信你可 ...

  2. Beginning SDL 2.0(3) SDL介绍及BMP渲染

    SDL是一个跨平台的多媒体库.为了实现跨平台,SDL提供了一个简单的界面库抽象,比如提供了SDL_Window用于表示窗口句柄,SDL_Surface.SDL_Texture.SDL_Renderer ...

  3. Beginning SDL 2.0(2) TwinklebearDev SDL 2.0 Tutorial

    本文整理并简要介绍了TwinklebearDev SDL 2.0 Tutorial相关内容(以下简称TDSDLTutorial). 这是作为我学习并了解SDL2.0功能一篇学习总结. TDSDLTut ...

  4. 基于MFC和opencv的FFT

    在网上折腾了一阵子,终于把这个程序写好了,程序是基于MFC的,图像显示的部分和获取图像的像素点是用到了opencv的一些函数,不过FFT算法没有用opencv的(呵呵,老师不让),网上的二维的FFT程 ...

  5. 基于MFC的socket编程(异步非阻塞通信)

       对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手.许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清, ...

  6. SDL 开发实战(二):SDL 2.0 核心 API 解析

    在上一篇文章 SDL 开发实战(一):SDL介绍及开发环境配置 中,我们配置好了SDL的开发环境,并成功运行了SDL的Hello World 代码.但是可能大部分人还是读不太明白具体Hello Wol ...

  7. 基于MFC开发的指纹识别系统.

    MFC-FingerPrint 基于MFC开发的指纹识别系统. 效果图如下: 在第12步特征入库中,会对当前指纹的mdl数据与databases中所有的mdl进行对比,然后返回识别结果. 一.载入图像 ...

  8. 最全的基于MFC的ActiveX控件开发教程

    浏览器插件之ActiveX开发(一) 一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题.在有特殊需求(如涉及数据安全的金融业务 ...

  9. 基于MFC的ActiveX控件开发教程------------浏览器插件之ActiveX开发

    浏览器插件之ActiveX开发(一) 一般的Web应用对于浏览器插件能不使用的建议尽量不使用,因为其涉及到安全问题以及影响用户安装(或自动下载注册安装)体验问题.在有特殊需求(如涉及数据安全的金融业务 ...

随机推荐

  1. [转]NLP Tasks

    Natural Language Processing Tasks and Selected References I've been working on several natural langu ...

  2. SQLDumpSplitter sql文件分割工具

    数据库误操作,只好使用使用原来的备份数据去恢复数据,但是数据量太大,只好使用SQLDumpSplitter将大文件分割成小文件,然后恢复指定的表即可.

  3. idea 不下载jar包

    是因为用的gradle 然后没有设置gradle jvm

  4. ROS学习(四)—— 创建ROS Package

    一.caktin Package的组成 1.必须含有 package.xml文件,提供有关程序包的元信息 2.必须含有一个catkin版本的 CmakeLists.txt文件,如果是一个catkin元 ...

  5. Linux下使用Nexus搭建Maven私服

    在开发过程中,有时候会使用到公司内部的一些开发包,显然把这些包放在外部是不合适的.另外,由于项目一直在开发中,这些内部的依赖可能也在不断的更新.可以通过搭建公司内部的Maven服务器,将第三方和内部的 ...

  6. 忙里偷闲写的小例子---读取android根目录下的文件或文件夹

    最近几天真的是各种意义上的忙,忙着考试,还要忙着课程设计,手上又有外包的项目,另一边学校的项目还要搞,自己的东西还在文档阶段,真的是让人想死啊!! 近半个月来,C#这方面的编码比较多,android和 ...

  7. Oracle 12C -- Unified Auditing Policy

    1.审计策略是一组审计选项,用来审计数据库用户 2.创建审计策略需要被授予audit_admin角色(create audit policy ...) 3.可以在CDB.PDB级别创建创建审计策略 4 ...

  8. Android 自定义可拖拽View,界面渲染刷新后不会自动回到起始位置

    以自定义ImageView为例: /** * 可拖拽ImageView * Created by admin on 2017/2/21. */ public class FloatingImageVi ...

  9. 还没被玩坏的robobrowser(8)——robobrowser的实现原理

    背景 学习使用工具实际上不难,不过我们应该通过阅读工具源码来提升自己的水平. 多读代码,读好代码.很不错,robobrowser的代码简单易懂,值得学习. 预备知识 源码地址 一起其实是从browse ...

  10. proguard的简单配置说明

    #需要转换的jar文件路径-injars 'D:\fs-np.jar'#转换后的jar文件名称-outjars 'D:\fs-np-sec.jar' #关联的第三方jar-libraryjars 'C ...