reactos/sdk/lib/drivers/sound/mmebuddy/wave/streaming.c
Oleg Dubinskiy 24e088daa8
[MMEBUDDY] Implement support for looped wave playback (#6761)
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
2024-04-24 11:08:40 +02:00

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);
}