2607C++,soui,xplayer视频播放器

原文

XPlayer:SOUI5+SDL3打造的高性能``跨平台视频播放器

桌面应用开发字段,跨平台一直很难.特别是对窗口平台的你来说,想要将应用移植到林操平台,往往需要重写大量代码.

今天我要介绍的XPlayer项,带SOUI5+SWINX这套跨平台框架,实现了真正的"一套代码,多平台运行",同时用IHostPresenter机制实现了高效的视频渲染,带来了超一流的流畅体验.

软件下载:XPlayer,基于SOUI5跨平台视频播放器(选择下载windows/debian版本)

一,无缝跨平台:SOUI+SWINX的神奇

1.1传统跨平台的痛点

传统的跨平台方案主要有两个:

方案一:重写UI

1,使用Qt,GTK跨平台``UI框架

2,需要学习新的框架API

3,UI风格与本地应用差异明显

4,代码移动成本巨大

方案二:使用Wine

1,在林操上运行窗口程序

2,兼容问题多

3,性能损耗明显

4,用户体验不佳

1.2SOUI+SWINX的创新方案

SOUI5是启程软件开发DirectUI框架,带XML布局+CSS风格的声明式UI设计,类似网络前端开发模式.

SWINX则是专门为SOUI5开发的跨平台适配层,类似Wine,但更加轻量,更加精准.

核心设计理念:

1,在Linux/macOS上实现窗口客户API

2,保持与窗口接口``完全一致的接口签名.

3,底层使用POSIX本地API实现

4,代码几乎无需修改即可跨平台运行

1.3SWINX的实现机制

SWINX以下机制实现跨平台:

1.HWND``窗口系统

cpp 复制代码
//`窗口平台`
HWND hWnd = CreateWindowEx(...);
//`林操`平台(`SWINX`实现)
HWND hWnd = CreateWindowEx(...);
//`完全相同``API!`

林操上,SWINX使用X11``创建窗口,窗柄实际上是一个xcb_window_t类型的X11``窗口标识;在马操上,窗柄是一个NSView*指针.

该设计SDL3的窗口定义保持一致,SDL3窗口上使用窗柄,在林操上使用xcb_window_t,在马操上使用NSView*.SWINX这样实现了与底层平台无缝对接.

2.内核对象同步SWINX实现了完整的窗口``内核对象系统:

1,事件(事件)

2,信号量(信号量)

3,互斥锁(互斥量)

4,WaitableTimer(可等待计时器)

特别注意,SWINX不用pthread_cond_wait,而是用select+pipe/fifo的机制实现等待功能,该设计避免了pthread``条件变量复杂性,同时保持了与窗口语义的一致性.

3.GDI绘画系统SWINX使用Cairo``图形库实现了GDI,API:

1,HDC(设环)

2,位图(位图)

3,字体(字体)

4,Brush/Pen(画刷/画笔)

窗口上的GDI``绘画代码可直接在林操上运行,无需修改.

4.管理资源系统SWINX使用MinGWwindres工具在林操上编译.rc``资源文件,在ELF``二进制文件中嵌入窗口资源,运行时使用标准窗口资源API访问.

1.4XPlayer跨平台效果

XPlayer项完美展示了SOUI+SWINX跨平台能力:

窗口版本:

使用本地窗柄窗口

GDI渲染UI

所有功能正常运行

林操版本:

使用X11窗口(SWINX)

Cairo渲染UI(SWINX)

代码完全相同,仅编译目标不同

你只需要维护一套代码,CMake配置不同编译目标即可生成窗口林操版本.该"零成本``跨平台"的体验,是传统跨平台方案无法比拟的.

二,IHostPresenter:UI与视频的完美整合

2.1传统视频播放器的渲染问题

传统的视频播放器一般带以下渲染方案:

方案一:独立窗口叠加

视频使用独立窗口渲染

UI控件叠加在视频窗口

问题:窗口管理复杂,动画效果受限

方案二:GDI/Direct2D渲染视频

UI窗口绘画视频帧.

问题:性能差,无法硬件加速

方案三:嵌入浏览器

使用WebView嵌入播放器

问题:资源占用大,本地体验差

2.2IHostPresenter的创新设计

SOUIIHostPresenter接口提供了全新渲染方案:将UI渲染转发给自定义``渲染器.

