1 /*
2  * PROJECT:     ReactOS Sound System "MME Buddy" Library
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        lib/drivers/sound/mmebuddy/wave/streaming.c
5  *
6  * PURPOSE:     Wave streaming
7  *
8  * PROGRAMMERS: Andrew Greenwood (silverblade@reactos.org)
9 */
10 
11 #include "precomp.h"
12 
13 /*
14     DoWaveStreaming
15         Check if there is streaming to be done, and if so, do it.
16 */
17 
18 VOID
DoWaveStreaming(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)19 DoWaveStreaming(
20     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
21 {
22     MMRESULT Result;
23     MMDEVICE_TYPE DeviceType;
24     PSOUND_DEVICE SoundDevice;
25     PMMFUNCTION_TABLE FunctionTable;
26     PWAVEHDR Header;
27     PWAVEHDR_EXTENSION HeaderExtension;
28 
29     Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
30     SND_ASSERT( MMSUCCESS(Result) );
31 
32     Result = GetSoundDeviceType(SoundDevice, &DeviceType);
33     SND_ASSERT( MMSUCCESS(Result) );
34 
35     Result = GetSoundDeviceFunctionTable(SoundDevice, &FunctionTable);
36     SND_ASSERT( MMSUCCESS(Result) );
37     SND_ASSERT( FunctionTable );
38     SND_ASSERT( FunctionTable->CommitWaveBuffer );
39 
40     /* No point in doing anything if no resources available to use */
41     if ( SoundDeviceInstance->OutstandingBuffers >= SoundDeviceInstance->BufferCount )
42     {
43         SND_TRACE(L"DoWaveStreaming: No available buffers to stream with - doing nothing\n");
44         return;
45     }
46 
47     /* Is there any work to do? */
48     Header = SoundDeviceInstance->HeadWaveHeader;
49 
50     if ( ! Header )
51     {
52         SND_TRACE(L"DoWaveStreaming: No work to do - doing nothing\n");
53         return;
54     }
55 
56     /* Do we need to loop a header? */
57     if (DeviceType == WAVE_OUT_DEVICE_TYPE && (Header->dwFlags & WHDR_BEGINLOOP))
58     {
59         if ((Header->dwFlags & WHDR_ENDLOOP))
60         {
61             /* Get loop count */
62             SoundDeviceInstance->LoopsRemaining = Header->dwLoops;
63         }
64         else
65         {
66             /* Report and help notice such a case */
67             SND_WARN(L"Looping multiple headers is UNIMPLEMENTED. Will play once only\n");
68             SND_ASSERT((Header->dwFlags & (WHDR_BEGINLOOP | WHDR_ENDLOOP)) == (WHDR_BEGINLOOP | WHDR_ENDLOOP));
69         }
70     }
71 
72     while ( ( SoundDeviceInstance->OutstandingBuffers < SoundDeviceInstance->BufferCount ) &&
73             ( Header ) && SoundDeviceInstance->ResetInProgress == FALSE)
74     {
75         HeaderExtension = (PWAVEHDR_EXTENSION) Header->reserved;
76         SND_ASSERT( HeaderExtension );
77 
78         /* Saniy checks */
79         SND_ASSERT(Header->dwFlags & WHDR_PREPARED);
80         SND_ASSERT(Header->dwFlags & WHDR_INQUEUE);
81 
82         /* Can never be *above* the length */
83         SND_ASSERT( HeaderExtension->BytesCommitted <= Header->dwBufferLength );
84 
85         /* Is this header entirely committed? */
86         if ( HeaderExtension->BytesCommitted == Header->dwBufferLength )
87         {
88             {
89                 /* Move on to the next header */
90                 SND_ASSERT(Header != Header->lpNext);
91                 Header = Header->lpNext;
92             }
93         }
94         else
95         {
96             PSOUND_OVERLAPPED Overlap;
97             LPVOID OffsetPtr;
98             DWORD BytesRemaining, BytesToCommit;
99             BOOL OK;
100 
101             /* Where within the header buffer to stream from */
102             OffsetPtr = Header->lpData + HeaderExtension->BytesCommitted;
103 
104             /* How much of this header has not been committed */
105             BytesRemaining = Header->dwBufferLength - HeaderExtension->BytesCommitted;
106 
107             /* We can commit anything up to the buffer size limit */
108             BytesToCommit = BytesRemaining > SoundDeviceInstance->FrameSize ?
109                             SoundDeviceInstance->FrameSize :
110                             BytesRemaining;
111 
112             /* Should always have something to commit by this point */
113             SND_ASSERT( BytesToCommit > 0 );
114 
115             /* We need a new overlapped info structure for each buffer */
116             Overlap = AllocateStruct(SOUND_OVERLAPPED);
117 
118             if ( Overlap )
119             {
120                 ZeroMemory(Overlap, sizeof(SOUND_OVERLAPPED));
121                 Overlap->SoundDeviceInstance = SoundDeviceInstance;
122                 Overlap->Header = Header;
123 
124                 /* Adjust the commit-related counters */
125                 HeaderExtension->BytesCommitted += BytesToCommit;
126                 ++ SoundDeviceInstance->OutstandingBuffers;
127 
128                 OK = MMSUCCESS(FunctionTable->CommitWaveBuffer(SoundDeviceInstance,
129                                                                OffsetPtr,
130                                                                BytesToCommit,
131                                                                Overlap,
132                                                                CompleteIO));
133 
134                 if ( ! OK )
135                 {
136                     /* Clean-up and try again on the next iteration (is this OK?) */
137                     SND_WARN(L"FAILED\n");
138 
139                     FreeMemory(Overlap);
140                     HeaderExtension->BytesCommitted -= BytesToCommit;
141                     -- SoundDeviceInstance->OutstandingBuffers;
142                 }
143             }
144         }
145     }
146 }
147 
148 
149 /*
150     CompleteIO
151         An APC called as a result of a call to CommitWaveHeaderToKernelDevice.
152         This will count up the number of bytes which have been dealt with,
153         and when the entire wave header has been dealt with, will call
154         CompleteWaveHeader to have the wave header returned to the client.
155 
156     CommitWaveHeaderToKernelDevice
157         Sends portions of the buffer described by the wave header to a kernel
158         device. This must only be called from within the context of the sound
159         thread. The caller supplies either their own commit routine, or uses
160         WriteFileEx_Committer. The committer is called with portions of the
161         buffer specified in the wave header.
162 
163     WriteFileEx_Committer
164         Commit buffers using the WriteFileEx API.
165 */
166 
167 VOID CALLBACK
CompleteIO(IN DWORD dwErrorCode,IN DWORD dwNumberOfBytesTransferred,IN LPOVERLAPPED lpOverlapped)168 CompleteIO(
169     IN  DWORD dwErrorCode,
170     IN  DWORD dwNumberOfBytesTransferred,
171     IN  LPOVERLAPPED lpOverlapped)
172 {
173     MMDEVICE_TYPE DeviceType;
174     PSOUND_DEVICE SoundDevice;
175     PSOUND_DEVICE_INSTANCE SoundDeviceInstance;
176     PSOUND_OVERLAPPED SoundOverlapped = (PSOUND_OVERLAPPED) lpOverlapped;
177     PWAVEHDR WaveHdr;
178     PWAVEHDR_EXTENSION HdrExtension;
179     MMRESULT Result;
180     DWORD Bytes;
181 
182     WaveHdr = (PWAVEHDR) SoundOverlapped->Header;
183     SND_ASSERT( WaveHdr );
184 
185     HdrExtension = (PWAVEHDR_EXTENSION) WaveHdr->reserved;
186     SND_ASSERT( HdrExtension );
187 
188     SoundDeviceInstance = SoundOverlapped->SoundDeviceInstance;
189 
190     Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
191     SND_ASSERT( MMSUCCESS(Result) );
192 
193     Result = GetSoundDeviceType(SoundDevice, &DeviceType);
194     SND_ASSERT( MMSUCCESS(Result) );
195 
196     do
197 	{
198 
199         /* We have an available buffer now */
200         -- SoundDeviceInstance->OutstandingBuffers;
201 
202         /* Did we finish a WAVEHDR and aren't looping? */
203         if (HdrExtension->BytesCompleted + dwNumberOfBytesTransferred >= WaveHdr->dwBufferLength &&
204             SoundDeviceInstance->LoopsRemaining == 0)
205         {
206             /* Wave buffer fully completed */
207             Bytes = WaveHdr->dwBufferLength - HdrExtension->BytesCompleted;
208 
209             HdrExtension->BytesCompleted += Bytes;
210             dwNumberOfBytesTransferred -= Bytes;
211 
212             CompleteWaveHeader(SoundDeviceInstance, WaveHdr);
213             SND_TRACE(L"%d/%d bytes of wavehdr completed\n", HdrExtension->BytesCompleted, WaveHdr->dwBufferLength);
214         }
215 		else
216 		{
217             /* Do we loop a header? */
218             if (HdrExtension->BytesCommitted == WaveHdr->dwBufferLength &&
219                 SoundDeviceInstance->LoopsRemaining != 0)
220             {
221                 /* Reset amount of bytes and decrement loop count, to play next iteration */
222                 HdrExtension->BytesCommitted = 0;
223 
224                 if (SoundDeviceInstance->LoopsRemaining != INFINITE)
225                     --SoundDeviceInstance->LoopsRemaining;
226                 SND_TRACE(L"Looping the header, remaining loops %u\n", SoundDeviceInstance->LoopsRemaining);
227             }
228             else
229             {
230                 /* Partially completed */
231                 HdrExtension->BytesCompleted += dwNumberOfBytesTransferred;
232                 SND_TRACE(L"%u/%u bytes of wavehdr completed\n", HdrExtension->BytesCompleted, WaveHdr->dwBufferLength);
233             }
234 
235             break;
236 		}
237 
238         /* Move to next wave header */
239         WaveHdr = WaveHdr->lpNext;
240 
241         if (!WaveHdr)
242 		{
243             /* No following WaveHdr */
244             SND_ASSERT(dwNumberOfBytesTransferred == 0);
245             break;
246 		}
247 
248         HdrExtension = (PWAVEHDR_EXTENSION) WaveHdr->reserved;
249         SND_ASSERT( HdrExtension );
250 
251 
252 	}while(dwNumberOfBytesTransferred);
253 
254     // AUDIO-BRANCH DIFF
255     // completion callback is performed in a thread
256     DoWaveStreaming(SoundDeviceInstance);
257 
258     //CompleteWavePortion(SoundDeviceInstance, dwNumberOfBytesTransferred);
259 
260     FreeMemory(lpOverlapped);
261 }
262 
263 MMRESULT
WriteFileEx_Committer(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,IN PVOID OffsetPtr,IN DWORD Length,IN PSOUND_OVERLAPPED Overlap,IN LPOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine)264 WriteFileEx_Committer(
265     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
266     IN  PVOID OffsetPtr,
267     IN  DWORD Length,
268     IN  PSOUND_OVERLAPPED Overlap,
269     IN  LPOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine)
270 {
271     HANDLE Handle;
272 
273     VALIDATE_MMSYS_PARAMETER( SoundDeviceInstance );
274     VALIDATE_MMSYS_PARAMETER( OffsetPtr );
275     VALIDATE_MMSYS_PARAMETER( Overlap );
276     VALIDATE_MMSYS_PARAMETER( CompletionRoutine );
277 
278     GetSoundDeviceInstanceHandle(SoundDeviceInstance, &Handle);
279 
280     if ( ! WriteFileEx(Handle, OffsetPtr, Length, (LPOVERLAPPED)Overlap, CompletionRoutine) )
281     {
282         // TODO
283     }
284 
285     return MMSYSERR_NOERROR;
286 }
287 
288 
289 /*
290     Stream control functions
291     (External/internal thread pairs)
292 
293     TODO - Move elsewhere as these shouldn't be wave specific!
294 */
295 
296 MMRESULT
StopStreamingInSoundThread(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,IN PVOID Parameter)297 StopStreamingInSoundThread(
298     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
299     IN  PVOID Parameter)
300 {
301     MMDEVICE_TYPE DeviceType;
302     PMMFUNCTION_TABLE FunctionTable;
303     MMRESULT Result;
304     PSOUND_DEVICE SoundDevice;
305 
306     /* set state reset in progress */
307     SoundDeviceInstance->ResetInProgress = TRUE;
308 
309     /* Get sound device */
310     Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
311     SND_ASSERT( Result == MMSYSERR_NOERROR );
312 
313     /* Obtain the function table */
314     Result = GetSoundDeviceFunctionTable(SoundDevice, &FunctionTable);
315     SND_ASSERT( Result == MMSYSERR_NOERROR );
316 
317     /* Obtain device instance type */
318     Result = GetSoundDeviceType(SoundDevice, &DeviceType);
319     SND_ASSERT( Result == MMSYSERR_NOERROR );
320 
321     /* Check if reset function is supported */
322     if (FunctionTable->ResetStream)
323     {
324          /* cancel all current audio buffers */
325          FunctionTable->ResetStream(SoundDeviceInstance, DeviceType, TRUE);
326     }
327     while(SoundDeviceInstance->OutstandingBuffers)
328     {
329         SND_TRACE(L"StopStreamingInSoundThread OutStandingBufferCount %lu\n", SoundDeviceInstance->OutstandingBuffers);
330         /* wait until pending i/o has completed */
331         SleepEx(10, TRUE);
332     }
333 
334     /* complete all current headers */
335     while( SoundDeviceInstance->HeadWaveHeader )
336     {
337         SND_TRACE(L"StopStreamingInSoundThread: Completing Header %p\n", SoundDeviceInstance->HeadWaveHeader);
338         CompleteWaveHeader( SoundDeviceInstance, SoundDeviceInstance->HeadWaveHeader );
339     }
340 
341     /* there should be no oustanding buffers now */
342     SND_ASSERT(SoundDeviceInstance->OutstandingBuffers == 0);
343 
344 
345     /* Check if reset function is supported */
346     if (FunctionTable->ResetStream)
347     {
348         /* finish the reset */
349         FunctionTable->ResetStream(SoundDeviceInstance, DeviceType, FALSE);
350     }
351 
352     /* clear state reset in progress */
353     SoundDeviceInstance->ResetInProgress = FALSE;
354 
355 
356     return MMSYSERR_NOERROR;
357 }
358 
359 MMRESULT
StopStreaming(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)360 StopStreaming(
361     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
362 {
363     MMRESULT Result;
364     PSOUND_DEVICE SoundDevice;
365     MMDEVICE_TYPE DeviceType;
366 
367     if ( ! IsValidSoundDeviceInstance(SoundDeviceInstance) )
368         return MMSYSERR_INVALHANDLE;
369 
370     Result = GetSoundDeviceFromInstance(SoundDeviceInstance, &SoundDevice);
371     if ( ! MMSUCCESS(Result) )
372         return TranslateInternalMmResult(Result);
373 
374     Result = GetSoundDeviceType(SoundDevice, &DeviceType);
375     if ( ! MMSUCCESS(Result) )
376         return TranslateInternalMmResult(Result);
377 
378     if ( DeviceType != WAVE_OUT_DEVICE_TYPE && DeviceType != WAVE_IN_DEVICE_TYPE )
379         return MMSYSERR_NOTSUPPORTED;
380 
381     return CallSoundThread(SoundDeviceInstance,
382                            StopStreamingInSoundThread,
383                            NULL);
384 }
385 
386 MMRESULT
PerformWaveStreaming(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance,IN PVOID Parameter)387 PerformWaveStreaming(
388     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance,
389     IN  PVOID Parameter)
390 {
391     DoWaveStreaming(SoundDeviceInstance);
392 
393     return MMSYSERR_NOERROR;
394 }
395 
396 DWORD
397 WINAPI
WaveActivateSoundStreaming(IN PVOID lpParameter)398 WaveActivateSoundStreaming(
399     IN PVOID lpParameter)
400 {
401     CallSoundThread((PSOUND_DEVICE_INSTANCE)lpParameter,
402                     PerformWaveStreaming,
403                     NULL);
404 
405     ExitThread(0);
406 }
407 
408 VOID
InitiateSoundStreaming(IN PSOUND_DEVICE_INSTANCE SoundDeviceInstance)409 InitiateSoundStreaming(
410     IN  PSOUND_DEVICE_INSTANCE SoundDeviceInstance)
411 {
412     HANDLE hThread;
413 
414     hThread = CreateThread(NULL, 0, WaveActivateSoundStreaming, (PVOID)SoundDeviceInstance, 0, NULL);
415 
416     if (hThread != NULL)
417         CloseHandle(hThread);
418 }
419