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使用MinGW的windres工具在林操上编译.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的创新设计
SOUI的IHostPresenter接口提供了全新渲染方案:将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的硬件加速能力与SOUI的UI渲染完美结合.
核心实现流程:
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.SdlPresenter按SDL``纹理缓存转换这些数据.
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框架的发展,打造更多优秀的跨平台应用!