cpp 复制代码
class IHostPresenter {
public:
//`创建窗口`时调用
    void OnHostCreate();
//`窗口析构`时调用
    void OnHostDestroy();
//改变`窗口大小`时调用
    void OnHostResize(SIZE szHost);
//`UI`渲染完成时调用
    void OnHostPresent(HDC hdc, IRenderTarget *pMemRT, LPCRECT pRect, BYTE byAlpha);
};

实现该接口,你可完全控制``UI的渲染进程,将SOUI生成的UI数据交给任何渲染器处理.

2.3SdlPresenter的实现原理

XPlayer使用SdlPresenter类实现了IHostPresenter接口,将SDL3硬件加速能力与SOUIUI渲染完美结合.

核心实现流程:

1.创建窗口与绑定

cpp 复制代码
void SdlPresenter::OnHostCreate() {
    SDL_PropertiesID props = SDL_CreateProperties();
    #ifdef _WIN32
//`窗口平台`:绑定`窗柄`
    SDL_SetPointerProperty(props,
        SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER,
        m_pHostWnd->GetHwnd());
    #elif defined(__linux__)
//`林操`平台:绑定`X11`窗口
    SDL_SetNumberProperty(props,
        SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER,
        (Sint64)m_pHostWnd->GetHwnd());
    #endif
    m_sdlWnd = SDL_CreateWindowWithProperties(props);
    m_sdlRenderer = SDL_CreateRenderer(m_sdlWnd, NULL);
}

SDL3提供了SDL_CreateWindowWithProperties该新API,允许在已存在的本地窗口上绑定SDL窗口.这样,SOUI的窗口和SDL渲染器就完美结合在一起了.

2.UI``纹理缓存

cpp 复制代码
void SdlPresenter::OnHostPresent(HDC hdc, IRenderTarget *pMemRT, ...) {
    IBitmapS* pBmp = (IBitmapS*)pMemRT->GetCurrentObject(OT_BITMAP);
//创建或更新`UI``纹理`
    m_wndTexture = SDL_CreateTexture(m_sdlRenderer,
        SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STREAMING,
        pBmp->Width(), pBmp->Height());
//复制`UI`像素数据到`纹理`
    SDL_LockTexture(m_wndTexture, &rcSdl, &pDst, &pitch);
    memcpy(pDst, pBmp->GetPixelBits(), pitch * rcSdl.h);
    SDL_UnlockTexture(m_wndTexture);
}

SOUI渲染完成后,会调用OnHostPresent方法,将渲染结果(ARGB像素数据)传递给SdlPresenter.SdlPresenterSDL``纹理缓存转换这些数据.

3.更新视频帧

cpp 复制代码
void SdlPresenter::UpdateVideoCanvas(int canvasId, IAVframe *frame) {
//创建`YUV``纹理`
    it->second.texture = SDL_CreateTexture(m_sdlRenderer,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        frame->width(), frame->height());
//使用`SDL3`的`YUV`更新`API`
    SDL_UpdateYUVTexture(it->second.texture, NULL,
        frame->getData(), frame->lineSize()[0],
        ...);
}

视频解码线程产生的YUV帧数据,UpdateVideoCanvas方法异步更新到SDL``纹理.这里使用了SDL_PIXELFORMAT_IYUV格式,支持硬件加速渲染.

4.混合渲染

cpp 复制代码
void SdlPresenter::UpdateRender() {
    SDL_RenderClear(m_sdlRenderer);
//渲染视频`纹理`(底层)
    for (auto& it : m_videoTextures) {
        if (it.second.bVisible && it.second.texture) {
            SDL_RenderTexture(m_sdlRenderer, it.second.texture, NULL, &rcDst);
        }
    }
//渲染`UI``纹理`(顶级,带`透明`)
    if (m_wndTexture) {
        SDL_RenderTexture(m_sdlRenderer, m_wndTexture, NULL, NULL);
    }
    SDL_RenderPresent(m_sdlRenderer);
}

最终的渲染流程非常简洁:先渲染视频层,再渲染UI层.因为UI``纹理设置了SDL_BLENDMODE_BLEND``混合模式,UI元素可完美叠加在视频上,支持透明效果.

2.4技术优势

IHostPresenter+SDL3的方案带来了诸多优势:

1.单窗口架构

