* Sync up to trunk head (r65120).
[reactos.git] / dll / win32 / mmdrv / wave.c
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 }