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