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
9 * Jan 30, 2004: Imported into ReactOS tree
10 * Jan 14, 2007: Rewritten and tidied up
18 #define MAX_WAVE_BUFFER_SIZE 65536
22 SessionInfo
* session_info
,
23 LPWAVEHDR wave_header
)
25 PWAVEHDR queue_node
, previous_node
;
26 DPRINT("Queueing wave buffer\n");
30 return MMSYSERR_INVALPARAM
;
33 if ( ! wave_header
->lpData
)
35 return MMSYSERR_INVALPARAM
;
38 /* Headers must be prepared first */
39 if ( ! ( wave_header
->dwFlags
& WHDR_PREPARED
) )
41 DPRINT("I was given a header which hasn't been prepared yet!\n");
42 return WAVERR_UNPREPARED
;
45 /* ...and they must not already be in the playing queue! */
46 if ( wave_header
->dwFlags
& WHDR_INQUEUE
)
48 DPRINT("I was given a header for a buffer which is already playing\n");
49 return WAVERR_STILLPLAYING
;
53 wave_header
->dwBytesRecorded
= 0;
55 /* Clear the DONE bit, and mark the buffer as queued */
56 wave_header
->dwFlags
&= ~WHDR_DONE
;
57 wave_header
->dwFlags
|= WHDR_INQUEUE
;
59 /* Save our handle in the header */
60 wave_header
->reserved
= (DWORD_PTR
) session_info
;
62 /* Locate the end of the queue */
64 queue_node
= session_info
->wave_queue
;
68 previous_node
= queue_node
;
69 queue_node
= queue_node
->lpNext
;
72 /* Go back a step to obtain the previous node (non-NULL) */
73 queue_node
= previous_node
;
75 /* Append our buffer here, and terminate the queue */
76 queue_node
->lpNext
= wave_header
;
77 wave_header
->lpNext
= NULL
;
79 /* When no buffers are playing there's no play queue so we start one */
81 if ( ! session_info
->next_buffer
)
83 session_info
->buffer_position
= 0;
84 session_info
->next_buffer
= wave_header
;
88 /* Pass to the driver - happens automatically during playback */
89 // return PerformWaveIO(session_info);
90 return MMSYSERR_NOERROR
;
94 ReturnCompletedBuffers(SessionInfo
* session_info
)
96 PWAVEHDR header
= NULL
;
98 /* Set the current header and test to ensure it's not NULL */
99 while ( ( header
= session_info
->wave_queue
) )
101 if ( header
->dwFlags
& WHDR_DONE
)
105 /* Mark as done, and unqueued */
106 header
->dwFlags
&= ~WHDR_INQUEUE
;
107 header
->dwFlags
|= WHDR_DONE
;
109 /* Trim it from the start of the queue */
110 session_info
->wave_queue
= header
->lpNext
;
112 /* Choose appropriate notification */
113 message
= (session_info
->device_type
== WaveOutDevice
) ? WOM_DONE
:
116 DPRINT("Notifying client that buffer 0x%p is done\n", header
);
118 /* Notify the client */
119 NotifyClient(session_info
, message
, (DWORD_PTR
) header
, 0);
123 /* TODO: Perform I/O as a new buffer may have arrived */
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().
136 ProcessSessionThreadRequest(SessionInfo
* session_info
)
138 MMRESULT result
= MMSYSERR_NOERROR
;
140 switch ( session_info
->thread
.function
)
144 result
= QueueWaveBuffer(session_info
,
145 (LPWAVEHDR
) session_info
->thread
.parameter
);
175 result
= SetDeviceData(session_info
->kernel_device_handle
,
176 IOCTL_WAVE_SET_PITCH
,
177 (PBYTE
) session_info
->thread
.parameter
,
184 result
= GetDeviceData(session_info
->kernel_device_handle
,
185 IOCTL_WAVE_GET_PITCH
,
186 (PBYTE
) session_info
->thread
.parameter
,
191 case WODM_SETVOLUME
:
196 case WODM_GETVOLUME
:
199 result
= GetDeviceData(session_info
->kernel_device_handle
,
200 IOCTL_WAVE_GET_VOLUME
,
201 (PBYTE
) session_info
->thread
.parameter
,);
206 case WODM_SETPLAYBACKRATE
:
208 result
= SetDeviceData(session_info
->kernel_device_handle
,
209 IOCTL_WAVE_SET_PLAYBACK_RATE
,
210 (PBYTE
) session_info
->thread
.parameter
,
215 case WODM_GETPLAYBACKRATE
:
217 result
= GetDeviceData(session_info
->kernel_device_handle
,
218 IOCTL_WAVE_GET_PLAYBACK_RATE
,
219 (PBYTE
) session_info
->thread
.parameter
,
226 DPRINT("Thread was asked if OK to close device\n");
228 if ( session_info
->wave_queue
!= NULL
)
229 result
= WAVERR_STILLPLAYING
;
231 result
= MMSYSERR_NOERROR
;
236 case DRVM_TERMINATE
:
238 DPRINT("Terminating thread...\n");
239 result
= MMSYSERR_NOERROR
;
245 DPRINT("INVALID FUNCTION\n");
246 result
= MMSYSERR_ERROR
;
251 /* We're done with the function now */
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.
266 WaveThread(LPVOID parameter
)
268 MMRESULT result
= MMSYSERR_ERROR
;
269 SessionInfo
* session_info
= (SessionInfo
*) parameter
;
270 BOOL terminate
= FALSE
;
272 /* All your CPU time are belong to us */
273 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL
);
275 DPRINT("Wave processing thread setting ready state\n");
277 SetEvent(session_info
->thread
.ready_event
);
279 while ( ! terminate
)
281 /* Wait for GO event, or IO completion notification */
282 while ( WaitForSingleObjectEx(session_info
->thread
.go_event
,
284 TRUE
) == WAIT_IO_COMPLETION
)
286 /* A buffer has been finished with - pass back to the client */
287 ReturnCompletedBuffers(session_info
);
290 DPRINT("Wave processing thread woken up\n");
292 /* Set the terminate flag if that's what the caller wants */
293 terminate
= (session_info
->thread
.function
== DRVM_TERMINATE
);
295 /* Process the request */
296 DPRINT("Processing thread request\n");
297 result
= ProcessSessionThreadRequest(session_info
);
299 /* Store the result code */
300 session_info
->thread
.result
= result
;
302 /* Submit new buffers and continue existing ones */
303 DPRINT("Performing wave I/O\n");
304 PerformWaveIO(session_info
);
306 /* Now we're ready for more action */
307 DPRINT("Wave processing thread sleeping\n");
308 SetEvent(session_info
->thread
.ready_event
);
316 Convenience function for calculating the size of the WAVEFORMATEX struct.
320 GetWaveFormatExSize(PWAVEFORMATEX format
)
322 if ( format
->wFormatTag
== WAVE_FORMAT_PCM
)
323 return sizeof(PCMWAVEFORMAT
);
325 return sizeof(WAVEFORMATEX
) + format
->cbSize
;
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.
336 DeviceType device_type
,
340 return WAVERR_BADFORMAT
;
345 Set the format to be used.
350 HANDLE device_handle
,
351 PWAVEFORMATEX format
)
353 DWORD bytes_returned
;
356 size
= GetWaveFormatExSize(format
);
358 DPRINT("SetWaveFormat\n");
360 return DeviceIoControl(device_handle
,
361 IOCTL_WAVE_SET_FORMAT
,
373 DWORD_PTR private_handle
,
374 PWAVEHDR wave_header
,
375 DWORD wave_header_size
)
377 SessionInfo
* session_info
= (SessionInfo
*) private_handle
;
378 ASSERT(session_info
);
380 /* Let the processing thread know that it has work to do */
381 return CallSessionThread(session_info
, WODM_WRITE
, wave_header
);