1,所有内容在同一个窗口中渲染

2,避免了多窗口叠加的复杂性

3,窗口管理更加简洁

2.硬件加速

1,SDL3自动选择最佳渲染后端(OpenGL/Vulkan/Metal/Direct3D)

2,视频解码UI渲染都享受GPU加速

3,性能远超GDI方案

3.完美混合

1,UI``纹理支持透明

2,视频和UI无缝叠加

3,可实现半透明控制栏等效果

4.异步更新

1,视频帧更新不阻塞UI线程

2,UI渲染不阻塞视频解码

3,两者独立运行,互不干扰

5.跨平台``一致性

1,窗口林操使用相同渲染代码

2,SDL3在各平台提供一致的渲染效果

3,用户体验``完全一致

三,SOUI``动画系统:流畅的可视体验

3.1幕布动画:戏剧化的开场

XPlayer视频播放开始时,会展示一个幕布拉开的效果,该戏剧化的设计大大提升用户体验.

实现原理:

幕布由两个半透明的窗口组成,使用SVG绘画的褶皱效果.播放开始时,SOUI动画系统实现幕布向两侧滑动的效果:

cpp 复制代码
void CMainDlg::OnBtnPlay() {
//左幕布向左滑动隐藏
    IAnimation* pAni = SApplication::getSingleton().LoadAnimation(
        UIRES.anim.slide_hide_left);
    FindChildByID(R.id.curtainLeft)->SetAnimation(pAni);
    pAni->Release();
//右幕布向右滑动隐藏
    IAnimation* pAni2 = SApplication::getSingleton().LoadAnimation(
        UIRES.anim.slide_hide_right);
    FindChildByID(R.id.curtainRight)->SetAnimation(pAni2);
    pAni2->Release();
}

XML资源中定义动画,使用相对位移百分比方式:

xml 复制代码
<!-- 左幕布滑出:从0%移动至-100%(向左滑出),持续1800ms -->
<animation name="slide_hide_left" type="translate">
  <translate duration="1800" fromXDelta="0" toXDelta="-100%" fromYDelta="0" toYDelta="0"/>
</animation>
<!-- 右幕布滑出:从0%移动至100%(向右滑出),持续1800ms -->
<animation name="slide_hide_right" type="translate">
  <translate duration="1800" fromXDelta="0" toXDelta="100%" fromYDelta="0" toYDelta="0"/>
</animation>

幕布滑出的动画持续1800ms(约2秒),给用户一个戏剧性的"大幕拉开"效果.播放结束时,幕布会同样滑回合上.

3.2``提示动画:优雅的消息反馈

XPlayer使用自定义PlayTip方法显示提示消息,而不是传统的消息框.提示框会从顶底部滑入,停留5秒后自动向上滑出消失:

cpp 复制代码
void CMainDlg::PlayTip(const SStringT& strTip) {
//从模板创建`提示`窗口
    SXmlNode xmlTip = GETUIDEF->GetTemplate(L"txt_tip").first_child();
    IWindow* pTip = SApplication::getSingletonPtr()->CreateWindowByName(xmlTip.name());
    FindChildByID(R.id.wnd_toast_host)->InsertIChild(pTip);
    pTip->InitFromXml(&xmlTip);
    pTip->SetWindowText(strTip);
//设置`目标位置`:顶部居中
    AnchorPos toPos;
    toPos.type = APT_Center_Top;
    toPos.x = SLayoutSize(-10, dp);
    toPos.y = SLayoutSize(-10, dp);
    toPos.fOffsetX = -0.5f;
    toPos.fOffsetY = -0.5f;
//`播放动画`并5秒后`自动隐藏`
    MoveAndHideSprite(pTip, fromPos, toPos, 5000);
}

动画的实现使用了属性值动画器SPropertyAnimator,同时改变位置和透明:

