Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / OpenGL

MediaBox (MBox64)

5.00/5 (3 votes)
3 Jul 2017CPOL7 min read 17.1K   772  
Windows 10 multimedia player 64-bit

Introduction

MediaBox (MBox64) has been designed to work on WinDows 10 Creators+, using a mix of DirectX and Media Foundation (video), OpenGL (visual plugins), GDImage64.dll (graphic), WinLIFT64.dll (Skin), Bass.dll (Audio), AudioGenie3.dll (mp3 tag audio).

The 64-bit project is written in C++, with the Visual Studio VS 2015/2017 Community edition.

Background

It is the ultimate development of several of my projects that have been floating around since more than a decade.

The "C# Movie Player", posted on CodeProject on may 2007:
https://www.codeproject.com/Articles/18552/C-Movie-Player

The "BassBox" audio player, posted first on José Roca's forum on october 2007:
http://www.jose.it-berater.org/smfforum/index.php?board=179.0

The Work In Project project is hosted on my private forum in this dedicated WIP section:
http://www.objreader.com/index.php?topic=80.0

The OpenGL visual plugins are mainly a 64-bit transcription of those written for the BassBox player along the years (also used in several commercial applications).
The souce code of the visual plugins is available from www.objreader.com (you need to register first).

The PNG animations are based on the proprietary format already posted on CodeProject here:
https://www.codeproject.com/Articles/1181320/PNG-animation

The audio code is a mix of the AudoSession API, and BASS.dll (www.un4seen.com) that is able to play a larger range of audio format, including the soundtracker music format.

Using the code

Because i am working with several languages, i am using mostly the procedural core flat API syntax, that is the only common denominator understood by all of them. It is also the best way to produce small binaries without using any additional framework.

The whole interface is based on the use of GDImage sprite widgets, that acts like real controls. They are working in composited mode using the alpha channel to hide them, while in full screen mode.

The code has been designed to work on Windows 10 Creators+ version, for the purpose of the MKV video support. However it is recommended to install first the CCCP 64-bit video codecs to be able to use DirectX and detect the correct video size (i have not found yet a Media Foundation API doing the same than the DirectX GetVideoSize)

The "MediaCheckExtension" function, has the list of all the supported media, the correct media type is returned based on the file name extension.
Note:  in case of Audio file, priority is given to Bass.dll.

C++
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;
}

All the processing of the GDImage sprite widgets is done within the "GDImageControls.h", and especially inside of the "GDImageCallBack" function. Creation of the widgets is done in "InitGDImageControls".

Why using GDImage?
Because it was very important for this project to work in composited mode, using multiple transparent layers like in PhotoShop. The DWM compatibility was another mandatory, to render everything onto the hidden DirectDraw surface with full respect of the z-order, not only for the transparent windows, but also for the transparent sprite widgets, and the unique combination of GDI32, GDIPLUS, DirectX, OpenGL, inside of the same container.
None of the built-in Windows standard controls could be used, because they only work in 24-bit RGB color (no alpha channel), 32-bit ARGB is needed to perform the fading effect used while running in full screen mode. And the unique combination of GDImage with the WinLIFT skin engine, allows me to take full control of the whole GUI, including the window non-client area.
All the graphic components are stored inside of these 4 folders :
1 - Background,
2 - BBplugin\Texture,
3 - Resource,
4 - Resource\MBoxSkin.
The MBox64.sks file should not be removed, it is the skin script (.sks) file, used by the WinLIFT skin engine.
(to see some more skins, search google for "gdimage winlift", and select Images).

Using the central timer
This is a critical part of the code, using one single timer to control everything from the "Animate" procedure. The timer uses the same frequency that the display refresh rate (mostly 60 hz native on a LCD display), that is also the same FPS used by DWM to bitblt the hidden composited DirectDraw surface onto the display without flickering.
ALL the animations are performed from here, and the GUI changes are blited in one go from the GDImage "ZI_UpdateWindow" API to match the refresh rate.

OpenGL visual plugins
They are { litteraly } true pieces of programming art, because their purpose is to produce eye candy effects while processing the { wimdata } stream flow in real time. They are processed by "RenderOpenGL" from inside the "Oscillo" function.

Below you will find a very simple one,
but some could be rather complex and requires a good knowledge of 3D programming. You can download the source code of those used with MBox64, from the private objreader forum. If ever you want to create you own, i will be glad to answer your questions in the dedicated section named :
"Post Your Questions & Comments Here" 
http://www.objreader.com/index.php?board=1.0

C++
//+--------------------------------------------------------------------------+
//|                                                                          |
//|                                 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.0f0.0f, 0.0f);
  glRotatef(Cy, 0.0f1.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.0f125.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.1f0.7f);
      glTranslatef(Ax, 0.0f, 0.0f);
      glRotatef(Cx, 1.0f0.0f, 0.0f);
      glRotatef(Cy, 0.0f1.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;
}

There are much more to say about the code, especially about GDImage and WinLIFT, but this is not the purpose of this code, that must focuse on the use of "PlayMediaFile" to select a specific media.

The master piece being the class MFPlayer2 (see Player2.h) that is being used to play video.

MFPlayer2 is a reworked version of the Windows SDK Media foundation example that can be found here:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa371827(v=vs.85).aspx

How to use the player

The already compiled binary files are inside the x64\Release folder.

When you first start the application, it is in idle mode waiting to play a specific media.

While in idle mode, you can change the image background by clicking the left or right mouse button on the top left caption icon.

You can select a specific interlude animation by clicking the auto hide arrows located to the edge of the window.

You can select a media from the play button, or use D&D from the Explorer. When using D&D you can select one single file, a group of files, and one or several folders (recursive search).

Most commands are located on the bottom pannel section of the window.
And from the left to the right:
On/Off (close all media playing and return into idle mode).
Use internet URL (load a media from the internet and play it in streaming mode).
Loop mode (play a media or a group of media in loop mode).
Play Pause (play or pause the current selected media).
Mute (quickly turn the sound on or off).
Volume slider (adjust the audio level).
Cover art icon (show or hide the cover art icon or the BassBox woofer animation).
Visual plugin (enable/disable the use of the visual OpenGL effects while playing audio).
Toggle fullscreen (in full screen mode, the control pannel auto vanish based on the mouse activity).

The media Seek bar is located above the commands, while the Status informations are shown below.
When a media comes to full completion the player returns in "idle" mode, except if "loop mode" is enabled.

Cover art, they are shown in audio mode (mostly mp3), if they are missing the BassBox woofer animation is used.

To select a Visual plugin (when this mode is enabled) click with either the left (previous) or right (next) mouse button on the part of the window that is above the control pannel.

While playing a group of media, you can move to previous or next, by cliking either of the auto hide left or right arrow located onto the edge of the window.

http://www.objreader.com/download/demo/MediaBox.png

Feedback

I couldn't explain in this post all the things used by the code, that is the result of years of work, so please keep this in mind if ever you want to rate it. Constructive feedbacks are welcomed, but the best place to do this is on my dedicated private forum, i am looking forward for those wanting to enrole and contribute, especially for those with an OpenGL expertise wanting to write new visual plugins, thank you!.

See how to enrole to this project here:
http://www.objreader.com/index.php?topic=5.0

Note

To keep the size of the ZIP file under 10 Mb, i was forced to remove several resource files.
However they are available on www.objreader.com.

History

July 3, 2017 first public release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)