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