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