增强axmol引擎视频播放之 - Linux(Ubuntu)视频播放支持

引言

接续“增强axmol引擎视频播放之 - android视频播放支持”,本文主要描述如何在 著名的Linux发行版Ubuntu支持视频渲染到纹理播放。如无特殊说明,一下描述中出现Linux均指的是Ubuntu Linux,版本Ubuntu 22.04+

为什么开发

当笔者成功完成了android视频纹理渲染后,axmol引擎所支持的平台(macos,windows,ios/tvos,android,linux),就只剩下Linux了,因此萌生了何不顺带把Linux支持下。

调研

经过研究发现,Linux下比较优秀的播放器里,VLC提供了libvlc LGPL-2.1授权(不修改源码的情况下,以动态库形式免费商用),因此集成libvlc是比较好的方案。

如何实现

依然是实现axmol的MediaEngine接口,不过我们使用libvlc作为backend

  1. 安装libvlc开发包

    sudo apt install libvlc-dev libvlccore-dev vlc
    

    其中libvlc-devlibvlccore-dev是编译连接必须的,而安装vlc实际上是安装播放器和插件.so,其中播放器不需要,插件才是运行必须的,否则运行就会无法解码视频。
    TIPS: 插件加载潜规则:

    • 默认情况下,当调用libvlc_new API时,libvlc会从libvlc.solibvlccore.so库所在目录下的plugins目录搜索播放视频所需解码插件。安装vlc会将插件放到正确的路径
    • 通过环境变量: VLC_PLUGIN_PATH指定libvlc加载插件路径
      • Linux可通过API setenv动态设置:setenv("VLC_PLUGIN_PATH", "/path/to/vlc/plugins", true);
      • Windows可通过API _putenv_s 设置,但特别注意需要根据libvlc.dlllibvlccore.dll以来的c运行库决定,官方预编译好的libvlc库使用的是msvcrt,意味着在win10系统上,直接调用_putenv_s是无效的,直接调用_putenv_s通常会设置ucrt里的环境变量,msvcrtucrtbased/ucrtbase不同C运行库共存时,内存时隔离的,如果想时两种运行库都生效,可通过如下方式设置:
        _putenv_s("VLC_PLUGIN_PATH", R"(D:\dev\axmol\thirdparty\vlc\win\lib\plugins)");
        
        HMODULE hmsvcrt = GetModuleHandleW(L"msvcrt.dll");
        auto msvcrt_getenv = (decltype(getenv)*)GetProcAddress(hmsvcrt, "getenv");
        
        if (msvcrt_getenv)
        {
            auto msvcrt_putenv_s = (decltype(_putenv_s)*)GetProcAddress(hmsvcrt, "_putenv_s");
            if (msvcrt_putenv_s)
                msvcrt_putenv_s("VLC_PLUGIN_PATH", R"(D:\dev\axmol\thirdparty\vlc\win\lib\plugins)");
        }
        
  2. 编写代码实现MediaEngine的核心功能,如播放,暂停等,注册callbacks(媒体事件,视频NV12 Frame事件)等功能。

  3. 关于如何让获取VLC解码的NV12视频数据(网上多数都是RGBA),本文只贴出关键代码

    libvlc lock回调中,为本次视频数据frame锁定内存以便写入:

    static constexpr auto VLC_OUTPUT_FORMAT = ax::MEVideoPixelFormat::NV12;
    
    void* VlcMediaEngine::libvlc_video_lock(void* data, void** p_pixels)
    {
        VlcMediaEngine* mediaEngine = static_cast<VlcMediaEngine*>(data);
    
        auto& bufferDim    = mediaEngine->_videoDim;
        auto& outputBuffer = mediaEngine->_frameBuffer1;
        mediaEngine->_frameBuffer1Mtx.lock();
        if constexpr (VLC_OUTPUT_FORMAT == ax::MEVideoPixelFormat::NV12)
        {
            outputBuffer.resize_fit(bufferDim.x * bufferDim.y + (bufferDim.x * bufferDim.y >> 1));  // NV12
            p_pixels[0] = outputBuffer.data();
            p_pixels[1] = outputBuffer.data() + (bufferDim.x * bufferDim.y);
        }
        else if constexpr (VLC_OUTPUT_FORMAT == ax::MEVideoPixelFormat::YUY2)
        {
            outputBuffer.resize_fit(bufferDim.x * bufferDim.y + ((bufferDim.x >> 1) * bufferDim.y * 4));  // YUY2
            p_pixels[0] = outputBuffer.data();
        }
        else
        {
            outputBuffer.resize_fit(bufferDim.x * bufferDim.y * 4);  // RGBA32
            p_pixels[0] = outputBuffer.data();
        }
        return nullptr;
    }
    void VlcMediaEngine::libvlc_video_unlock(void* data, void* id, void* const* p_pixels)
    {
        VlcMediaEngine* mediaEngine = static_cast<VlcMediaEngine*>(data);
        mediaEngine->_frameBuffer1Mtx.unlock();
        ++mediaEngine->_frameIndex;
        assert(id == nullptr);
    }
    

    libvlc使用回调模式格式设置,值得注意的是NV12需要指定第二个planepitch(bytesPerRow)和lines(视频期望渲染高度/2)

    unsigned int VlcMediaEngine::libvlc_video_format_setup(void** opaque,
                                                           char* chroma,  // forcc, refer to:vlc_fourcc.h
                                                           unsigned* width,
                                                           unsigned* height,
                                                           unsigned* pitches,
                                                           unsigned* lines)
    {
        // refer to: vmem.c:Open https://github.com/videolan/vlc/blob/3.0.18/modules/video_output/vmem.c#L150
        // future: 4.0: will be widths, heights:
        // https://github.com/videolan/vlc/blob/master/modules/video_output/vmem.c#L156
        VlcMediaEngine* mediaEngine = static_cast<VlcMediaEngine*>(*opaque);
    
        // vlc tell us the original codecDim(ALIGNED)
        mediaEngine->_codecDim.set(width[0], height[0]);
    
        // tell vlc we want render as video size
        width[0]  = mediaEngine->_videoDim.x;
        height[0] = mediaEngine->_videoDim.y;
    
        // plane0
        pitches[0] = width[0];   // bytesPerRow
        lines[0]   = height[0];  // rows
    
    #    if LIBVLC_VERSION_MAJOR >= 4
        mediaEngine->_videoDim.set(width[1], height[1]);
    #    endif
    
        int num_of_plane = 1;
    
        if constexpr (VLC_OUTPUT_FORMAT == ax::MEVideoPixelFormat::NV12)
        {
            memcpy(chroma, "NV12", 4);
    
            // plane1
            pitches[1] = mediaEngine->_videoDim.x;       // bytesPerRow
            lines[1]   = mediaEngine->_videoDim.y >> 1;  // rows
    
            num_of_plane = 2;
        }
        else if constexpr (VLC_OUTPUT_FORMAT == ax::MEVideoPixelFormat::YUY2)
        {
            memcpy(chroma, "YUY2", 4);
            pitches[0] = width[0] * 2;  // bytesPerRow
        }
        else
        {
            memcpy(chroma, "RGBA", 4);
            pitches[0] = width[0] * 4;  // bytesPerRow
        }
    
        // return the number of picture buffers allocated, 0 indicates failure
        return num_of_plane;
    }
    

运行和注意事项

请下载axmol引擎最新源码在Ubuntu 22.04+系统下编译运行。如果遇到播放视频出现无法解码问题,请安装ubuntu扩展组件,命令如下:

sudo apt install ubuntu-restricted-extras

结语

自此,axmol引擎的ui::MediaPlayer支持全部平台上的视频纹理渲染播放,这也得益于重新设计的MediaEngine 框架,该框架不仅能在axmol引擎中工作,也可以独立编译,例如笔者编写的bgfx视频播放示例:bgfx-axplay
另外:libvlc本身是跨平台的,因此本文中的实现VlcMediaEngine经过简单配置也可以运行在其他平台,例如Windows

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值