zoukankan      html  css  js  c++  java
  • 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 YUV, 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 YUV, 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目录下。

  • 相关阅读:
    [APIO2014]序列分割
    [USACO08MAR]土地征用Land Acquisition
    Cats Transport
    [ZJOI2007]仓库建设
    [CEOI2004]锯木厂选址
    Print Article
    Interval 间隔问题
    Trie树
    动态规划-子数组乘积小于k的总个数 Subarray Product Less Than K
    动态规划-独特的子字符串存在于Wraparound String总个数 Unique Substrings in Wraparound String
  • 原文地址:https://www.cnblogs.com/tocy/p/Beginning-SDL2-5-YUV-Player.html
Copyright © 2011-2022 走看看