mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 20:32:36 +00:00
24e088daa8
Fix playing wave header multiple times in case the caller requests to do it. In Windows, it is supported by WHDR_BEGINLOOP and WHDR_ENDLOOP flags (specified together) and dwLoops member of WAVEHDR structure (ususally set to 0xFFFFFFFF (INFINITE constant)). - Check whenther WHDR_BEGINLOOP | WHDR_ENDLOOP flags are set by the caller. - If they are, get the amount of times to play the header from WAVEHDR.dwLoops. - Perform wave header competion only when the loop count is equal to zero. Otherwise, don't do it. - When the header is entirely committed, in case completion is not needed, reset committed bytes count to the starting value to allow the header play again. - Decrement loop count in case it isn't set to INFINITE, to mark loop as played correctly. When this count becomes zero, then the playback is finished. - Get rid from SOUND_OVERLAPPED.PerformCompletion member. Use SOUND_DEVICE_INSTANCE.LoopsRemaining == 0 condition instead. - Do this only for WaveOut devices, since MSDN states it works only with output buffers. Hence, there is nothing changed for WaveIn. - Update an appropriate statement about unimplemented functionality from mmebuddy notes. TODO: handle the case when multiple headers are requested to be looped (WHDR_BEGINLOOP and WHDR_ENDLOOP are set separatedly: 1st flag - in the 1st header, and 2nd in the last header). Currently, only looping a single wave header is supported. This fixes the playback in the apps those request looped wave playback of some audio data (e. g., BRD Demo app, which did play its sound only first 500 ms before, now it plays endlessly until closing it manually). CORE-10118
419 lines
13 KiB
C
419 lines
13 KiB
C
/*
|
|
* PROJECT: ReactOS Sound System "MME Buddy" Library
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: lib/drivers/sound/mmebuddy/wave/streaming.c
|
|
*
|
|
* PURPOSE: Wave streaming
|
|
*
|
|
* PROGRAMMERS: Andrew Greenwood (silverblade@reactos.org)
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
/*
|
|
DoWaveStreaming
|
|
Check if there is streaming to be done, and if so, do it.
|
|
*/
|
|
|
|
VOID
|
|
DoWaveStreaming(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
|
|
{
|
|
MMRESULT Result;
|
|
MMDEVICE_TYPE DeviceType;
|
|
PSOUND_DEVICE SoundDevice;
|
|
PMMFUNCTION_TABLE FunctionTable;
|
|
PWAVEHDR Header;
|
|
PWAVEHDR_EXTENSION HeaderExtension;
|
|
|
|
Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
|
|
SND_ASSERT( MMSUCCESS(Result) );
|
|
|
|
Result = GetSoundDeviceType(SoundDevice, &DeviceType);
|
|
SND_ASSERT( MMSUCCESS(Result) );
|
|
|
|
Result = GetSoundDeviceFunctionTable(SoundDevice, &FunctionTable);
|
|
SND_ASSERT( MMSUCCESS(Result) );
|
|
SND_ASSERT( FunctionTable );
|
|
SND_ASSERT( FunctionTable->CommitWaveBuffer );
|
|
|
|
/* No point in doing anything if no resources available to use */
|
|
if ( SoundDeviceInstance->OutstandingBuffers >= SoundDeviceInstance->BufferCount )
|
|
{
|
|
SND_TRACE(L"DoWaveStreaming: No available buffers to stream with - doing nothing\n");
|
|
return;
|
|
}
|
|
|
|
/* Is there any work to do? */
|
|
Header = SoundDeviceInstance->HeadWaveHeader;
|
|
|
|
if ( ! Header )
|
|
{
|
|
SND_TRACE(L"DoWaveStreaming: No work to do - doing nothing\n");
|
|
return;
|
|
}
|
|
|
|
/* Do we need to loop a header? */
|
|
if (DeviceType == WAVE_OUT_DEVICE_TYPE && (Header->dwFlags & WHDR_BEGINLOOP))
|
|
{
|
|
if ((Header->dwFlags & WHDR_ENDLOOP))
|
|
{
|
|
/* Get loop count */
|
|
SoundDeviceInstance->LoopsRemaining = Header->dwLoops;
|
|
}
|
|
else
|
|
{
|
|
/* Report and help notice such a case */
|
|
SND_WARN(L"Looping multiple headers is UNIMPLEMENTED. Will play once only\n");
|
|
SND_ASSERT((Header->dwFlags & (WHDR_BEGINLOOP | WHDR_ENDLOOP)) == (WHDR_BEGINLOOP | WHDR_ENDLOOP));
|
|
}
|
|
}
|
|
|
|
while ( ( SoundDeviceInstance->OutstandingBuffers < SoundDeviceInstance->BufferCount ) &&
|
|
( Header ) && SoundDeviceInstance->ResetInProgress == FALSE)
|
|
{
|
|
HeaderExtension = (PWAVEHDR_EXTENSION) Header->reserved;
|
|
SND_ASSERT( HeaderExtension );
|
|
|
|
/* Saniy checks */
|
|
SND_ASSERT(Header->dwFlags & WHDR_PREPARED);
|
|
SND_ASSERT(Header->dwFlags & WHDR_INQUEUE);
|
|
|
|
/* Can never be *above* the length */
|
|
SND_ASSERT( HeaderExtension->BytesCommitted <= Header->dwBufferLength );
|
|
|
|
/* Is this header entirely committed? */
|
|
if ( HeaderExtension->BytesCommitted == Header->dwBufferLength )
|
|
{
|
|
{
|
|
/* Move on to the next header */
|
|
SND_ASSERT(Header != Header->lpNext);
|
|
Header = Header->lpNext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PSOUND_OVERLAPPED Overlap;
|
|
LPVOID OffsetPtr;
|
|
DWORD BytesRemaining, BytesToCommit;
|
|
BOOL OK;
|
|
|
|
/* Where within the header buffer to stream from */
|
|
OffsetPtr = Header->lpData + HeaderExtension->BytesCommitted;
|
|
|
|
/* How much of this header has not been committed */
|
|
BytesRemaining = Header->dwBufferLength - HeaderExtension->BytesCommitted;
|
|
|
|
/* We can commit anything up to the buffer size limit */
|
|
BytesToCommit = BytesRemaining > SoundDeviceInstance->FrameSize ?
|
|
SoundDeviceInstance->FrameSize :
|
|
BytesRemaining;
|
|
|
|
/* Should always have something to commit by this point */
|
|
SND_ASSERT( BytesToCommit > 0 );
|
|
|
|
/* We need a new overlapped info structure for each buffer */
|
|
Overlap = AllocateStruct(SOUND_OVERLAPPED);
|
|
|
|
if ( Overlap )
|
|
{
|
|
ZeroMemory(Overlap, sizeof(SOUND_OVERLAPPED));
|
|
Overlap->SoundDeviceInstance = SoundDeviceInstance;
|
|
Overlap->Header = Header;
|
|
|
|
/* Adjust the commit-related counters */
|
|
HeaderExtension->BytesCommitted += BytesToCommit;
|
|
++ SoundDeviceInstance->OutstandingBuffers;
|
|
|
|
OK = MMSUCCESS(FunctionTable->CommitWaveBuffer(SoundDeviceInstance,
|
|
OffsetPtr,
|
|
BytesToCommit,
|
|
Overlap,
|
|
CompleteIO));
|
|
|
|
if ( ! OK )
|
|
{
|
|
/* Clean-up and try again on the next iteration (is this OK?) */
|
|
SND_WARN(L"FAILED\n");
|
|
|
|
FreeMemory(Overlap);
|
|
HeaderExtension->BytesCommitted -= BytesToCommit;
|
|
-- SoundDeviceInstance->OutstandingBuffers;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
CompleteIO
|
|
An APC called as a result of a call to CommitWaveHeaderToKernelDevice.
|
|
This will count up the number of bytes which have been dealt with,
|
|
and when the entire wave header has been dealt with, will call
|
|
CompleteWaveHeader to have the wave header returned to the client.
|
|
|
|
CommitWaveHeaderToKernelDevice
|
|
Sends portions of the buffer described by the wave header to a kernel
|
|
device. This must only be called from within the context of the sound
|
|
thread. The caller supplies either their own commit routine, or uses
|
|
WriteFileEx_Committer. The committer is called with portions of the
|
|
buffer specified in the wave header.
|
|
|
|
WriteFileEx_Committer
|
|
Commit buffers using the WriteFileEx API.
|
|
*/
|
|
|
|
VOID CALLBACK
|
|
CompleteIO(
|
|
IN DWORD dwErrorCode,
|
|
IN DWORD dwNumberOfBytesTransferred,
|
|
IN LPOVERLAPPED lpOverlapped)
|
|
{
|
|
MMDEVICE_TYPE DeviceType;
|
|
PSOUND_DEVICE SoundDevice;
|
|
PSOUND_DEVICE_INSTANCE SoundDeviceInstance;
|
|
PSOUND_OVERLAPPED SoundOverlapped = (PSOUND_OVERLAPPED) lpOverlapped;
|
|
PWAVEHDR WaveHdr;
|
|
PWAVEHDR_EXTENSION HdrExtension;
|
|
MMRESULT Result;
|
|
DWORD Bytes;
|
|
|
|
WaveHdr = (PWAVEHDR) SoundOverlapped->Header;
|
|
SND_ASSERT( WaveHdr );
|
|
|
|
HdrExtension = (PWAVEHDR_EXTENSION) WaveHdr->reserved;
|
|
SND_ASSERT( HdrExtension );
|
|
|
|
SoundDeviceInstance = SoundOverlapped->SoundDeviceInstance;
|
|
|
|
Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
|
|
SND_ASSERT( MMSUCCESS(Result) );
|
|
|
|
Result = GetSoundDeviceType(SoundDevice, &DeviceType);
|
|
SND_ASSERT( MMSUCCESS(Result) );
|
|
|
|
do
|
|
{
|
|
|
|
/* We have an available buffer now */
|
|
-- SoundDeviceInstance->OutstandingBuffers;
|
|
|
|
/* Did we finish a WAVEHDR and aren't looping? */
|
|
if (HdrExtension->BytesCompleted + dwNumberOfBytesTransferred >= WaveHdr->dwBufferLength &&
|
|
SoundDeviceInstance->LoopsRemaining == 0)
|
|
{
|
|
/* Wave buffer fully completed */
|
|
Bytes = WaveHdr->dwBufferLength - HdrExtension->BytesCompleted;
|
|
|
|
HdrExtension->BytesCompleted += Bytes;
|
|
dwNumberOfBytesTransferred -= Bytes;
|
|
|
|
CompleteWaveHeader(SoundDeviceInstance, WaveHdr);
|
|
SND_TRACE(L"%d/%d bytes of wavehdr completed\n", HdrExtension->BytesCompleted, WaveHdr->dwBufferLength);
|
|
}
|
|
else
|
|
{
|
|
/* Do we loop a header? */
|
|
if (HdrExtension->BytesCommitted == WaveHdr->dwBufferLength &&
|
|
SoundDeviceInstance->LoopsRemaining != 0)
|
|
{
|
|
/* Reset amount of bytes and decrement loop count, to play next iteration */
|
|
HdrExtension->BytesCommitted = 0;
|
|
|
|
if (SoundDeviceInstance->LoopsRemaining != INFINITE)
|
|
--SoundDeviceInstance->LoopsRemaining;
|
|
SND_TRACE(L"Looping the header, remaining loops %u\n", SoundDeviceInstance->LoopsRemaining);
|
|
}
|
|
else
|
|
{
|
|
/* Partially completed */
|
|
HdrExtension->BytesCompleted += dwNumberOfBytesTransferred;
|
|
SND_TRACE(L"%u/%u bytes of wavehdr completed\n", HdrExtension->BytesCompleted, WaveHdr->dwBufferLength);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Move to next wave header */
|
|
WaveHdr = WaveHdr->lpNext;
|
|
|
|
if (!WaveHdr)
|
|
{
|
|
/* No following WaveHdr */
|
|
SND_ASSERT(dwNumberOfBytesTransferred == 0);
|
|
break;
|
|
}
|
|
|
|
HdrExtension = (PWAVEHDR_EXTENSION) WaveHdr->reserved;
|
|
SND_ASSERT( HdrExtension );
|
|
|
|
|
|
}while(dwNumberOfBytesTransferred);
|
|
|
|
// AUDIO-BRANCH DIFF
|
|
// completion callback is performed in a thread
|
|
DoWaveStreaming(SoundDeviceInstance);
|
|
|
|
//CompleteWavePortion(SoundDeviceInstance, dwNumberOfBytesTransferred);
|
|
|
|
FreeMemory(lpOverlapped);
|
|
}
|
|
|
|
MMRESULT
|
|
WriteFileEx_Committer(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
|
|
IN PVOID OffsetPtr,
|
|
IN DWORD Length,
|
|
IN PSOUND_OVERLAPPED Overlap,
|
|
IN LPOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine)
|
|
{
|
|
HANDLE Handle;
|
|
|
|
VALIDATE_MMSYS_PARAMETER( SoundDeviceInstance );
|
|
VALIDATE_MMSYS_PARAMETER( OffsetPtr );
|
|
VALIDATE_MMSYS_PARAMETER( Overlap );
|
|
VALIDATE_MMSYS_PARAMETER( CompletionRoutine );
|
|
|
|
GetSoundDeviceInstanceHandle(SoundDeviceInstance, &Handle);
|
|
|
|
if ( ! WriteFileEx(Handle, OffsetPtr, Length, (LPOVERLAPPED)Overlap, CompletionRoutine) )
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
Stream control functions
|
|
(External/internal thread pairs)
|
|
|
|
TODO - Move elsewhere as these shouldn't be wave specific!
|
|
*/
|
|
|
|
MMRESULT
|
|
StopStreamingInSoundThread(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
|
|
IN PVOID Parameter)
|
|
{
|
|
MMDEVICE_TYPE DeviceType;
|
|
PMMFUNCTION_TABLE FunctionTable;
|
|
MMRESULT Result;
|
|
PSOUND_DEVICE SoundDevice;
|
|
|
|
/* set state reset in progress */
|
|
SoundDeviceInstance->ResetInProgress = TRUE;
|
|
|
|
/* Get sound device */
|
|
Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
|
|
SND_ASSERT( Result == MMSYSERR_NOERROR );
|
|
|
|
/* Obtain the function table */
|
|
Result = GetSoundDeviceFunctionTable(SoundDevice, &FunctionTable);
|
|
SND_ASSERT( Result == MMSYSERR_NOERROR );
|
|
|
|
/* Obtain device instance type */
|
|
Result = GetSoundDeviceType(SoundDevice, &DeviceType);
|
|
SND_ASSERT( Result == MMSYSERR_NOERROR );
|
|
|
|
/* Check if reset function is supported */
|
|
if (FunctionTable->ResetStream)
|
|
{
|
|
/* cancel all current audio buffers */
|
|
FunctionTable->ResetStream(SoundDeviceInstance, DeviceType, TRUE);
|
|
}
|
|
while(SoundDeviceInstance->OutstandingBuffers)
|
|
{
|
|
SND_TRACE(L"StopStreamingInSoundThread OutStandingBufferCount %lu\n", SoundDeviceInstance->OutstandingBuffers);
|
|
/* wait until pending i/o has completed */
|
|
SleepEx(10, TRUE);
|
|
}
|
|
|
|
/* complete all current headers */
|
|
while( SoundDeviceInstance->HeadWaveHeader )
|
|
{
|
|
SND_TRACE(L"StopStreamingInSoundThread: Completing Header %p\n", SoundDeviceInstance->HeadWaveHeader);
|
|
CompleteWaveHeader( SoundDeviceInstance, SoundDeviceInstance->HeadWaveHeader );
|
|
}
|
|
|
|
/* there should be no oustanding buffers now */
|
|
SND_ASSERT(SoundDeviceInstance->OutstandingBuffers == 0);
|
|
|
|
|
|
/* Check if reset function is supported */
|
|
if (FunctionTable->ResetStream)
|
|
{
|
|
/* finish the reset */
|
|
FunctionTable->ResetStream(SoundDeviceInstance, DeviceType, FALSE);
|
|
}
|
|
|
|
/* clear state reset in progress */
|
|
SoundDeviceInstance->ResetInProgress = FALSE;
|
|
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT
|
|
StopStreaming(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
|
|
{
|
|
MMRESULT Result;
|
|
PSOUND_DEVICE SoundDevice;
|
|
MMDEVICE_TYPE DeviceType;
|
|
|
|
if ( ! IsValidSoundDeviceInstance(SoundDeviceInstance) )
|
|
return MMSYSERR_INVALHANDLE;
|
|
|
|
Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
|
|
if ( ! MMSUCCESS(Result) )
|
|
return TranslateInternalMmResult(Result);
|
|
|
|
Result = GetSoundDeviceType(SoundDevice, &DeviceType);
|
|
if ( ! MMSUCCESS(Result) )
|
|
return TranslateInternalMmResult(Result);
|
|
|
|
if ( DeviceType != WAVE_OUT_DEVICE_TYPE && DeviceType != WAVE_IN_DEVICE_TYPE )
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
|
|
return CallSoundThread(SoundDeviceInstance,
|
|
StopStreamingInSoundThread,
|
|
NULL);
|
|
}
|
|
|
|
MMRESULT
|
|
PerformWaveStreaming(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
|
|
IN PVOID Parameter)
|
|
{
|
|
DoWaveStreaming(SoundDeviceInstance);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
WaveActivateSoundStreaming(
|
|
IN PVOID lpParameter)
|
|
{
|
|
CallSoundThread((PSOUND_DEVICE_INSTANCE)lpParameter,
|
|
PerformWaveStreaming,
|
|
NULL);
|
|
|
|
ExitThread(0);
|
|
}
|
|
|
|
VOID
|
|
InitiateSoundStreaming(
|
|
IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
|
|
{
|
|
HANDLE hThread;
|
|
|
|
hThread = CreateThread(NULL, 0, WaveActivateSoundStreaming, (PVOID)SoundDeviceInstance, 0, NULL);
|
|
|
|
if (hThread != NULL)
|
|
CloseHandle(hThread);
|
|
}
|