home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
cset21v6.zip
/
MMPM2TK
/
TK
/
ADMCT
/
ADMCPLAY.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-04-08
|
49KB
|
1,424 lines
/********************* START OF SPECIFICATIONS *********************
*
* SUBROUTINE NAME: ADMCPLAY.C
*
*
*
* Copyright (c) IBM Corporation 1991, 1993
* All Rights Reserved
*
* DESCRIPTIVE NAME: Audio MCD WaveAudio Playback Source.
*
* FUNCTION:Play an Waveform Audio Element.
*
* On MCI_PLAY, a streaming MCD should perform the following commands:
*
* Always check flags and validate memory first. This way if the flags
* are invalid, the previous command will not be interrupted.
* If there is a command active on another thread (i.e. and play, record
* or save), then either abort (record or save) or superced (play) by
* stopping the stream and sending a message to the caller.
* If the stream is going the wrong way (e.g. it is setup for recording)
* then destroy the stream.
* If no stream has been created, then create one. If the stream handler
* needs to associate a data object, do it here.
* If we destroyed a recording stream before creating the play back stream,
* ensure that playback stream has the same position as the previous record
* stream.
* Enable any events (such as cuepoints or position advises).
* Set up networking hooks,
* Start stream.
* Wait for a streaming event.
* Stop the stream if necessary.
* If MCI_NOTIFY was sent used, inform the caller of command completion.
*
* NOTES: This source file illustrates the following concepts:
* A. Aborting or superceding a previous command
* B. Transitioning from a record stream->playback stream
* so that the caller at the MCI Layer is unaware of the
* change.
* C. Processing a stream which was altered by the MCI_SET
* command.
* D. Proper creation of a stream.
* E. Processing the MCI_FROM/MCI_TO flags in a streaming
* environment.
* F. Enable both cuepoints and position advises.
* G. How to use MMIO networking features.
* H. Determing when the creation of a thread is necessary
* for an MCI_NOTIFY
* I. Proper handling of an event procedure.
* J. Proper termination of a playback thread.
*
* ENTRY POINTS:
*
* INPUT: MCI_PLAY message.
* MCI_FROM Flag
* MCI_TO Flag
*
* EXIT-NORMAL: MCIERR_SUCCESS
*
* EXIT_ERROR: Error Code.
*
* EFFECTS:
*
*
* INTERNAL REFERENCES: CreateNAssocStream ().
* DestroyStream ().
* AssocMemPlayToAudioStream ().
* PostMDMMessage ().
* CreateToEvent().
* ConvertTimeUnits ().
* StartPlay().
* PlEventProc().
* SetAudioDevice().
* SetWaveDeviceDefaults().
* OpenFile().
* CheckMem ().
*
* EXTERNAL REFERENCES: DosResetEventSem () - OS/2 API
* DosPostEventSem () - OS/2 API
* DosCreateThread () - OS/2 API
* SpiEnableEvent () - MME API
* SpiStopStream () - MME API
* SpiCreateStream () - MME API
* SpiAssociate () - MME API
* SpiSeekStream () - MME API
* mdmDriverNotify () - MME API
* mmioGetHeader () - MME API
*
*********************** END OF SPECIFICATIONS **********************/
#define INCL_BASE
#define INCL_DOSMODULEMGR
#define INCL_DOSSEMAPHORES
#include <os2.h>
#include <string.h>
#include <os2medef.h> // MME includes files.
#include <audio.h> // Audio Device defines
#include <ssm.h> // SSM spi includes.
#include <meerror.h> // MM Error Messages.
#include <mmioos2.h> // MMIO Include.
#include <mcios2.h> // MM System Include.
#include <mmdrvos2.h> // Mci Driver Include.
#include <mcd.h> // VSDIDriverInterface.
#include <hhpheap.h> // Heap Manager Definitions
#include <qos.h>
#include <audiomcd.h> // Component Definitions.
#include "admcfunc.h" // Function Prototypes
/********************* START OF SPECIFICATIONS *******************************
*
* SUBROUTINE NAME: CheckPlayFlags
*
* DESCRIPTIVE NAME: Ensures that the play flags are valid.Record
*
* FUNCTION: Report errors, do preprocessing for MCI play
*
* ENTRY POINTS:
*
* NOTE: All MCI Functions should check to ensure that the flags are
* valid before interrupting a previous operation. This means
* that if the caller supplied bad flags while a play was
* operating, the play would not be interrupted.
*
* EXIT-NORMAL: Return Code 0.
*
* EXIT_ERROR: Error Code.
*
* EFFECTS:
*
* INTERNAL REFERENCES: WaveIOPROC.
*
* EXTERNAL REFERENCES: DosQueryProcAddr - OS/2 API.
*
*********************** END OF SPECIFICATIONS *******************************/
RC CheckPlayFlags ( FUNCTION_PARM_BLOCK *pFuncBlock )
{
ULONG ulrc; // RC
ULONG ulParam1; // Incoming MCI Flags
INSTANCE *ulpInstance; // Local Instance
ULONG ulPlayFlags; // Mask for MCI Play
ULONG ulFileLength; // Length of the File in MMTIME
ULONG ulTemp1 = 0; // Scratch For Time Conversion
ULONG ulTempTO = 0; // Scratch for Play Till
ULONG ulFromPosition = 0; // holds where we will play from
PMCI_PLAY_PARMS pPlayParms; // caller data struct
/* Intialize local variables */
ulParam1 = pFuncBlock->ulParam1;
ulpInstance = (INSTANCE *) pFuncBlock->ulpInstance;
/*********************************
Check For Illegal Flags
**********************************/
ulPlayFlags = pFuncBlock->ulParam1;
ulPlayFlags &= ~(MCI_FROM + MCI_TO + MCI_NOTIFY + MCI_WAIT);
/* If the caller passed in an unsupported flag--return an error */
if (ulPlayFlags > 0)
return ( MCIERR_INVALID_FLAG );
/*******************************************
* Check to see If A Valid Element Specified
*******************************************/
if (ulpInstance->fFileExists != TRUE)
return ( MCIERR_FILE_NOT_FOUND );
/***********************************
* Retrieve the callers play parms
***********************************/
pPlayParms = ( PMCI_PLAY_PARMS )pFuncBlock->ulParam2;
/****************************************
* The caller is required to pass in
* valid play parms if the MCI_TO, MCI_FROM
* or if it is a notify.
*****************************************/
if ((pFuncBlock->ulParam1 & MCI_TO) ||
(pFuncBlock->ulParam1 & MCI_FROM) ||
(pFuncBlock->ulNotify))
{
ulrc = CheckMem ((PVOID)pPlayParms,
sizeof (MCI_PLAY_PARMS), PAG_READ);
if (ulrc != MCIERR_SUCCESS)
return ( MCIERR_MISSING_PARAMETER );
} /* if play parms were passed in */
if (ulpInstance->usPlayLstStrm == TRUE)
{
return ( MCIERR_SUCCESS );
}
/*********************************************
* In order to determine if the play request
* is valid, we must retrieve the length of
* the file.
* Note: the length of the file is returned in
* the time units we are using currently.
*********************************************/
ConvertTimeUnits ( ulpInstance,
&ulFileLength,
FILE_LENGTH);
ulTemp1 = ulFileLength;
/* Convert file length to mmtime for stream comparisons later */
ConvertToMM( ulpInstance, &ulFileLength, ulFileLength );
/*****************************************************
* To ensure that the MCI_FROM is valid, we need to
* do the following checks:
* If we currently have a playback stream then:
* A. Is the from greater than the file length?
*
* If a record stream is active then:
* A. If it has not been started, then is it
* shorter than the file length.
* A. Else it must be less than both the file
* length AND what we have recorded so far.
******************************************************/
if (ulParam1 & MCI_FROM)
{
/*******************************************
* If we have a playback stream and the
* request is not less than the file length
* then return an outofrange error.
*******************************************/
if ( pPlayParms->ulFrom > ulTemp1
&& AMPMIX.ulOperation != OPERATION_RECORD )
{
return ( MCIERR_OUTOFRANGE );
}
else
{
/***************************************
* This is a record stream, check if
* a stream been created and started
* by a play/record or cue?
* If not, just ensure that the request
* is within the limits of the file.
****************************************/
if ( ulpInstance->ulCreateFlag != PREROLL_STATE )
{
if ( pPlayParms->ulFrom > ulTemp1 )
{
return ( MCIERR_OUTOFRANGE );
}
} /* if not in preroll state */
else
/*******************************************************
* We have a record stream which is active. This is
* special case since record can be continually updating
* the file length. Therefore we must assure that
* the request is less than what has been recorded and
* the file length.
*******************************************************/
{
/* Determine where we are in the stream */
ulrc = SpiGetTime( ulpInstance->StreamInfo.hStream,
( PMMTIME ) &( ulpInstance->StreamInfo.mmStreamTime ) );
if ( ulrc )
{
return ( ulrc );
}
if ( AMPMIX.ulOperation == OPERATION_RECORD )
{
/*******************************************
* Convert the from request to mmtime so we
* can compare it with the stream time.
* The new from time will be place in ulTemp1
********************************************/
ulrc = ConvertToMM ( ulpInstance,
(PULONG) &ulTemp1,
pPlayParms->ulFrom );
/************************************
* Our to point must be less than
* what we have recorded so far, and it
* must be less than the length of the
* file!
**************************************/
if ( ulTemp1 > (ULONG) ulpInstance->StreamInfo.mmStreamTime &&
ulTemp1 > ulFileLength )
{
return ( MCIERR_OUTOFRANGE);
}
} /* If the card is in record mode */
else
{
/**********************************************************
* Save an mmtime version of the from position, so that the
* the MCI_TO checks below will ensure that to to position
* is valid.
**********************************************************/
ulrc = ConvertToMM ( ulpInstance,
&ulFromPosition,
pPlayParms->ulFrom );
}
} /* If the stream is in a prerolled stated--i.e. buffers full */
} /* else from < length && ! in record mode */
} /* Play From flag was specified */
/************************************************************
* If the caller specified the MCI_TO flag, then the following
* checks will be required:
*
* If we currently have a playback stream then:
* A. Is the to position greater than the file length?
* B. Is the to position greater than the from postion
* (if and only if) MCI_FROM was specified.
*
* If a record stream is active then:
* A. If it has not been started, then the to point
* must not be 0 and must be greater than the from.
* A. Else it must be less than both the file
* length AND what we have recorded so far.
************************************************************/
if (ulParam1 & MCI_TO)
{
/***************************************
* If the to position is greater than
* the file length (using the currently
* specfied time format) return an error.
****************************************/
if ( pPlayParms->ulTo > ulTemp1 &&
AMPMIX.ulOperation != OPERATION_RECORD )
{
return ( MCIERR_OUTOFRANGE );
}
else
{
/* If to position is <= from position--it's an error */
if ( ulParam1 & MCI_FROM )
{
if ( pPlayParms->ulTo <= pPlayParms->ulFrom )
{
return ( MCIERR_OUTOFRANGE );
}
} /* if from flag was specified */
/****************************************
* Was the stream previously started by a
* record, cue or play?
*****************************************/
if ( ulpInstance->ulCreateFlag != PREROLL_STATE )
{
/* As long as they don't want to play to 0 -- we are ok */
if ( pPlayParms->ulTo == 0 )
{
return ( MCIERR_OUTOFRANGE );
}
return ( MCIERR_SUCCESS );
} /* Stream not in preroll state */
/*****************************************
* Determine our current stream time--use
* as a reference point for future checks.
******************************************/
ulrc = SpiGetTime( ulpInstance->StreamInfo.hStream,
( PMMTIME ) &( ulpInstance->StreamInfo.mmStreamTime ) );
if ( ulrc )
{
return ( ulrc );
}
/*********************************************************
* The documentation states that the play will start from
* either the current position or the from point.
* If the from flag was not passed in, then set the
* from position to be equivalent to our current position
* for simplicity purposes.
* Note: ulFromPosition was filled in the from processing
*********************************************************/
if ( ulFromPosition == 0 &&
!(ulParam1 & MCI_FROM ) )
{
ulFromPosition = ( ULONG ) ulpInstance->StreamInfo.mmStreamTime;
}
if ( AMPMIX.ulOperation == OPERATION_RECORD )
{
/*******************************************
* Convert the to request to mmtime so we
* can compare it with the stream time.
* The new TO time will be place in ulTempTO
********************************************/
ulrc = ConvertToMM ( ulpInstance,
&ulTempTO,
pPlayParms->ulTo);
/******************************************************
* If we are past what we have recorded so far and past
* the length of the file (in case of recording in the
* middle or start of the file), then return an error
*******************************************************/
if ( ulTempTO > (ULONG ) ulpInstance->StreamInfo.mmStreamTime &&
ulTempTO > ulFileLength )
{
return ( MCIERR_OUTOFRANGE );
}
} /* if we are in record mode */
else
{
/*******************************************
* Convert the to request to mmtime so we
* can compare it with the stream time.
* The new TO time will be place in ulTempTO
********************************************/
ulrc = ConvertToMM ( ulpInstance,
&ulTempTO,
pPlayParms->ulTo);
/************************************************
* We cannot play to a point which is behind our
* current position or to a point which is less
* than our from position.
************************************************/
if ( ulTempTO <= ( ULONG ) ulpInstance->StreamInfo.mmStreamTime &&
ulTempTO <= ulFromPosition )
{
/********************************************************
* it is possible that we had rounding problems so ensure
* that user did indeed pass in an illegal value
********************************************************/
if ( ( ulParam1 & MCI_FROM ) &&
!(pPlayParms->ulTo <= pPlayParms->ulFrom ) )
{
}
else
{
return ( MCIERR_OUTOFRANGE );
}
}
}
} /* else to < file length */
} // Of Play Till XXXX
return (MCIERR_SUCCESS);
} /* Check play flags */
/********************* START OF SPECIFICATIONS *********************
*
* SUBROUTINE NAME: MCIPlay
*
* DESCRIPTIVE NAME: Waveform Play Routine.
*
* FUNCTION: Play an Waveform File.
*
* NOTES: hStream[1] = A --> B = Playback stream.
*
* ENTRY POINTS:
* LINKAGE: CALL FAR
*
* INPUT: MCI_PLAY message.
*
* EXIT-NORMAL: Return Code 0.
*
* EXIT_ERROR: Error Code.
*
* EFFECTS:
*
* INTERNAL REFERENCES: ADMCERR (). CreateNAssocStream ().
*
* EXTERNAL REFERENCES: spiStartStream () - SSM Spi
* spiStopStream () - SSM Spi
*
*********************** END OF SPECIFICATIONS **********************/
RC MCIPlay (FUNCTION_PARM_BLOCK *pFuncBlock)
{
ULONG ulParam1; // Flags for this Msg
ULONG ulrc; // Error Code Propogated
ULONG lCnt; // Number Of Posts
ULONG ulAbortNotify = FALSE; // whether or not to abort notify's
INSTANCE *ulpInstance; // Local Instance
BOOL fInitNeeded = FALSE;// must we reinit the card?
BOOL fSeekNeeded = FALSE;// is a seek needed after a create
LONG rc; // thread id return code
/**************************************
* Intialize Variables on Stack
**************************************/
ulParam1 = pFuncBlock->ulParam1;
/**************************************
* Derefernce Pointers
**************************************/
ulpInstance = (INSTANCE *) pFuncBlock->ulpInstance;
/*************************************************
* Check to ensure that all of the flags are valid
* memory is properly allocated and that the
* requested action is possible (i.e. the from
* position < to position etc.)
**************************************************/
ulrc = CheckPlayFlags ( pFuncBlock );
if (ulrc)
{
return (ulrc);
}
/****************************************
* If no stream has been created, and the
* card has not changed modes, there is no
* need to reinit it!
*****************************************/
if ( AMPMIX.ulOperation == OPERATION_RECORD ||
ulpInstance->StreamInfo.ulState == STREAM_SET_STATE )
{
fInitNeeded = TRUE;
}
/*****************************************
* To ensure proper syncronization, acquire
* the semaphore which is used to control
* who can check to see which processes are
* active. This function will also tell us
* if there is an operation to abort or
* supercede.
******************************************/
GetNotifyAbortAccess ( ulpInstance, &ulAbortNotify );
/*******************************************
* If there is an operation active (i.e. a
* play, record or save) then post a message
* stating that the command has been
* aborted (record), superceded (play) or
* wait for completion (save).
********************************************/
if ( ulAbortNotify == TRUE)
{
ulrc = AbortInProgressNotify( ulpInstance, pFuncBlock, ulParam1, MCI_PLAY );
}
/***************************************************
* If the audio card and stream are setup for
* recording, then retrieve the old stream position,
* destroy the stream, and set a flag to indicate that
* the stream must be created for playback.
****************************************************/
if (AMPMIX.ulOperation == OPERATION_RECORD)
{
/***********************************************
* Since we are destroying an existing stream
* retrieve the current stream position. WHen
* the playback stream is created, we will
* seek to this point. Do this so that the
* caller at the MCI Layer is ignorant of the
* streaming operations.
***********************************************/
GetOldStreamPosition( ulpInstance );
DestroyStream ( &ulpInstance->StreamInfo.hStream);
if ( ulpInstance->ulOldStreamPos > 0 )
{
fSeekNeeded = TRUE;
}
ulpInstance->ulCreateFlag = CREATE_STATE;
} /* Transition from Recd State */
/***********************************************
* If a set was performed on an existing stream,
* destroy the stream and get new spcb keys. You
* MUST recreate the stream after and MCI_SET has
* been done since there is no way to communicate
* to the stream handler the change in data rates
* other than at spiCreate time.
***********************************************/
DestroySetStream ( ulpInstance );
/* Check to see if a stream has been created */
if (ulpInstance->ulCreateFlag != PREROLL_STATE)
{
/*******************************
* Do stream set up work and then
* create the stream
*******************************/
ulrc = PrepareAndCreateStream( ulpInstance, OPERATION_PLAY, fInitNeeded );
if ( ulrc )
{
return ( ulrc );
}
/***************************************************************
* Set the stream up with the same position advises and cuepoints
* as the previous stream had (if there any).
***************************************************************/
ulrc = RememberStreamState( ulpInstance, ulParam1, fSeekNeeded );
if ( ulrc )
{
return ( ulrc );
}
} /* PreRoll State */
/***************************
* Set a flag which indicates
* that a stream has been
* created.
****************************/
ulpInstance->ulCreateFlag = PREROLL_STATE;
/******************************************************************
* Place the stream in the correct position if MCI_FROM is specified
* and set the correct stopping point if MCI_TO is specfied
******************************************************************/
ulrc = ProcessFromToFlags( pFuncBlock, ulpInstance, MCI_PLAY, ulParam1 );
if ( ulrc )
{
return ( ulrc );
}
/****************************************
* See if the network can support the
* the file we are about to start streaming.
*****************************************/
ulrc = BeginQualityofService( ulpInstance, STREAM_READ );
/*-----------------------------------------
* If there is not a network io proc, we will
* receive unsupported message. In this case
* we will ignore the error.
*
* If there is a network io proc we may have
* to examine the error a little more carefully
*--------------------------------------------*/
if ( ulrc && ulrc != MMIOERR_UNSUPPORTED_MESSAGE )
{
/*--------------------------------------------
* At open time, we retrieved a variable called
* MSV_SYSQOSERRORFLAG which indicates if the
* user wants to be notified if the streaming
* operation will not be possible. It can
* have the following values:
*
* A. ERROR_REPORT (report all errors).
* B. ERROR_IGNORE (ignore all errors).
*---------------------------------------------*/
if ( ulpInstance->lQOSReporting == ERROR_REPORT )
{
/* Tell the caller of the network bandwidth problem */
return ( ulrc );
}
} /* If there was a problem setting up network support */
/***************************************
* Enable Position Advise if Needed
****************************************/
ulrc = EnableEvents( ulpInstance );
/************************************************
** To determine whether or not an operation must be
** done on a thread, use the following criteria:
**
** A. Will the operation take a long time.
** B. Is there any way to make it appear as if
** a thread was active.
**
** We could do an spiStart, receive the event
** in our event proc and clean up on the SSM
** thread. However, this thread will be a HIGH
** PRIORITY thread and we have too much processing
** to do. Therefore, there is no way to fake it.
** Thus we must create a play thread if the notify
** flag is sent
************************************************/
if (!ulrc)
{
if (ulParam1 & MCI_NOTIFY)
{
ulpInstance->usNotifyPending = TRUE;
ulpInstance->usNotPendingMsg = MCI_PLAY;
/****************************************************
* This thread is kicked off by the MCD mainly
* to start the stream. When it is safe to continue
* the play thread will post the thread semaphore and
* this thread can continue.
****************************************************/
DosResetEventSem (ulpInstance->hThreadSem, &lCnt);
/* Note: I think this is too large */
rc = _beginthread ( (PFNTHREAD)StartPlay,
0,
NOTIFY_THREAD_STACKSIZE,
(ULONG) pFuncBlock );
/*************************************************
* Wait for the play thread to indicate that it is
* safe to continue.
**************************************************/
if ( rc != -1 )
{
DosWaitEventSem (ulpInstance->hThreadSem, -1);
}
else
{
/****************************************
* Tell the network that we are done
* streaming since an error occurred
*****************************************/
EndQualityofService( ulpInstance );
ulrc = MCIERR_OUT_OF_MEMORY;
}
}
/* The caller used MCI_WAIT, so do everything on one thread */
else
{
ulpInstance->usWaitPending = TRUE;
ulpInstance->usWaitMsg = MCI_PLAY;
/************************************************
* Start playback. This function will kick
* off the play, get an event (i.e. stream
* complete, stopped etc.) and report the return
* code. We will return this return code to the
* caller.
************************************************/
ulrc = StartPlay (pFuncBlock);
}
} /* If no errors so far */
DosResetEventSem (ulpInstance->hThreadSem, &lCnt);
return (ulrc);
}
/********************* START OF SPECIFICATIONS *********************
*
* SUBROUTINE NAME: EventProc.
*
* DESCRIPTIVE NAME: SSM Event Notifications Receiever.
*
* FUNCTION: Handle Streaming Event Notifications from SSM.
*
* NOTES: This routine is presumed to receive all types of event
* notifications from SSM. The types include Implicit
* events, Cue point notifications in terms of both time
* and data. In response to Cue point notifications an
* MCI_CUEPOINT message is returned to MDM via mdmDriverNotify ()
*
* ENTRY POINTS:
*
* INPUT:
*
* EXIT-NORMAL: Return Code 0.
*
* EXIT_ERROR: Error Code and flError flag is set.
*
* EFFECTS:
*
* INTERNAL REFERENCES:
*
* EXTERNAL REFERENCES: mdmDriverNotify () - MDM API
*
*********************** END OF SPECIFICATIONS **********************/
RC APIENTRY PlayEventRoutine ( MEVCB *pevcb)
{
MTIME_EVCB *pMTimeEVCB; // Modified Time EVCB
INSTANCE * ulpInstance; // Current Instance
HWND hWnd; // CallBack Handle
BOOL fPlayListDone = FALSE;
/***********************************************************
* EventProc receives asynchronous SSM event notifications
* When the event is received, the event semaphore is posted
* which will wake up the MCD thread(s) blocked on this
* semaphore.
* The semaphore is not posted for time events like
* cuepoint (TIME) and media position changes since they do
* not alter the state of the stream.
************************************************************/
switch (pevcb->evcb.ulType)
{
case EVENT_IMPLICIT_TYPE:
/* Retrieve our instance from the EVCB */
ulpInstance = (INSTANCE *)pevcb->ulpInstance;
/* Retrieve the callback handle to post messages on */
hWnd = ulpInstance->hwndCallBack;
switch (pevcb->evcb.ulSubType)
{
case EVENT_EOS:
ulpInstance->StreamEvent = EVENT_EOS;
DosPostEventSem (ulpInstance->hEventSem);
break;
case EVENT_ERROR:
/****************************************
* Check for playlist specific error first
*****************************************/
if (ulpInstance->usPlayLstStrm == TRUE)
{
if ( pevcb->evcb.ulStatus == MCIERR_CUEPOINT_LIMIT_REACHED )
{
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
hWnd,
MM_MCINOTIFY,
(USHORT )pevcb->evcb.unused1,
MAKEULONG(MCI_PLAY, pevcb->evcb.ulStatus));
}
fPlayListDone = TRUE;
}
/********************************************
* Because the target stream handler is able
* to recover from underruns, we will let the
* stream continue, other error are lethal, so
* alert the waiting thread that something has
* seriously gone wrong.
*********************************************/
if (pevcb->evcb.ulStatus != ERROR_DEVICE_UNDERRUN)
{
ulpInstance->StreamEvent = EVENT_ERROR;
DosPostEventSem (ulpInstance->hEventSem);
}
else if ( !fPlayListDone )
{
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
hWnd,
MM_MCINOTIFY,
(USHORT) pevcb->evcb.unused1,
MAKEULONG(MCI_PLAY, pevcb->evcb.ulStatus));
}
break;
case EVENT_STREAM_STOPPED:
/* Self explanatory--someone stopped the stream */
ulpInstance->StreamEvent = EVENT_STREAM_STOPPED;
DosPostEventSem (ulpInstance->hEventSem);
break;
case EVENT_SYNC_PREROLLED:
/******************************************
* This event is received in reponse to a
* preroll start. A Preroll start is done
* on an MCI_CUE message.
*******************************************/
ulpInstance->StreamEvent = EVENT_SYNC_PREROLLED;
DosPostEventSem (ulpInstance->hEventSem);
break;
case EVENT_PLAYLISTMESSAGE:
/******************************************
* We can receive this event if a playlist
* parser hits the MESSAGE COMMAND.
* NOTE: The MCD should return this message
* with the callback handle specified on the
* open. This could be the source of much
* grief if you return on the wrong handle.
******************************************/
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
ulpInstance->hwndOpenCallBack,
MM_MCIPLAYLISTMESSAGE,
(USHORT) MAKEULONG(pevcb->evcb.ulStatus, ulpInstance->usWaveDeviceID),
(ULONG) pevcb->evcb.unused1);
break;
case EVENT_PLAYLISTCUEPOINT:
/************************************************
* We can receive this event if a playlist
* parser hits the CUEPOINT COMMAND opcode
* in the playlist. This differs from a "normal"
* cuepoint because it is detected by the source,
* rather than the target stream handler.
************************************************/
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
ulpInstance->hwndOpenCallBack,
MM_MCICUEPOINT,
(USHORT) MAKEULONG(pevcb->evcb.ulStatus, ulpInstance->usWaveDeviceID),
(ULONG) pevcb->evcb.unused1);
break;
} /* SubType case of Implicit Events */
break;
case EVENT_CUE_TIME_PAUSE:
{
/***************************************************
* This event will arrive if we played to a certain
* position in the stream. Let the play thread know
* that we have reached the desired point.
****************************************************/
pMTimeEVCB = (MTIME_EVCB *)pevcb;
ulpInstance = (INSTANCE *)pMTimeEVCB->ulpInstance;
ulpInstance->StreamEvent = EVENT_CUE_TIME_PAUSE;
DosPostEventSem (ulpInstance->hEventSem);
}
break;
case EVENT_CUE_TIME:
{
pMTimeEVCB = (MTIME_EVCB *)pevcb;
ulpInstance = (INSTANCE *)pMTimeEVCB->ulpInstance;
/*************************************************
* Single Events are Treated as Time Cue Points
* Note: the caller is required to have a callback
* specifically for this purpose which we stored in
* our Time Event Control Block (EVCB). See the
* ADMCCUE.C file for more information on how to
* manipulate your own EVCB and why you would want to
**************************************************/
if ( pMTimeEVCB->evcb.ulFlags == EVENT_SINGLE)
{
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
pMTimeEVCB->hwndCallback,
MM_MCICUEPOINT,
(USHORT) pMTimeEVCB->usCueUsrParm,
(ULONG) pMTimeEVCB->evcb.mmtimeStream);
}
/************************************************
* Recurring events equate to position advise events
* or media changed events.
*
* Note: the caller is required to have a callback
* specifically for this purpose which we stored in
* our Time Event Control Block (EVCB). See the
* ADMCCUE.C file for more information on how to
* manipulate your own EVCB and why you would want to
**************************************************/
if (pMTimeEVCB->evcb.ulFlags == EVENT_RECURRING)
{
mdmDriverNotify ( ulpInstance->usWaveDeviceID,
ulpInstance->StreamInfo.PosAdvEvcb.hwndCallback,
MM_MCIPOSITIONCHANGE,
(USHORT) ulpInstance->usPosUserParm,
(ULONG) pMTimeEVCB->evcb.mmtimeStream);
} /* Event Cue Time */
}
break;
} /* All Events case */
return (MCIERR_SUCCESS);
} /* PlayEventProc */
/********************* START OF SPECIFICATIONS *******************************
*
* SUBROUTINE NAME: StartPlay
*
* DESCRIPTIVE NAME:Start Play
*
* FUNCTION: Start the playback, wait till it is over and finish processing.
* If MCI_NOTIFY is used, also post a message.
*
*
* NOTES: This routine is called using caller' thread (MCI_WAIT)
* or a separate thread spawned by MCD on MCI Notify. Once the stream
* is started, the event procedure above is called by SSM on a
* high priority thread. The event procedure will post a semaphore
* awaking this function.
*
* When a streaming operation is interuptted (usually by a stop)
* the interuptting thread waits for the MCDs thread to complete
* its remaing tasks. This wait is controlled via the instance based
* thread semaphore.
*
* Further, on a notify the Play Notify command does not return
* until the newly created thread is ready to block itself. This
* ensures that any other MCI messages that are free to be intercepted
* following the MCI_PLAY message operate on a running stream.
* This also means there is minimum latency between the return of the
* Play command to the application and start of audible sound.
*
* ENTRY POINTS:
*
* INPUT:
*
* EXIT-NORMAL: MCIERR_SUCCESS
*
* EXIT_ERROR: Error Code.
*
* EFFECTS:
*
* INTERNAL REFERENCES: None
*
* EXTERNAL REFERENCES: None
*
*********************** END OF SPECIFICATIONS *******************************/
RC StartPlay (FUNCTION_PARM_BLOCK * pFuncBlockCopy )
{
ULONG ulrc;
ULONG lCnt;
ULONG ulParam1;
INSTANCE *ulpInstance;
ULONG ulErr;
FUNCTION_PARM_BLOCK FuncBlock;
BOOL fPostMessage; // should the caller be informed
// about the stream state
memmove( &FuncBlock, pFuncBlockCopy, sizeof( FUNCTION_PARM_BLOCK ) );
ulrc = MCIERR_SUCCESS; // is this assignment needed?
ulErr = MCI_NOTIFY_SUCCESSFUL;
ulpInstance = (INSTANCE *) FuncBlock.pInstance;
ulParam1 = FuncBlock.ulParam1;
fPostMessage = TRUE;
/**************************************
* Reset the event semaphore used by the
* PlayEventProc. See comments below.
****************************************/
DosResetEventSem (ulpInstance->hEventSem, &lCnt);
/* Update state to reflect the flact we are playing */
ulpInstance->StreamInfo.ulState = MCI_PLAY;
/****************************************
* Set a flag so we can sense when someone
* issues an aborted or superceded request
*****************************************/
ulpInstance->ulNotifyAborted = FALSE;
/****************************
* Start Playing the Stream.
*****************************/
ulrc = SpiStartStream (ulpInstance->StreamInfo.hStream, SPI_START_STREAM);
/*****************************************
* If this is a notify thread, let the
* caller know it is ok to continue
*****************************************/
if (ulParam1 & MCI_NOTIFY)
{
FuncBlock.pInstance->usUserParm = FuncBlock.usUserParm;
DosPostEventSem (ulpInstance->hThreadSem);
}
/***********************************************
* It is VERY important to check the error code
* from the SpiStartStream before waiting on an
* event from it. If an error is returned and
* we wait on an event from the event proc, we
* will hang since an event will never come.
***********************************************/
if ( ulrc )
{
/**********************************
* Disable Notify Pending Flag
***********************************/
if (ulpInstance->usNotifyPending == TRUE)
{
ulpInstance->usNotifyPending =FALSE;
}
/* Let the caller know things went VERY badly */
if ( ulParam1 & MCI_NOTIFY )
{
PostMDMMessage (ulrc, MCI_PLAY, &FuncBlock);
}
return ( ulrc );
}
/**********************************************
* Block this thread until an event happens in
* the stream. When it does, the PlayEventProc
* will signal us via this semaphore.
**********************************************/
DosWaitEventSem (ulpInstance->hEventSem, (ULONG) -1);
/*******************************************************
* Acquire semaphore which will prevent any thread from
* aborting us. Once we have this semaphore, enter a
* critical section, update some key instance variables
* and get out quickly.
*******************************************************/
DosRequestMutexSem( ulpInstance->hmtxNotifyAccess, -1 );
/**********************************************
* Let any other thread know that there no
* longer is a command active which can be
* aborted. We are in the clean up stage now
* and once we exit the critical section, other
* commands can safely operate.
***********************************************/
if (ulpInstance->usNotifyPending == TRUE)
ulpInstance->usNotifyPending =FALSE;
/**************************************
* Ensure that if we are being aborted
* by another thread, and we received
* an event that we ignore the event
* and let the other process post the
* notify
**************************************/
if (ulpInstance->ulNotifyAborted == TRUE)
{
fPostMessage = FALSE;
}
/* End of Stream (EOS) is good--the play is complete */
if (ulpInstance->StreamEvent == EVENT_EOS)
{
ulErr = MCI_NOTIFY_SUCCESSFUL;
}
/************************************************
* We have set up the event proc to give us the
* more detailed error in ulStatus if an EVENT
* ERROR is returned.
*************************************************/
else if (ulpInstance->StreamEvent == EVENT_ERROR)
{
ulErr = ulpInstance->StreamInfo.Evcb.evcb.ulStatus;
DosResetEventSem (ulpInstance->hEventSem, &lCnt);
/*********************************************
* Because we are stopping the stream, we must
* save the stream event that happened before
* the stop, otherwise, the stopped event will
* overwrite the old event and we will report
* the wrong event via MDMDriverNotify
**********************************************/
ulrc = SpiStopStream (ulpInstance->StreamInfo.hStream, SPI_STOP_DISCARD );
if (!ulrc)
{
DosWaitEventSem (ulpInstance->hEventSem, (ULONG) -1);
}
}
/************************************************
* Store the fact that we were doing a play to
* a certain point in the stream. Other threads
* may overwrite this variable so keep its value
* while we are in the critical section.
************************************************/
if (ulpInstance->fToEvent == TRUE)
{
ulpInstance->fToEvent = FALSE;
/*************************************************
* NOTE: it is very important that we disable this
* event since following commands could receive a
* bogus cuepoint when the stream by the to point.
**************************************************/
SpiDisableEvent(ulpInstance->StreamInfo.hPlayToEvent);
/***************************************************
* Just because we have received the cue time event
* does not mean that the stream is stopped. If we
* tried to seek in the current stream state, BAD
* things will happen so stop the stream.
***************************************************/
SpiStopStream (ulpInstance->StreamInfo.hStream, SPI_STOP_STREAM );
}
/******************************************************
* Fix for MMPM/2 AVC--they do repeated close and after
* approximately 80 closes, due to some OS/2 scheduling
* quirk, close would free the instance before the thread
* finished processing. Therefore, require close to
* acquire the exclusive semaphore before freeing
* instance.
********************************************************/
DosRequestMutexSem( ulpInstance->hmtxCloseAccess, -1 );
/*******************************************************
* Play back stream will perform an spi stop_pause
* to ensure that if someone requested another playback
* no information would be lost. If you do a stop
* discard/flush, the next playback may lose several
* bytes of information. Most people won't hear it,
* but some may.
*******************************************************/
ulpInstance->StreamInfo.ulState = STOP_PAUSED;
/****************************************
* Tell the network that we are done
* streaming.
*****************************************/
EndQualityofService( ulpInstance );
/*********************************************
* If the caller used MCI_WAIT, then return
* without sending a message via MDMDriverNotify
*********************************************/
if (ulpInstance->usWaitPending == TRUE)
{
ulpInstance->usWaitPending = FALSE;
DosReleaseMutexSem( ulpInstance->hmtxNotifyAccess );
DosReleaseMutexSem( ulpInstance->hmtxCloseAccess );
return ( ulErr );
}
/****************************************************
* If we received an eos or an event error, then this
* means this thread is terminated--inform the caller.
* Note: the posting of the MCI_TO case was handled
* above.
*****************************************************/
if ( fPostMessage )
{
PostMDMMessage (ulErr, MCI_PLAY, &FuncBlock);
}
/********************************************************
* If another command (such as a record, stop, seek etc.)
* tries to abort this play thread, they will wait on
* the thread semaphore to ensure that we cleaned up
* properly. Therefore, post the semaphore to indicate
* that we are done.
********************************************************/
DosPostEventSem (ulpInstance->hThreadSem);
/***************************************
* Allow other threads in the process to
* continue and release exlcusive notify
* semaphore.
***************************************/
DosReleaseMutexSem( ulpInstance->hmtxNotifyAccess );
DosReleaseMutexSem( ulpInstance->hmtxCloseAccess );
return (MCIERR_SUCCESS);
} /* StartPlay */