MediaBox (MBox64)





5.00/5 (3投票s)
Windows 10 多媒体播放器 64 位
引言
MediaBox (MBox64) 被设计用于 WinDows 10 Creators+,使用 DirectX 和 Media Foundation (视频)、OpenGL (视觉插件)、GDImage64.dll (图形)、WinLIFT64.dll (皮肤)、Bass.dll (音频)、AudioGenie3.dll (mp3 标签音频) 的组合。
该 64 位项目使用 C++ 编写,基于 Visual Studio VS 2015/2017 社区版。
背景
这是我十多年前开始的多个项目发展的最终成果。
“C# 电影播放器”,于 2007 年 5 月发布在 CodeProject 上
https://codeproject.org.cn/Articles/18552/C-Movie-Player
“BassBox”音频播放器,首次发布于 2007 年 10 月的 José Roca 论坛
http://www.jose.it-berater.org/smfforum/index.php?board=179.0
Work In Project 项目托管在我私人论坛的专用 WIP 部分
http://www.objreader.com/index.php?topic=80.0
OpenGL 视觉插件主要是过去几年为 BassBox 播放器编写的插件的 64 位移植版 (也用于多个商业应用程序)。
视觉插件的源代码可在 www.objreader.com 获取 (需要先注册)。
PNG 动画基于之前在此处 CodeProject 上发布的专有格式
https://codeproject.org.cn/Articles/1181320/PNG-animation
音频代码是 AudioSession API 和 BASS.dll (www.un4seen.com) 的混合,BASS.dll 能够播放更广泛的音频格式,包括音轨音乐格式。
使用代码
由于我使用多种语言进行开发,我主要使用过程式核心的扁平 API 语法,这是所有语言都能理解的唯一共同点。这也是生成小型二进制文件而无需任何额外框架的最佳方式。
整个界面基于 GDImage 精灵控件的使用,这些控件就像真正的控件一样工作。它们在复合模式下工作,使用 alpha 通道在全屏模式下隐藏它们。
代码被设计用于 Windows 10 Creators+ 版本,以支持 MKV 视频。但是,建议先安装 CCCP 64 位视频编解码器,以便使用 DirectX 并检测正确的视频大小 (我还没有找到一个 Media Foundation API 能够实现与 DirectX GetVideoSize 相同的功能)。
“MediaCheckExtension”函数包含所有支持的媒体列表,根据文件名扩展名返回正确的媒体类型。
注意:如果遇到音频文件,优先使用 Bass.dll。
long MediaCheckExtension(IN WCHAR* zFileName) {
long nRet = 0;
WCHAR drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT + 1];
if (zFileName) {
wsplitpath(zFileName, drive, dir, fname, ext);
if (lstrlen(ext)) {
Add_Str(ext, L".");
CharLower(ext);
if (wcsstr(L".bmp.dib.gif.ico.jpg.jpeg.png.tif.tiff.", ext)) { return MEDIA_IMAGE; }
if (wcsstr(L".avi.wmv.mpeg.mpg.mov.qt.mkv.mp4.flv.", ext)) { return MEDIA_VIDEO; }
if (wcsstr(L".mp3.wav.aac.asf.m4a.wma.3gp.3g2.", ext)) { nRet = MEDIA_AUDIO; }
if (wcsstr(L".mp3.wav.ogg.aif.mo3.it.xm.s3m.mtm.mod.umx.", ext)) { nRet = MEDIA_BASS_AUDIO; }
}
}
return nRet;
}
GDImage 精灵控件的所有处理都在“GDImageControls.h”中完成,尤其是在“GDImageCallBack”函数内部。控件的创建在“InitGDImageControls”中完成。
为什么使用 GDImage?
因为对于此项目以复合模式工作,使用多个透明层(如 PhotoShop)非常重要。DWM 兼容性是另一个强制要求,将所有内容渲染到隐藏的 DirectDrawSurface 上,同时完全尊重 z 顺序,不仅是透明窗口,还包括透明精灵控件,以及 GDI32、GDIPLUS、DirectX、OpenGL 在同一个容器内的独特组合。
Windows 内置的标准控件都无法使用,因为它们仅支持 24 位 RGB 颜色(无 Alpha 通道),而全屏运行时用于实现淡入淡出效果需要 32 位 ARGB。GDImage 与 WinLIFT 皮肤引擎的独特组合,使我能够完全控制整个 GUI,包括窗口的非客户端区域。
所有图形组件都存储在这 4 个文件夹中
1 - Background (背景),
2 - BBplugin\Texture (纹理),
3 - Resource (资源),
4 - Resource\MBoxSkin (MBox 皮肤).
MBox64.sks 文件不应被删除,它是 WinLIFT 皮肤引擎使用的皮肤脚本 (.sks) 文件。
(要查看更多皮肤,请在 Google 上搜索“gdimage winlift”,然后选择图片)。
使用中央定时器
这是代码的关键部分,使用一个单一的定时器通过“Animate”过程来控制一切。定时器使用的频率与显示刷新率相同 (LCD 显示器上通常是 60 Hz),这也是 DWM 位块传输隐藏的复合 DirectDrawSurface 到显示器而不闪烁所使用的 FPS。
所有动画都从这里执行,GUI 的更改一次性从 GDImage 的“ZI_UpdateWindow”API 中进行位块传输,以匹配刷新率。
OpenGL 视觉插件
它们是 { 字面意义上的 } 真正的编程艺术品,因为它们的目的是在实时处理 { wimdata } 流时产生眼花缭乱的效果。它们由“Oscillo”函数中的“RenderOpenGL”处理。
下面是一个非常简单的示例,
但有些可能相当复杂,需要对 3D 编程有深入的了解。您可以在私人 objreader 论坛下载与 MBox64 一起使用的插件的源代码。如果您想创建自己的插件,我很乐意在名为
“Post Your Questions & Comments Here” (在此处发布您的问题和评论)
http://www.objreader.com/index.php?board=1.0
//+--------------------------------------------------------------------------+
//| |
//| Woofer |
//| BassBox OpenGL plugin |
//| |
//+--------------------------------------------------------------------------+
#include <windows.h>
#include "..\TCLib\math.h"
#include "..\TCLib\Strings.cpp"
#include <gl/GL.h>
#include <gl/GLU.h>
#include "gl2.h"
typedef ULONGLONG QWORD;
#include "BBplugin.h"
#define ExportC extern "C" _declspec (dllexport)
DWORD g_color[34];
float gsR[32], gsG[32], gsB[32];
void bbp_ColorInit() {
g_color[0] = 0;
g_color[1] = bbpARGB(255, 32,32,32);
g_color[2] = bbpARGB(255, 0,44,233);
g_color[3] = bbpARGB(255, 0,67,210);
g_color[4] = bbpARGB(255, 0,89,187);
g_color[5] = bbpARGB(255, 0,112,164);
g_color[6] = bbpARGB(255, 0,135,142);
g_color[7] = bbpARGB(255, 0,159,117);
g_color[8] = bbpARGB(255, 0,183,88);
g_color[9] = bbpARGB(255, 0,207,58);
g_color[10] = bbpARGB(255, 0,231,29);
g_color[11] = bbpARGB(255, 26,234,26);
g_color[12] = bbpARGB(255, 52,237,23);
g_color[13] = bbpARGB(255, 79,240,20);
g_color[14] = bbpARGB(255, 105,243,17);
g_color[15] = bbpARGB(255, 126,245,14);
g_color[16] = bbpARGB(255, 147,248,11);
g_color[17] = bbpARGB(255, 168,250,8);
g_color[18] = bbpARGB(255, 189,253,5);
g_color[19] = bbpARGB(255, 210,255,2);
g_color[20] = bbpARGB(255, 233,255,0);
g_color[21] = bbpARGB(255, 255,255,0);
g_color[22] = bbpARGB(255, 255,251,0);
g_color[23] = bbpARGB(255, 255,235,0);
g_color[24] = bbpARGB(255, 255,215,0);
g_color[25] = bbpARGB(255, 255,196,0);
g_color[26] = bbpARGB(255, 255,176,0);
g_color[27] = bbpARGB(255, 255,156,0);
g_color[28] = bbpARGB(255, 253,137,0);
g_color[29] = bbpARGB(255, 255,117,0);
g_color[30] = bbpARGB(255, 255,97,0);
g_color[31] = bbpARGB(255, 255,78,0);
g_color[32] = bbpARGB(255, 255,58,0);
g_color[33] = bbpARGB(255, 255,0,0);
}
void MakeColorLUT() {
COLORBYTES bytestruct;
for (long K = 0; K < 34; K++) {
MoveMemory(&bytestruct, &g_color[K], sizeof(COLORBYTES));
gsR[K] = (float)(bytestruct.R / 255.0f);
gsG[K] = (float)(bytestruct.G / 255.0f);
gsB[K] = (float)(bytestruct.B / 255.0f);
}
}
ExportC long BBProc (OUT BBPLUGIN &BBP) {
long K, KK, nRet = BBP_SUCCESS;
const float SmoothValue = 0.8f, Cx = -600.0f, Cy = -900.0f, Cz = -150.0f;
float fft[256], SoundBuffer[256], pulse, rAspect;
static BBPTEXTURE mt;
switch (BBP.msg) {
case BBP_RENDER:
pulse = (float) max(BBP.lpeak / 700, BBP.rpeak / 700) / 14.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f, -0.0f, Cz);
glRotatef(Cx, 1.0f, 0.0f, 0.0f);
glRotatef(Cy, 0.0f, 1.0f, 0.0f);
// This is where the magic happens:
// BBP.fftdata returns a pointer to an area of memory containing an array of 256 singles.
MoveMemory(&fft[0], BBP.fftdata, 256 * sizeof(float));
// Try and smooth out the buffer by averaging the last results:
for (K = 0; K < 256; K++) {
// We use SQR to bring out the midtone of the music, otherwise the spectrum is very flat.
SoundBuffer[K] = (float) (sqrt(sqrt(fft[K]) * 2));
}
for (long Direction = -1; Direction < 2; Direction++) {
if (Direction) {
for (KK = 0; KK < 3; KK++) {
K = KK * 16;
if (KK == 0) { K = 2; }
glPushMatrix();
// The iterator K goes from 0 (deepest bass) to 255 (highest treble pitch)
// Ax is the distance from the center. Ay & By are width and height of the flares.
float Ax = (KK * 16) / 128.0f * 125.0f * Direction * pulse;
float Ay = 2 + SoundBuffer[K] * (50.0f + K / 255.0f * 70.0f); // As K increases, the relative size of the signal
float By = 2 + SoundBuffer[K] * (50.0f + K / 255.0f * 70.0f); // drops, so the (K / 255 * 20) compensates by boosting the signal at higher frequencies.
glColor4f(gsR[K] * SoundBuffer[K], gsG[K] * SoundBuffer[K], gsB[K] * SoundBuffer[K] + 0.1f, 0.7f);
glTranslatef(Ax, 0.0f, 0.0f);
glRotatef(Cx, 1.0f, 0.0f, 0.0f);
glRotatef(Cy, 0.0f, 1.0f, 0.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f,0.0f); glVertex3f(-By , Ay, 0.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f( By , Ay, 0.0f);
glTexCoord2f(1.0f,1.0f); glVertex3f( By ,-Ay, 0.0f);
glTexCoord2f(0.0f,1.0f); glVertex3f(-By ,-Ay, 0.0f);
glEnd();
glPopMatrix();
}
}
}
// Very important we must reassign BBP.RC to the new BBP.DC
// Note: don't use permanent DC, this produce better and smoother display
wglMakeCurrent(BBP.dc, BBP.rc);
break;
case BBP_CREATE:
// Retrieve plugin details
strcpy(BBP.title, "Woofer");
strcpy(BBP.author, "Patrice Terrier");
BBP.version = MAKEWORD(2, 0); // Version 2.0"
BBP.renderto = BBP_OPENGL; // or BBP_GDIPLUS, or BBP_DIRECTX
break;
case BBP_INIT:
bbp_ColorInit();
glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading
glClearDepth(1.0f); // Depth Buffer Setup
glDisable(GL_DEPTH_TEST); // Disable Depth Buffer
glBlendFunc(GL_ONE, GL_ONE);
glDepthFunc(GL_LESS); // The Type Of Depth Test To Do
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glAlphaFunc(GL_GREATER, 0.01f);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glEnable(GL_TEXTURE_2D);
Path_Combine(mt.FullName, TexturePath(), L"boomer.png"); mt.ID = 1; mt.Square = 3;
MakeMultipleTexture(&mt, 1);
MakeColorLUT();
break;
case BBP_SIZE:
// The size of the view port has changed.
RECT rc; GetClientRect(BBP.parent, &rc);
glViewport(0, 0, Width(rc), Height(rc));
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
rAspect = 0.0f;
if (Height(rc)) { rAspect = (float) Width(rc) / (float) Height(rc); }
gluPerspective(50.0f, rAspect, 0.01f, 5000.0f);
glMatrixMode(GL_MODELVIEW);
break;
case BBP_DESTROY:
// Free up your resources there
DestroyTexture (&mt, 1);
break;
default:
nRet = BBP_ERROR;
break;
}
return nRet;
}
BOOL WINAPI DllMain (HINSTANCE hinstDLL, IN DWORD fdwReason, IN LPVOID lpvReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
还有很多关于代码的内容可以讲,尤其是关于 GDImage 和 WinLIFT,但这并非此代码的目的,此代码必须侧重于使用“PlayMediaFile”来选择特定的媒体。
其中的杰作是类 MFPlayer2 (参见 Player2.h),它被用来播放视频。
MFPlayer2 是 **Windows SDK Media Foundation 示例**的一个重写版本,该示例可以在这里找到
https://msdn.microsoft.com/en-us/library/windows/desktop/aa371827(v=vs.85).aspx
如何使用播放器
已编译的二进制文件位于 x64\Release 文件夹中。
当您首次启动应用程序时,它处于空闲模式,等待播放特定的媒体。
在空闲模式下,您可以通过单击左上角标题图标的左键或右键来更改图像背景。
您可以通过单击窗口边缘的自动隐藏箭头来选择特定的插播动画。
您可以从播放按钮选择媒体,或者使用 Explorer 的拖放功能。使用拖放时,您可以选择单个文件、一组文件,以及一个或多个文件夹 (递归搜索)。
大多数命令位于窗口底部的面板区域。
从左到右依次是
开/关 (关闭所有正在播放的媒体并返回空闲模式)。
使用互联网 URL (从互联网加载媒体并以流媒体模式播放)。
循环模式 (以循环模式播放媒体或一组媒体)。
播放/暂停 (播放或暂停当前选定的媒体)。
静音 (快速开启或关闭声音)。
音量滑块 (调整音频级别)。
封面艺术图标 (显示或隐藏封面艺术图标或 BassBox 低音炮动画)。
视觉插件 (在播放音频时启用/禁用视觉 OpenGL 效果的使用)。
切换全屏 (在全屏模式下,控制面板会根据鼠标活动自动消失)。
媒体进度条位于命令上方,而状态信息显示在下方。
当媒体播放完毕时,播放器会返回“空闲”模式,除非启用了“循环模式”。
封面艺术,它们在音频模式下显示 (主要是 mp3),如果缺少,则使用 BassBox 低音炮动画。
要选择一个视觉插件 (当此模式启用时),可以通过单击控制面板上方窗口部分的左键 (上一个) 或右键 (下一个) 来进行。
在播放一组媒体时,您可以通过单击窗口边缘的自动隐藏左右箭头来移动到上一个或下一个。
http://www.objreader.com/download/demo/MediaBox.png
反馈
我无法在此帖子中解释代码所使用的所有内容,这是多年工作的结果,所以如果您想对此进行评分,请牢记这一点。建设性的反馈是受欢迎的,但最好的地方是在我的专用私人论坛上,我期待那些想要加入并做出贡献的人,特别是那些拥有 OpenGL 专业知识并希望编写新视觉插件的人,谢谢!。
在这里查看如何加入此项目
http://www.objreader.com/index.php?topic=5.0
注意
为了将 ZIP 文件的大小保持在 10 MB 以下,我不得不删除几个资源文件.
但是,它们可以在 www.objreader.com 上找到。
历史
2017 年 7 月 3 日首次公开发布。