qzdoom-gpl/src/win32/i_movie.cpp
Randy Heit 601a6ad04c - Adjusted the noise debug table so that fractional volume levels do not
run into the adjacent columns.
- Added a NullSoundRenderer so that most of the checks against a NULL GSnd
  can be removed.
- Fixed: Looping sounds must always successfully allocate a channel, even if
  it's only a pre-evicted channel.


SVN r1058 (trunk)
2008-07-01 01:28:22 +00:00

522 lines
12 KiB
C++

/*
** i_movie.cpp
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* If you do not have dshow.h, either download the latest DirectX SDK
* or #define I_DO_NOT_LIKE_BIG_DOWNLOADS
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
#ifndef I_DO_NOT_LIKE_BIG_DOWNLOADS
#define I_DO_NOT_LIKE_BIG_DOWNLOADS
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define USE_WINDOWS_DWORD
#include "c_cvars.h"
CUSTOM_CVAR (Float, snd_movievolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
if (self < 0.f)
self = 0.f;
else if (self > 1.f)
self = 1.f;
}
#ifdef I_DO_NOT_LIKE_BIG_DOWNLOADS
#include "i_movie.h"
int I_PlayMovie (const char *movie)
{
return MOVIE_Failed;
}
#else
#include <dshow.h>
#include "i_movie.h"
#include "i_sound.h"
#include "v_video.h"
#include "c_console.h"
#include "win32iface.h"
#include "sbar.h"
EXTERN_CVAR (String, language)
#define WM_GRAPHNOTIFY (WM_APP+321)
#ifdef _DEBUG
#define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS
#else
//#define INGAME_PRIORITY_CLASS HIGH_PRIORITY_CLASS
#define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS
#endif
extern HWND Window;
extern IVideo *Video;
extern LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
extern void I_CheckNativeMouse (bool preferNative);
static bool MovieNotDone;
static bool MovieInterrupted;
static bool MovieDestroyed;
static bool FullVideo;
static bool NoVideo;
static IVideoWindow *vidwin;
static IMediaEventEx *event;
static IGraphBuilder *graph;
static IMediaControl *control;
static IBasicAudio *audio;
static IBasicVideo *video;
static void CheckIfVideo ();
static void SetMovieSize ();
static void SetTheVolume ();
static void SizeWindowForVideo ();
LRESULT CALLBACK MovieWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_GRAPHNOTIFY:
{
long code;
LONG_PTR parm1, parm2;
while (event->GetEvent (&code, &parm1, &parm2, 0) == S_OK)
{
if (code == EC_COMPLETE)
{
MovieNotDone = false;
}
event->FreeEventParams (code, parm1, parm2);
}
}
break;
case WM_SIZE:
if (vidwin == NULL)
{
InvalidateRect (Window, NULL, FALSE);
}
else if ((wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) && !FullVideo)
{
SetMovieSize ();
return 0;
}
break;
case WM_PAINT:
if (vidwin == NULL)
{
if (screen != NULL)
{
static_cast<BaseWinFB *> (screen)->PaintToWindow ();
}
}
else if (!FullVideo)
{
PAINTSTRUCT ps;
HBRUSH br;
HDC dc;
long left, top, width, height;
if (S_OK == vidwin->GetWindowPosition (&left, &top, &width, &height))
{
dc = BeginPaint (Window, &ps);
if (dc != NULL)
{
RECT rect = { left, top, left+width, top+height };
ScreenToClient (hWnd, (LPPOINT)&rect.left);
ScreenToClient (hWnd, (LPPOINT)&rect.right);
br = (HBRUSH)GetStockObject (BLACK_BRUSH);
switch (ExcludeClipRect (dc, rect.left, rect.top, rect.right, rect.bottom))
{
case SIMPLEREGION:
case COMPLEXREGION:
FillRect (dc, &ps.rcPaint, br);
break;
default:
break;
}
EndPaint (Window, &ps);
//return 0;
}
}
}
break;
case WM_DESTROY:
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
/*
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
*/
if (MovieNotDone)
{
if (wParam == VK_ESCAPE || message == WM_CANCELMODE || message == WM_DESTROY)
{
control->Stop ();
MovieNotDone = false;
MovieInterrupted = true;
}
}
if (message == WM_DESTROY)
{
MovieDestroyed = true;
}
return 0;
case WM_GETMINMAXINFO:
if (screen && !FullVideo)
{
LPMINMAXINFO mmi = (LPMINMAXINFO)lParam;
mmi->ptMinTrackSize.x = SCREENWIDTH + GetSystemMetrics (SM_CXSIZEFRAME) * 2;
mmi->ptMinTrackSize.y = SCREENHEIGHT + GetSystemMetrics (SM_CYSIZEFRAME) * 2 +
GetSystemMetrics (SM_CYCAPTION);
return 0;
}
break;
case WM_SETTINGCHANGE:
// In case regional settings were changed, reget preferred languages
language.Callback ();
return 0;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
int I_PlayMovie (const char *name)
{
HRESULT hr;
int returnval = MOVIE_Failed;
size_t namelen = strlen (name) + 1;
wchar_t *uniname = new wchar_t[namelen];
bool returnSound = false;
bool runningFull = false;
bool hotkey = false;
size_t i;
MSG msg;
MovieNotDone = true;
if (MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, name, -1, uniname, (int)namelen) == 0)
{ // Could not convert name to Unicode
goto bomb1;
}
// Convert slashes to backslashes because IGraphBuilder cannot deal with them
for (i = 0; i < namelen; ++i)
{
if (uniname[i] == L'/')
uniname[i] = L'\\';
}
if (FAILED(hr = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&graph)))
{
goto bomb1;
}
control = NULL;
event = NULL;
vidwin = NULL;
audio = NULL;
video = NULL;
if (FAILED(hr = graph->RenderFile (uniname, NULL)))
{
goto bomb2;
}
graph->QueryInterface (IID_IMediaControl, (void **)&control);
graph->QueryInterface (IID_IMediaEventEx, (void **)&event);
graph->QueryInterface (IID_IVideoWindow, (void **)&vidwin);
graph->QueryInterface (IID_IBasicAudio, (void **)&audio);
graph->QueryInterface (IID_IBasicVideo, (void **)&video);
if (control == NULL || event == NULL)
{
goto bomb3;
}
GSnd->MovieDisableSound ();
returnSound = true;
CheckIfVideo ();
SetTheVolume ();
FullVideo = false;
if (vidwin != NULL)
{
FullVideo = runningFull = screen->IsFullscreen ();
// If the message drain cannot be set, then we simply won't be able
// to catch mouse button presses in the video window.
HRESULT drainhr = vidwin->put_MessageDrain ((OAHWND)Window);
if (FullVideo)
{
// Try to avoid nasty palette flashes by clearing the screen to black.
// Does not always work. :-(
static_cast<Win32Video *> (Video)->BlankForGDI ();
static_cast<Win32Video *> (Video)->GoFullscreen (false);
static_cast<BaseWinFB *> (screen)->ReleaseResources ();
if (FAILED (drainhr) || FAILED(hr = vidwin->put_FullScreenMode (OATRUE)))
{
SizeWindowForVideo ();
FullVideo = false;
}
}
if (!FullVideo)
{
vidwin->put_Owner ((OAHWND)Window);
vidwin->put_WindowStyle (WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
SetMovieSize ();
}
else
{
RegisterHotKey (Window, 0, MOD_ALT, VK_TAB);
hotkey = true;
}
}
if (FAILED (hr = event->SetNotifyWindow ((OAHWND)Window, WM_GRAPHNOTIFY, 0)))
{
goto bomb3;
}
SetPriorityClass (GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
I_CheckNativeMouse (true);
SetWindowLongPtr (Window, GWLP_WNDPROC, (LONG_PTR)MovieWndProc);
if (FAILED (hr = control->Run ()))
{
goto bomb4;
}
MovieInterrupted = false;
MovieDestroyed = false;
while (MovieNotDone && GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
returnval = MovieInterrupted ? MOVIE_Played_Aborted :
NoVideo ? MOVIE_Played_NoVideo :
MOVIE_Played;
bomb4:
SetWindowLongPtr (Window, GWLP_WNDPROC, (LONG_PTR)WndProc);
SetPriorityClass (GetCurrentProcess(), INGAME_PRIORITY_CLASS);
bomb3:
if (hotkey)
{
UnregisterHotKey (Window, 0);
}
if (vidwin != NULL)
{
if (!FullVideo)
{
vidwin->put_Visible (OAFALSE);
vidwin->put_Owner (NULL);
}
vidwin->Release ();
}
if (video != NULL)
{
video->Release ();
}
if (audio != NULL)
{
audio->Release ();
}
if (event != NULL)
{
event->Release ();
}
if (control != NULL)
{
control->Release ();
}
SetActiveWindow (Window);
bomb2:
graph->Release ();
if (returnSound)
{
GSnd->MovieResumeSound ();
C_FlushDisplay ();
}
if (runningFull)
{
static_cast<Win32Video *> (Video)->GoFullscreen (true);
static_cast<BaseWinFB *> (screen)->CreateResources ();
}
bomb1:
delete[] uniname;
if (MovieDestroyed)
{
exit (0);
}
return returnval;
}
static void CheckIfVideo ()
{
long visible;
if (vidwin == NULL || video == NULL)
{
NoVideo = true;
return;
}
else if (vidwin->get_Visible (&visible) == E_NOINTERFACE)
{
NoVideo = true;
}
else
{
NoVideo = false;
}
if (NoVideo)
{
if (vidwin != NULL)
{
vidwin->Release ();
vidwin = NULL;
}
DHUDMessage *message = new DHUDMessage (
"This movie either does not contain a video stream "
"or no suitable decompressor could be found.",
1.5f, 0.375f, 0, 0, CR_RED, 1.f);
StatusBar->AttachMessage (message);
screen->Lock (false);
StatusBar->Draw (HUD_None);
StatusBar->DrawTopStuff (HUD_None);
screen->Update ();
StatusBar->DetachMessage (message);
delete message;
}
}
static void SetTheVolume ()
{
if (audio != NULL)
{
// Convert snd_movievolume from a linear range to 1/100th
// decibels and pass that to the IBasicAudio interface.
long volume;
if (snd_movievolume == 0.f)
{
volume = -10000;
}
else
{
volume = (long)(log10 (snd_movievolume) * 10000.0);
}
audio->put_Volume (volume);
}
}
static void SizeWindowForVideo ()
{
LONG width, height;
if (video == NULL || FAILED (video->GetVideoSize (&width, &height)))
return;
SetWindowPos (Window, NULL, 0, 0,
width + 2*GetSystemMetrics(SM_CXBORDER),
height + GetSystemMetrics(SM_CYCAPTION) + 2*GetSystemMetrics(SM_CYBORDER),
SWP_NOMOVE|SWP_NOOWNERZORDER);
}
static void SetMovieSize ()
{
LONG width, height, left, top;
RECT grc;
HRESULT hr;
GetClientRect (Window, &grc);
hr = video->GetVideoSize (&width, &height);
if (FAILED (hr))
{
vidwin->SetWindowPosition (0, 0, grc.right, grc.bottom);
return;
}
double aspect = (double)height / (double)width;
// Calculate size of video window that fits in our window
if (grc.right > grc.bottom)
{
height = grc.bottom;
width = (LONG)((double)height / aspect);
}
if (grc.right <= grc.bottom || width > grc.right)
{
width = grc.right;
height = (LONG)((double)width * aspect);
}
// Then center it
left = (grc.right - width) / 2;
top = (grc.bottom - height) / 2;
// And use it
vidwin->SetWindowPosition (left, top, width, height);
InvalidateRect (Window, NULL, FALSE);
}
#endif // I_DO_NOT_LIKE_BIG_DOWNLOADS