xref: /reactos/dll/win32/mmdrv/wave.c (revision 40462c92)
1 /*
2  *
3  * COPYRIGHT:            See COPYING in the top level directory
4  * PROJECT:              ReactOS Multimedia
5  * FILE:                 dll/win32/mmdrv/wave.c
6  * PURPOSE:              Multimedia User Mode Driver (Wave Audio)
7  * PROGRAMMER:           Andrew Greenwood
8  * UPDATE HISTORY:
9  *                       Jan 30, 2004: Imported into ReactOS tree
10  *                       Jan 14, 2007: Rewritten and tidied up
11  */
12 
13 #include "mmdrv.h"
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 #define MAX_WAVE_BUFFER_SIZE    65536
19 
20 MMRESULT
21 QueueWaveBuffer(
22     SessionInfo* session_info,
23     LPWAVEHDR wave_header)
24 {
25     PWAVEHDR queue_node, previous_node;
26     DPRINT("Queueing wave buffer\n");
27 
28     if ( ! wave_header )
29     {
30         return MMSYSERR_INVALPARAM;
31     }
32 
33     if ( ! wave_header->lpData )
34     {
35         return MMSYSERR_INVALPARAM;
36     }
37 
38     /* Headers must be prepared first */
39     if ( ! ( wave_header->dwFlags & WHDR_PREPARED ) )
40     {
41         DPRINT("I was given a header which hasn't been prepared yet!\n");
42         return WAVERR_UNPREPARED;
43     }
44 
45     /* ...and they must not already be in the playing queue! */
46     if ( wave_header->dwFlags & WHDR_INQUEUE )
47     {
48         DPRINT("I was given a header for a buffer which is already playing\n");
49         return WAVERR_STILLPLAYING;
50     }
51 
52     /* Initialize */
53     wave_header->dwBytesRecorded = 0;
54 
55     /* Clear the DONE bit, and mark the buffer as queued */
56     wave_header->dwFlags &= ~WHDR_DONE;
57     wave_header->dwFlags |= WHDR_INQUEUE;
58 
59     /* Save our handle in the header */
60     wave_header->reserved = (DWORD_PTR) session_info;
61 
62     /* Locate the end of the queue */
63     previous_node = NULL;
64     queue_node = session_info->wave_queue;
65 
66     while ( queue_node )
67     {
68         previous_node = queue_node;
69         queue_node = queue_node->lpNext;
70     }
71 
72     /* Go back a step to obtain the previous node (non-NULL) */
73     queue_node = previous_node;
74 
75     /* Append our buffer here, and terminate the queue */
76     queue_node->lpNext = wave_header;
77     wave_header->lpNext = NULL;
78 
79     /* When no buffers are playing there's no play queue so we start one */
80 #if 0
81     if ( ! session_info->next_buffer )
82     {
83         session_info->buffer_position = 0;
84         session_info->next_buffer = wave_header;
85     }
86 #endif
87 
88     /* Pass to the driver - happens automatically during playback */
89 //    return PerformWaveIO(session_info);
90     return MMSYSERR_NOERROR;
91 }
92 
93 VOID
94 ReturnCompletedBuffers(SessionInfo* session_info)
95 {
96     PWAVEHDR header = NULL;
97 
98     /* Set the current header and test to ensure it's not NULL */
99     while ( ( header = session_info->wave_queue ) )
100     {
101         if ( header->dwFlags & WHDR_DONE )
102         {
103             DWORD message;
104 
105             /* Mark as done, and unqueued */
106             header->dwFlags &= ~WHDR_INQUEUE;
107             header->dwFlags |= WHDR_DONE;
108 
109             /* Trim it from the start of the queue */
110             session_info->wave_queue = header->lpNext;
111 
112             /* Choose appropriate notification */
113             message = (session_info->device_type == WaveOutDevice) ? WOM_DONE :
114                                                                      WIM_DATA;
115 
116             DPRINT("Notifying client that buffer 0x%p is done\n", header);
117 
118             /* Notify the client */
119             NotifyClient(session_info, message, (DWORD_PTR) header, 0);
120         }
121     }
122 
123     /* TODO: Perform I/O as a new buffer may have arrived */
124 }
125 
126 
127 /*
128     Each thread function/request is packed into the SessionInfo structure
129     using a function ID and a parameter (in some cases.) When the function
130     completes, the function code is set to an "invalid" value. This is,
131     effectively, a hub for operations where sound driver I/O is concerned.
132     It handles MME message codes so is a form of deferred wodMessage().
133 */
134 
135 DWORD
136 ProcessSessionThreadRequest(SessionInfo* session_info)
137 {
138     MMRESULT result = MMSYSERR_NOERROR;
139 
140     switch ( session_info->thread.function )
141     {
142         case WODM_WRITE :
143         {
144             result = QueueWaveBuffer(session_info,
145                                      (LPWAVEHDR) session_info->thread.parameter);
146             break;
147         }
148 
149         case WODM_RESET :
150         {
151             /* TODO */
152             break;
153         }
154 
155         case WODM_PAUSE :
156         {
157             /* TODO */
158             break;
159         }
160 
161         case WODM_RESTART :
162         {
163             /* TODO */
164             break;
165         }
166 
167         case WODM_GETPOS :
168         {
169             /* TODO */
170             break;
171         }
172 
173         case WODM_SETPITCH :
174         {
175             result = SetDeviceData(session_info->kernel_device_handle,
176                                    IOCTL_WAVE_SET_PITCH,
177                                    (PBYTE) session_info->thread.parameter,
178                                    sizeof(DWORD));
179             break;
180         }
181 
182         case WODM_GETPITCH :
183         {
184             result = GetDeviceData(session_info->kernel_device_handle,
185                                    IOCTL_WAVE_GET_PITCH,
186                                    (PBYTE) session_info->thread.parameter,
187                                    sizeof(DWORD));
188             break;
189         }
190 
191         case WODM_SETVOLUME :
192         {
193             break;
194         }
195 
196         case WODM_GETVOLUME :
197         {
198 #if 0
199             result = GetDeviceData(session_info->kernel_device_handle,
200                                    IOCTL_WAVE_GET_VOLUME,
201                                    (PBYTE) session_info->thread.parameter,);
202 #endif
203             break;
204         }
205 
206         case WODM_SETPLAYBACKRATE :
207         {
208             result = SetDeviceData(session_info->kernel_device_handle,
209                                    IOCTL_WAVE_SET_PLAYBACK_RATE,
210                                    (PBYTE) session_info->thread.parameter,
211                                    sizeof(DWORD));
212             break;
213         }
214 
215         case WODM_GETPLAYBACKRATE :
216         {
217             result = GetDeviceData(session_info->kernel_device_handle,
218                                    IOCTL_WAVE_GET_PLAYBACK_RATE,
219                                    (PBYTE) session_info->thread.parameter,
220                                    sizeof(DWORD));
221             break;
222         }
223 
224         case WODM_CLOSE :
225         {
226             DPRINT("Thread was asked if OK to close device\n");
227 
228             if ( session_info->wave_queue != NULL )
229                 result = WAVERR_STILLPLAYING;
230             else
231                 result = MMSYSERR_NOERROR;
232 
233             break;
234         }
235 
236         case DRVM_TERMINATE :
237         {
238             DPRINT("Terminating thread...\n");
239             result = MMSYSERR_NOERROR;
240             break;
241         }
242 
243         default :
244         {
245             DPRINT("INVALID FUNCTION\n");
246             result = MMSYSERR_ERROR;
247             break;
248         }
249     }
250 
251     /* We're done with the function now */
252 
253     return result;
254 }
255 
256 
257 /*
258     The wave "session". This starts, sets itself as high priority, then waits
259     for the "go" event. When this occurs, it processes the requested function,
260     tidies up any buffers that have finished playing, sends new buffers to the
261     sound driver, then continues handing finished buffers back to the calling
262     application until it's asked to do something else.
263 */
264 
265 DWORD
266 WaveThread(LPVOID parameter)
267 {
268     MMRESULT result = MMSYSERR_ERROR;
269     SessionInfo* session_info = (SessionInfo*) parameter;
270     BOOL terminate = FALSE;
271 
272     /* All your CPU time are belong to us */
273     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
274 
275     DPRINT("Wave processing thread setting ready state\n");
276 
277     SetEvent(session_info->thread.ready_event);
278 
279     while ( ! terminate )
280     {
281         /* Wait for GO event, or IO completion notification */
282         while ( WaitForSingleObjectEx(session_info->thread.go_event,
283                                       INFINITE,
284                                       TRUE) == WAIT_IO_COMPLETION )
285         {
286             /* A buffer has been finished with - pass back to the client */
287             ReturnCompletedBuffers(session_info);
288         }
289 
290         DPRINT("Wave processing thread woken up\n");
291 
292         /* Set the terminate flag if that's what the caller wants */
293         terminate = (session_info->thread.function == DRVM_TERMINATE);
294 
295         /* Process the request */
296         DPRINT("Processing thread request\n");
297         result = ProcessSessionThreadRequest(session_info);
298 
299         /* Store the result code */
300         session_info->thread.result = result;
301 
302         /* Submit new buffers and continue existing ones */
303         DPRINT("Performing wave I/O\n");
304         PerformWaveIO(session_info);
305 
306         /* Now we're ready for more action */
307         DPRINT("Wave processing thread sleeping\n");
308         SetEvent(session_info->thread.ready_event);
309     }
310 
311     return 0;
312 }
313 
314 
315 /*
316     Convenience function for calculating the size of the WAVEFORMATEX struct.
317 */
318 
319 DWORD
320 GetWaveFormatExSize(PWAVEFORMATEX format)
321 {
322     if ( format->wFormatTag == WAVE_FORMAT_PCM )
323         return sizeof(PCMWAVEFORMAT);
324     else
325         return sizeof(WAVEFORMATEX) + format->cbSize;
326 }
327 
328 
329 /*
330     Query if the driver/device is capable of handling a format. This is called
331     if the device is a wave device, and the QUERYFORMAT flag is set.
332 */
333 
334 DWORD
335 QueryWaveFormat(
336     DeviceType device_type,
337     PVOID lpFormat)
338 {
339     /* TODO */
340     return WAVERR_BADFORMAT;
341 }
342 
343 
344 /*
345     Set the format to be used.
346 */
347 
348 BOOL
349 SetWaveFormat(
350     HANDLE device_handle,
351     PWAVEFORMATEX format)
352 {
353     DWORD bytes_returned;
354     DWORD size;
355 
356     size = GetWaveFormatExSize(format);
357 
358     DPRINT("SetWaveFormat\n");
359 
360     return DeviceIoControl(device_handle,
361                            IOCTL_WAVE_SET_FORMAT,
362                            (PVOID) format,
363                            size,
364                            NULL,
365                            0,
366                            &bytes_returned,
367                            NULL);
368 }
369 
370 
371 DWORD
372 WriteWaveBuffer(
373     DWORD_PTR private_handle,
374     PWAVEHDR wave_header,
375     DWORD wave_header_size)
376 {
377     SessionInfo* session_info = (SessionInfo*) private_handle;
378     ASSERT(session_info);
379 
380     /* Let the processing thread know that it has work to do */
381     return CallSessionThread(session_info, WODM_WRITE, wave_header);
382 }
383