文档章节

Beginning SDL 2.0(5) 基于MFC和SDL的YuvPlayer

Tocy
 Tocy
发布于 2015/09/09 22:15
字数 1344
阅读 58
收藏 0

本文是在“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[3], int stride[3]);
    bool Render();

private:
    bool CreateTexture(int width, int height);
    void FillTexture(unsigned char *data[3], int stride[3]);

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();
}
View Code

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 > 0 && width < 10000);
    ASSERT(height > 0 && height < 10000);

    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[3], int stride[3])
{
    void * pixel = NULL;
    int pitch = 0;
    if(0 == 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[0];
        for (int i = 0; i < h; ++i)
        {
            memcpy(dst, src, w);
            dst += pitch;
            src += stride[0];
        }

        h >>= 1;
        w >>= 1;
        pitch >>= 1;
        // for U
        for (int i = 0; i < h; ++i)
        {
            memcpy(dst, src, w);
            dst += pitch;
            src += stride[1];
        }

        // for V
        for (int i = 0; i < h; ++i)
        {
            memcpy(dst, src, w);
            dst += pitch;
            src += stride[2];
        }
        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[3], int stride[3])
{
    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 = 720, 
    DFT_HEIGHT = 576,
    DFT_FPS = 25,

    SHOW_TIMER_ID = WM_USER + 1,
};


bool CMy2_sdl_yuv_playerDlg::InitRender(CString file_path)
{
    UpdateData(TRUE);
    m_plane_size = (m_width * m_height) >> 2;
    m_frame_length = m_plane_size * 6;
    m_frame_data = new unsigned char[m_frame_length];
    if (nullptr == m_frame_data)
    {
        return false;
    }
    m_plane_size <<= 2;

    m_in_file = nullptr;
    if (0 != 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(0 != m_fps);
    int interval = 1000 / 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 = 0;
}

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, 1, m_frame_length, m_in_file);
        if(read_size == m_frame_length)
        {
            unsigned char *src[3] = {NULL};        //Y、U、V数据首地址
            src[0] = m_frame_data;
            src[1] = src[0] +  m_plane_size;
            src[2] = src[1] + (m_plane_size>>2);
            int stride[3] = {m_width, m_width/2, m_width/2};
            m_yuv_render.Update(m_width, m_height, src, stride);
            m_yuv_render.Render();
        }
        else
        {
            // 循环播放
            fseek(m_in_file, 0, SEEK_SET);
        }
    }

    CDialogEx::OnTimer(nIDEvent);
}

WM_DESTROY函数主要做必要的退出处理,并清理SDL的资源。

void CMy2_sdl_yuv_playerDlg::OnDestroy()
{
    CDialogEx::OnDestroy();

    DeinitRender();

    if (SDL_WasInit(0))SDL_Quit();
}

 

最终程序运行效果如下图:

 

总结

在BS4的基础上实现YUV播放器相对比较简单,整理这篇文章主要目的在于梳理SDL中视频渲染机制,同时提供尽可能直接的YUV渲染方法。

相关代码可以从我的git下载,url如下:https://git.oschina.net/Tocy/SampleCode.git,位于TocySDL2VisualTutorial目录下。

© 著作权归作者所有

共有 人打赏支持
Tocy
粉丝 27
博文 50
码字总数 59635
作品 0
海淀
程序员
私信 提问
《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leixiaohua1020/article/details/47068015 这两天开始带广播电视工程大二的暑假小学期的课程设计了。本次小学...

雷霄骅
2015/07/26
0
0
修改了一个YUV/RGB播放器

最近在学习过程中查看YUV/RGB像素数据的时候,发现找不到一个合适的播放器。主流的YUV播放器大多只支持YUV格式播放,却不支持RGB格式数据播放。而我正好需要查看RGB像素数据。在Sourceforge...

leixiaohua1020
2016/01/06
0
0
linux 配置 clang++ SDL 开发环境 (新手向)

前两天看到一篇介绍 clang 的文章 大为心动 新版本的 clang 比 gcc 支持 c++11 的东西还要多。 于是就想着配一个clang的环境。一开始在我的mingw下面配,结果不支持64位。无奈从虚拟机里面装...

架构梦想
2012/12/19
0
0
SDL+Rose:让跨平台编程返璞归真

十多年前,只要学会C/C++,基本就能写所有平台程序。十多年后的今天,说要用C/C++写各平台程序,不少人会认为不可能,原因很多,像平台原生语言就不是C/C++,程序要求部署灵活。——但是,程...

rose-sdk
2015/07/06
871
1
VS2017解决scanf语法错误

1、在程序最前面加: 2、在程序最前面加: 3、把scanf改为scanf_s; 4、消勾选“SDL检查” 无需在程序最前面加那行代码,只需在新建项目时取消勾选“SDL检查”即可; 5、现目属性中关闭SDL 若...

从梦流风
2018/06/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

SpringMVC工作原理

SpringMVC的工作原理图: SpringMVC流程 1、 用户发送请求至前端控制器DispatcherServlet。 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3、 处理器映射器找到具体的处理...

呵呵哒灬
23分钟前
1
0
数据库技术-Mysql主从复制与数据备份

数据库技术-Mysql 主从复制的原理: MySQL中数据复制的基础是二进制日志文件(binary log file)。一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以“事件”...

须臾之余
昨天
12
0
Git远程仓库——GitHub的使用(一)

Git远程仓库——GitHub的使用(一) 一 、 Git远程仓库 由于你的本地仓库和GitHub仓库之间的传输是通过SSH加密的,所以需要一下设置: 步骤一、 创建SSH key 在用户主目录下,看看有没有.ss...

lwenhao
昨天
4
0
SpringBoot 整合

springBoot 整合模板引擎 SpringBoot 整合Mybatis SpringBoot 整合redis SpringBoot 整合定时任务 SpringBoot 整合拦截器...

细节探索者
昨天
1
0
第二个JAVA应用

第二个JAVA应用 方法一:配置文件: # cd /usr/local/tomcat/conf/# vim server.xml</Host> <Host name="www.wangzb.cc" appBase="/data/wwwroot/www.wangzb.cc" //引用所......

wzb88
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部