cpp 复制代码
static SAutoRefPtr<IValueAnimator> MoveAndHideSprite(IWindow* pSprite,
    AnchorPos fromPos, AnchorPos toPos, int nSpeed) {
//创建位置动画
    AnchorPos pos[] = { fromPos, toPos };
    SAutoRefPtr<IPropertyValuesHolder> pPosHolder(
        SPropertyValuesHolder::ofPosition(LayoutProperty::POSITION,
            pos, ARRAYSIZE(pos), sizeof(AnchorPos)), FALSE);
//创建`透明`动画:从暗到`透明`
    BYTE alpha[] = { 255, 0 };
    SAutoRefPtr<IPropertyValuesHolder> pAlphaHolder(
        SPropertyValuesHolder::ofByte(WindowProperty::ALPHA, alpha, 2), FALSE);
//组合动画
    IPropertyValuesHolder* holders[] = { pPosHolder, pAlphaHolder };
    SAutoRefPtr<IValueAnimator> pAnimator(
        SPropertyAnimator::ofPropertyValuesHolder(pSprite, holders, 2), FALSE);
    pAnimator->setDuration(nSpeed);
//`nSpeed`为`5000ms`
    return pAnimator;
}

该动画提示比传统的消息框更加优雅,不会打断用户的操作过程,同时提供了更好的可视反馈.

3.3控制栏动画:智能的接口交互

XPlayer的控制栏在鼠标静止自动隐藏,移动鼠标平滑显示:

cpp 复制代码
void CMainDlg::OnMouseMove() {
    if (!m_isBarVisible) {
//显示控制栏
        IAnimation* pAni = SApplication::getSingleton().LoadAnimation(
            UIRES.anim.slide_show_bottom);
        FindChildByID(R.id.controlBar)->SetAnimation(pAni);
        m_isBarVisible = TRUE;
    }
//重置隐藏`计时器`
    SetTimer(TM_DELAY_HIDE, 3000, [&]() {
//隐藏控制栏
        IAnimation* pAni = SApplication::getSingleton().LoadAnimation(
            UIRES.anim.slide_hide_bottom);
        FindChildByID(R.id.controlBar)->SetAnimation(pAni);
        m_isBarVisible = FALSE;
    });
}

该智能的接口交互,让用户在观看视频时不受控制栏干扰,需要操作时又能快速取得反馈.

四,技术总结与展望

XPlayer项展示了SOUI5+SWINX+SDL3这套技术栈的强大能力:

跨平台能力:

一套代码,Windows/Linux双平台运行

无需学习新的框架API

保持本地应用的用户体验

渲染能力:

IHostPresenter机制实现UI与视频的完美混合

SDL3硬件加速带来高性能渲染

单窗口架构简化了窗口管理

动画能力:

SOUI``动画系统提供流畅的可视效果

XML定义动画,代码调用简单

支持多种动画类型(位移,透明,缩放等)

这套技术栈不仅适合视频播放器,还可应用在:

1,游戏客户端

2,直播软件

3,视频会议软件

4,多媒体编辑软件

任何需要UI与视频/图形混合渲染的应用,都可借鉴这套方案.

五,开源与贡献

XPlayer是基于SOUI5开源框架开发的,SOUI5带开源协议,SWINX带开源但不免费的协议(非商用免费,商用收费).

如果对这套技术感兴趣,欢迎:

1,加入SOUI开发社区(QQ组:229313785,385438344)

2,参考SOUI5源:https://gitee.com/setoutsoft/soui4,这里

3,参考SWINX源:https://gitee.com/setoutsoft/swinx,这里

让我们一起推动国产UI框架的发展,打造更多优秀的跨平台应用!

相关推荐
zh路西法2 小时前
【10天速通ROS2-PX4无人机】(四) 关掉GPS和气压计,纯激光定位还能飞吗
c++·无人机·px4·ros2·卡尔曼滤波·fastlio2
yyds_yyd_100863 小时前
1464. 数组中两元素的最大乘积(2026.07.27)
数据结构·c++·算法·leetcode
库克克5 小时前
【C++】set 与multiset
开发语言·c++
Mortalbreeze6 小时前
深入 Linux Socket 编程:端口号、网络字节序与 struct sockaddr 详解
linux·服务器·网络·c++
繁星蓝雨7 小时前
C++设计原理——异常处理
java·c++·异常处理·noexcept·throw·try catch
Kurisu_红莉栖7 小时前
关于相关问题的自我回答
c++
hansang_IR7 小时前
【题解】[AGC020E] Encoding Subsets
c++·算法·dp
我不管我就要叫小猪8 小时前
C/C++----命名空间
c语言·开发语言·c++
无忧.芙桃9 小时前
数据结构之堆
c语言·数据结构·c++·算法·