windows-nt/Source/XPSP1/NT/multimedia/media/avi/mciavi32/device.c
2020-09-26 16:20:57 +08:00

1137 lines
32 KiB
C

/******************************************************************************
Copyright (C) Microsoft Corporation 1985-1995. All rights reserved.
Title: device.c - Multimedia Systems Media Control Interface
driver for AVI.
*****************************************************************************/
#include "graphic.h"
#include "avitask.h"
#define ALIGNULONG(i) ((i+3)&(~3)) /* ULONG aligned ! */
#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (DWORD)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
// from wownt32.h
VOID (WINAPI * pWOWYield16)(VOID);
#ifdef DEBUG
#define AssertUserThread(npMCI) \
{ \
DWORD thread = GetCurrentThreadId(); \
Assert((npMCI)->hTask != (HTASK)thread); \
Assert(!((npMCI)->hwndDefault) || ((DWORD_PTR)GetWindowTask((npMCI)->hwndDefault) != thread));\
}
#else
#define AssertUserThread(npMCI)
#endif
/*
* send a request to the worker thread, and wait for it to complete,
* then return the result
*
* We must hold the CmdCritSec to stop other threads from making requests.
*
* if bDelayedComplete is true, the request is one that has two phases:
*
* phase 1: initiating the operation (eg starting play). No other
* requests are permitted during this phase, so we hold the
* critical section and wait. No yielding of any sort is safe
* at this point, since re-entry on the same thread is not
* something we can handle well. This means that the worker
* thread must not do anything before setting hEventResponse
* that could block on our processing a sendmessage
*
* phase 2: while the play is taking place, we must process messages,
* yield to the app and allow other requests (eg stop).
* For this, we wait on a second event, timing out and yielding
* to the driver 10 times a second.
*
*/
DWORD
mciaviTaskRequest(
NPMCIGRAPHIC npMCI,
UINT message,
DWORD dwFlags,
LPARAM lParam,
LPARAM dwCallback,
BOOL bDelayedComplete
)
{
DWORD dwRet;
MSG msg;
#ifdef _WIN32
// the gdi request queue is per-thread. We must flush the
// app thread q here, or updates done to the window at the apps
// request may appear before updates done by the app itself beforehand
GdiFlush();
#endif
// get the critsec that controls sending requests
EnterCriticalSection(&npMCI->CmdCritSec);
if (IsBadReadPtr(npMCI, sizeof(MCIGRAPHIC))) {
// device has been closed beneath us!
DPF(("help - npMCI has gone away"));
// not safe to leave critsec or dec count
return MCIERR_DEVICE_NOT_READY;
}
if (npMCI->EntryCount++ > 0) {
DPF(("re-entering requestor on same thread (SendMessage?)"));
//DebugBreak();
npMCI->EntryCount--;
LeaveCriticalSection(&npMCI->CmdCritSec);
return MCIERR_DEVICE_NOT_READY;
//return 0;
}
if (!IsTask(npMCI->hTask)) {
// worker thread has gone away (previous close ?)
npMCI->EntryCount--;
LeaveCriticalSection(&npMCI->CmdCritSec);
DPF(("worker thread has gone away"));
return MCIERR_DEVICE_NOT_READY;
}
// the response event should not be set yet!
Assert(WaitForSingleObject(npMCI->hEventResponse, 0) == WAIT_TIMEOUT);
// write the params
npMCI->message = message;
npMCI->dwParamFlags = dwFlags;
npMCI->lParam = lParam;
npMCI->dwReqCallback = dwCallback;
npMCI->bDelayedComplete = bDelayedComplete;
// we are the requesting task (we will be thrown out if this is
// bDelayedComplete and there is an outstanding bDelayedComplete
// from someone else)
npMCI->hRequestor = GetCurrentTask();
// signal that there is a request
SetEvent(npMCI->hEventSend);
// and wait for the response.
//
// in the play-wait case, this wait will complete once the play
// has started. So at this point, no yields.
// send-message processing needed for RealizePalette on worker thread
#if 1
// this could cause re-entry on this thread, and the critical section
// will not prevent that. Hence the EntryCount checks.
while (MsgWaitForMultipleObjects(1, &npMCI->hEventResponse, FALSE,
INFINITE, QS_SENDMESSAGE) != WAIT_OBJECT_0) {
DPF2(("rec'd sendmessage during wait\n"));
// this peekmessage allows an inter-thread sendmessage to complete.
// no message needs to be removed or processed- the range filtering is
// essentially irrelevant for this.
PeekMessage(&msg, NULL, WM_QUERYNEWPALETTE, WM_QUERYNEWPALETTE, PM_NOREMOVE);
}
#else
WaitForSingleObject(npMCI->hEventResponse, INFINITE);
#endif
// pick up the return value
dwRet = npMCI->dwReturn;
DPF2(("Task returns %d\n", dwRet));
// release the critsec now that request is all done
if (--npMCI->EntryCount != 0) {
DPF(("EntryCount not 0 on exit"));
}
LeaveCriticalSection(&npMCI->CmdCritSec);
// if this is a two-phased operation such as play + wait
// we must do the yielding wait here
if (!dwRet && bDelayedComplete) {
DWORD dw;
UINT nYieldInterval = 300;
#ifdef DEBUG
nYieldInterval = mmGetProfileInt(szIni, TEXT("YieldInterval"), nYieldInterval);
#endif
do {
if (mciDriverYield(npMCI->wDevID)) {
// app says we must stop now. do this by issuing a stop
// request and carry on waiting for the play+wait to finish
mciaviTaskRequest(npMCI, AVI_STOP, 0, 0, 0, FALSE);
}
dw = WaitForSingleObject(npMCI->hEventAllDone, nYieldInterval);
// this peekmessage allows an inter-thread sendmessage to complete.
// no message needs to be removed or processed- the range filtering is
// essentially irrelevant for this.
PeekMessage(&msg, NULL, WM_QUERYNEWPALETTE, WM_QUERYNEWPALETTE, PM_NOREMOVE);
} while(dw != WAIT_OBJECT_0);
// until this is cleared, no other task can issue delayed requests
npMCI->hWaiter = 0;
}
return dwRet;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceOpen | Open an AVI file.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LPSTR | lpName | file name.
*
* @parm DWORD | dwFlags | Open flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceOpen(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dwRet;
AssertUserThread(npMCI);
// init the yield proc we will need for wow yielding
if (IsNTWOW()) {
if (pWOWYield16 == 0) {
HMODULE hmod;
hmod = GetModuleHandle(TEXT("wow32.dll"));
if (hmod != NULL) {
(FARPROC)pWOWYield16 = GetProcAddress(hmod, "WOWYield16");
}
}
}
// note that DeviceClose *will* be called anyway, even if DeviceOpen
// fails, so make sure that allocations and events can be cleaned up
// correctly.
npMCI->uErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS |
SEM_NOOPENFILEERRORBOX);
// must open the file on this app thread for ole reasons
if (!mciaviOpenFile(npMCI)) {
SetErrorMode(npMCI->uErrorMode);
return npMCI->dwTaskError;
}
// OpenFileInit() on worker thread completes this open later.
// create the communication channel to the worker thread, and
// then start the thread
// do this first, so that whenever we call DeviceClose we can always
// safely do the Delete..
InitializeCriticalSection(&npMCI->CmdCritSec);
SetNTFlags(npMCI, NTF_DELETECMDCRITSEC); // Remember to do the delete
npMCI->EntryCount = 0;
// must be manual-reset to allow polling during play.
npMCI->hEventSend = CreateEvent(NULL, TRUE, FALSE, NULL);
npMCI->hEventResponse = CreateEvent(NULL, FALSE, FALSE, NULL);
npMCI->hEventAllDone = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!npMCI->hEventSend || !npMCI->hEventResponse || !npMCI->hEventAllDone) {
// cleanup of events actually allocated will be done in DeviceClose
return MCIERR_OUT_OF_MEMORY;
}
// create the worker thread
#if 0
if (mmTaskCreate(mciaviTask, &npMCI->hThreadTermination,
(DWORD)(UINT)npMCI) == 0)
#else
// We do not want the thread id, but CreateThread blows up if we pass
// a null parameter. Hence overload dwRet...
if (npMCI->hThreadTermination = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)mciaviTask,
(LPVOID)npMCI, 0, &dwRet))
#endif
{
// check that the thread is actually created
// either hEventResponse will be set, indicating that
// the thread completed, or hThreadTermination will be
// set, indicating that the thread aborted.
#if 0
if (WaitForMultipleObjects(2,
&npMCI->hEventResponse, FALSE, INFINITE) == WAIT_OBJECT_0)
{
// task completed ok
Assert(IsTask(npMCI->hTask));
}
#else
// We must process messages during this phase... IF messages
// must be processed by this thread before the AVI window can
// be created. The most likely case is when a parent window
// is passed and the parent window belongs to this (the UI)
// thread. If no messages need to be processed (i.e. the AVI
// window being created has no parent) then we could use the
// simpler code above. DO THIS LATER.
UINT n;
while (WAIT_OBJECT_0+2 <= (n = MsgWaitForMultipleObjects(2,
&npMCI->hEventResponse, FALSE, INFINITE, QS_SENDMESSAGE)))
{
MSG msg;
if (n!=WAIT_OBJECT_0+2) {
DPF0(("MsgWaitForMultipleObjects gave an unexpected return of %d\n", n));
}
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
// PeekMessage with PM_NOREMOVE causes the inter thread
// sent messages to be processed
}
dwRet = 0;
if (n == WAIT_OBJECT_0) {
// task completed ok
Assert(IsTask(npMCI->hTask));
}
#endif
else {
// hThreadTermination has been signalled - abort
CloseHandle(npMCI->hThreadTermination);
npMCI->hThreadTermination = 0;
dwRet = npMCI->dwTaskError;
Assert(dwRet);
}
} else {
npMCI->hTask = 0;
dwRet = MCIERR_OUT_OF_MEMORY;
npMCI->dwTaskError = GetLastError();
}
SetErrorMode(npMCI->uErrorMode);
if (dwRet != 0) {
// open failed - the necessary cleanup will be done in DeviceClose
// which will be called after a bad return from DeviceOpen. In
// fact graphic.c (which calls DeviceOpen) will call GraphicClose
// when DeviceOpen fails. GraphicClose will then call DeviceClose
// which will delete the cmdCritSec
}
return dwRet;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceClose | Close an AVI file.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceClose (NPMCIGRAPHIC npMCI)
{
DWORD dw = 0L;
if (npMCI && IsTask(npMCI->hTask)) {
AssertUserThread(npMCI);
// tell the worker to close and wait for it to happen
mciaviTaskRequest(npMCI, AVI_CLOSE, 0, 0, 0, FALSE);
}
// must wait for thread to exit
if (npMCI->hThreadTermination != 0) {
/*
** Wait for the thread to complete so the DLL doesn't get unloaded
** while it's still executing code in that thread
*/
// we must allow sendmessage at this point since the winproc thread
// will block until it can send messages to our thread, and we are
// waiting for the winproc thread to exit.
// do not do this between setting hEventSend and receiving hEventResponse
// though or we could re-enter the Request block and get confused
// about whether we have seen hEventResponse.
// we also need to yield in case we are on a wow thread - any
// interthread sendmessage to another wow thread will block until
// we yield here allowing other wow threads to run
DWORD dw;
do {
if (pWOWYield16) {
pWOWYield16();
}
dw = MsgWaitForMultipleObjects(
1,
&npMCI->hThreadTermination,
FALSE,
100,
QS_SENDMESSAGE);
if (dw == WAIT_OBJECT_0 + 1) {
MSG msg;
DPF2(("rec'd sendmessage during shutdown wait\n"));
// just a single peekmessage with NOREMOVE will
// process the inter-thread send and not affect the queue
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
}
} while (dw != WAIT_OBJECT_0);
CloseHandle(npMCI->hThreadTermination);
npMCI->hThreadTermination = 0;
}
if (TestNTFlags(npMCI, NTF_DELETECMDCRITSEC)) {
DeleteCriticalSection(&npMCI->CmdCritSec);
}
if (npMCI->hEventSend) {
CloseHandle(npMCI->hEventSend);
}
if (npMCI->hEventAllDone) {
CloseHandle(npMCI->hEventAllDone);
}
if (npMCI->hEventResponse) {
CloseHandle(npMCI->hEventResponse);
}
// uninitialize AVIFile and hence OLE - must be done on app thread
#ifdef USEAVIFILE
//
// we must do this so COMPOBJ will shut down right.
//
FreeAVIFile(npMCI);
#endif
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePlay | Play an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @parm LPMCI_DGV_PLAY_PARMS | lpPlay | Parameters for the play message.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL
DevicePlay(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
LPMCI_DGV_PLAY_PARMS lpPlay,
LPARAM dwCallback
)
{
BOOL bWait = FALSE;
DWORD dwErr;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
// all handled by the worker thread
AssertUserThread(npMCI);
if (dwFlags & MCI_WAIT) {
bWait = TRUE;
}
dwErr = mciaviTaskRequest(npMCI,
AVI_PLAY, dwFlags, (LPARAM) lpPlay, dwCallback, bWait);
if (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2)) {
MSG msg;
DPF(("DevicePlay, removing stray messages\n"));
/* Remove stray mouse and keyboard events after DispDib. */
while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST,
PM_NOYIELD | PM_REMOVE) ||
PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST,
PM_NOYIELD | PM_REMOVE))
;
}
return dwErr;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceRealize | Updates the frame into the given DC
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @parm BOOL | fForceBackground | Realize as background palette?
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceRealize(NPMCIGRAPHIC npMCI)
{
BOOL bWait = FALSE;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
// all handled by the worker thread
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_REALIZE, 0, 0, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceStop | Stop an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | Flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceStop(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dw = 0L;
/* Stop the record or playback if the task is currently playing */
if (!IsTask(npMCI->hTask)) {
DPF0(("DeviceStop called on a dead task, npMCI=%8x\n", npMCI));
return MCIERR_DEVICE_NOT_READY;
}
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_STOP, 0, 0, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceUpdate | Updates the frame into the given DC
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceUpdate(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
LPMCI_DGV_UPDATE_PARMS lpParms)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_UPDATE, dwFlags, (LPARAM) lpParms, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePause | Pause an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | Flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DevicePause(NPMCIGRAPHIC npMCI, DWORD dwFlags, LPARAM dwCallback)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_PAUSE, dwFlags, 0, dwCallback,
(dwFlags & MCI_WAIT));
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceCue | Cue an AVI movie for playing.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LONG | lTo | Frame to seek to, if MCI_TO set in <p dwFlags>.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceCue(NPMCIGRAPHIC npMCI, LONG lTo, DWORD dwFlags, LPARAM dwCallback)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_CUE, dwFlags, lTo, dwCallback,
(dwFlags & MCI_WAIT));
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceResume | Play an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceResume(NPMCIGRAPHIC npMCI, DWORD dwFlags, LPARAM dwCallback)
{
DWORD dw = 0L;
BOOL bWait = FALSE;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
// all handled by the worker thread
AssertUserThread(npMCI);
if (dwFlags & MCI_WAIT) {
bWait = TRUE;
}
return mciaviTaskRequest(npMCI,
AVI_RESUME, dwFlags, 0, dwCallback, bWait);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSeek | Seek to a position in an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LONG | lTo | Frame to seek to.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSeek(NPMCIGRAPHIC npMCI, LONG lTo, DWORD dwFlags, LPARAM dwCallback)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_SEEK, dwFlags, lTo, dwCallback,
(dwFlags & MCI_WAIT));
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetActive | is the movie active?
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetActive(NPMCIGRAPHIC npMCI, BOOL fActive)
{
// We cannot call AssertUserThread(npMCI);
// This routine is called on the winproc thread, as well as the user
// thread.
if (fActive)
// We must explicitly request a unicode string. %s will not
// work as dprintf uses wvsprintfA
DPF(("**** '%ls' is active.\n", (LPTSTR)npMCI->szFilename));
return 0;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceStatus | Returns the current status
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc Returns value for MCI's return value
*
***************************************************************************/
UINT PASCAL DeviceMode(NPMCIGRAPHIC npMCI)
{
if (!IsTask(npMCI->hTask)) {
return MCI_MODE_NOT_READY;
}
// there is no point in synchronizing with the worker thread for
// this since the task state will be transient anyway.
// just grab a snapshot and return that.
AssertUserThread(npMCI);
switch (npMCI->wTaskState) {
case TASKIDLE:
return MCI_MODE_STOP;
case TASKCUEING:
// problem: some apps (notably mplayer) will be surprised to
// get MCI_MODE_SEEK immediately after issuing a PLAY command.
// on win-16 the yielding model meant that the app would not
// normally get control back until after the play proper had
// started and so would never see the cueing state.
// to avoid this confusion (and the bugs that arise from it), we
// never return MCI_MODE_SEEK: we report this mode as playing.
// this is often what would be seen on win-16 anyway (even in the
// case of a PLAY command ?).
// Except... for apps that really do seek this can fool them into
// thinking that they are playing. So... we modify the algorithm
// to return MODE_SEEK if lTo==lFrom (why is obvious) OR if
// lRealStart==lTo. This latter is because if you seek in mplayer
// by dragging the thumb the image is only updated every key frame.
// lRealStart is updated to this key frame while seeking
//DPF0(("F: %8x, To=%d, From=%d lReal=%d lDrawn=%d Current=%d\n",
// npMCI->dwFlags, npMCI->lTo, npMCI->lFrom, npMCI->lRealStart, npMCI->lFrameDrawn, npMCI->lCurrentFrame));
if ((npMCI->lTo == npMCI->lFrom)
|| (npMCI->lTo == npMCI->lRealStart)) {
return(MCI_MODE_SEEK);
}
return MCI_MODE_PLAY;
case TASKSTARTING: // ready? of course we're ready
case TASKPLAYING:
return MCI_MODE_PLAY;
case TASKPAUSED:
return MCI_MODE_PAUSE;
default:
DPF(("Unexpected state %d in DeviceMode()\n", npMCI->wTaskState));
// fall through to the known states
//case TASKBEINGCREATED:
//case TASKINIT:
case TASKCLOSE:
//case TASKREADINDEX:
return MCI_MODE_NOT_READY;
}
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePosition | Returns the current frame
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @parm LPLONG | lpl | returns current frame
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DevicePosition(NPMCIGRAPHIC npMCI, LPLONG lpl)
{
// read a snapshot of the current state without
// synchronising with the worker thread!
AssertUserThread(npMCI);
return InternalGetPosition(npMCI, lpl);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetWindow | Set window for display
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm HWND | hwnd | Window to display into.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm Should this only take effect at time of next play?
*
***************************************************************************/
DWORD PASCAL DeviceSetWindow(NPMCIGRAPHIC npMCI, HWND hwnd)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_WINDOW, 0, (LPARAM) hwnd, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSpeed | Adjust the playback speed of an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwNewSpeed | New speed, where 1000 is 'normal' speed.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we stop the device, set our flag,
* and start playing again where we left off. If we were paused,
* we end up stopped. Is this bad?
* ** It is if you paused to change the speed then try and resume **
*
***************************************************************************/
DWORD PASCAL DeviceSetSpeed(NPMCIGRAPHIC npMCI, DWORD dwNewSpeed)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_SETSPEED, 0, (LPARAM) dwNewSpeed, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceMute | Turn AVI sound on/off.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm BOOL | fMute | TRUE If sound should be turned off, FALSE
* if sound should stay on.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we stop the device, set our flag,
* and start playing again where we left off. If we were paused,
* we end up stopped. Is this bad?
*
***************************************************************************/
DWORD PASCAL DeviceMute(NPMCIGRAPHIC npMCI, BOOL fMute)
{
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_MUTE, 0, (LPARAM) fMute, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetVolume | Set AVI volume.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwVolume | ranges from 0 to 1000.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we try to change the volume of the
* wave out device.
*
***************************************************************************/
DWORD PASCAL DeviceSetVolume(NPMCIGRAPHIC npMCI, DWORD dwVolume)
{
DWORD dw = 0L;
// switch audio off completely if setting volume level to 0, and back on
// again if not.
dw = DeviceMute(npMCI, (dwVolume == 0));
if (dw != 0) {
return dw;
}
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_SETVOLUME, 0, (LPARAM) dwVolume, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceGetVolume | Check the wave output device's current
* volume.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm The volume is left in npMCI->dwVolume
*
* Issue: On devices with global volume control, like an SBPro, how should
* things work?
*
***************************************************************************/
DWORD PASCAL DeviceGetVolume(NPMCIGRAPHIC npMCI)
{
// all reference to the hWave *must* be done on the worker thread
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_GETVOLUME, 0, 0, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetAudioStream | Choose which audio stream to use.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm WORD | wStream | ranges from 1 to the number of streams.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetAudioStream(NPMCIGRAPHIC npMCI, UINT wAudioStream)
{
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_AUDIOSTREAM, wAudioStream, 0, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetVideoStream | Choose which video stream is the
* "default". Also can enable/disable a stream. this works for both
* video and "other" streams.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm WORD | wStream | ranges from 1 to the number of streams.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetVideoStream(NPMCIGRAPHIC npMCI, UINT uStream, BOOL fOn)
{
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_AUDIOSTREAM, uStream, (BOOL)fOn, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePut | Change source or destination rectangle
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LPRECT | lprc | Pointer to new rectangle to use.
*
* @parm DWORD | dwFlags | Flags: will be either MCI_DGV_PUT_DESTINATION
* or MCI_DGV_PUT_SOURCE.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm
* If we end up using a custom stretch buffer, it would go here.
*
***************************************************************************/
DWORD FAR PASCAL DevicePut(NPMCIGRAPHIC npMCI, LPRECT lprc, DWORD dwFlags)
{
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_PUT, dwFlags, (LPARAM)lprc, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetPalette | Changes the override palette.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm HPALETTE | hpal | New palette to use.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD FAR PASCAL DeviceSetPalette(NPMCIGRAPHIC npMCI, HPALETTE hpal)
{
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_PALETTE, 0, (LPARAM) hpal, 0, FALSE);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetPaletteColor | Changes the a single color
* in the movie's palette.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | index | color index to change
*
* @parm DWORD | color | color value to use
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD FAR PASCAL DeviceSetPaletteColor(NPMCIGRAPHIC npMCI, DWORD index, DWORD color)
{
AssertUserThread(npMCI);
return mciaviTaskRequest(npMCI, AVI_PALETTECOLOR, color, (LPARAM) index, 0, FALSE);
}
//
// user-thread version of ResetDestRect - note that there is a similar
// winproc-thread-only version in window.c
//
void FAR PASCAL ResetDestRect(NPMCIGRAPHIC npMCI, BOOL fUseDefaultSizing)
{
RECT rc;
/* WM_SIZE messages (on NT at least) are sometimes sent
* during CreateWindow processing (eg if the initial window size
* is not CW_DEFAULT). Some fields in npMCI are only filled in
* after CreateWindow has returned. So there is a danger that at this
* point some fields are not valid.
*/
if (npMCI->hwndPlayback &&
npMCI->hwndPlayback == npMCI->hwndDefault &&
(npMCI->dwOptionFlags & MCIAVIO_STRETCHTOWINDOW)) {
GetClientRect(npMCI->hwndPlayback, &rc);
}
// Only allow ZOOMBY2 and fixed % defaults for our default playback window
else if ((npMCI->streams > 0) && (npMCI->hwndPlayback == npMCI->hwndDefault)) {
rc = npMCI->rcMovie;
if (fUseDefaultSizing)
AlterRectUsingDefaults(npMCI, &rc);
}
else {
return;
}
if (!IsRectEmpty(&rc)) {
DevicePut(npMCI, &rc, MCI_DGV_PUT_DESTINATION);
}
}