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
16 #define MAX_WAVE_BUFFER_SIZE 65536
21 SessionInfo
* session_info
,
22 LPWAVEHDR wave_header
)
24 PWAVEHDR queue_node
, previous_node
;
25 DPRINT("Queueing wave buffer\n");
29 return MMSYSERR_INVALPARAM
;
32 if ( ! wave_header
->lpData
)
34 return MMSYSERR_INVALPARAM
;
37 /* Headers must be prepared first */
38 if ( ! ( wave_header
->dwFlags
& WHDR_PREPARED
) )
40 DPRINT("I was given a header which hasn't been prepared yet!\n");
41 return WAVERR_UNPREPARED
;
44 /* ...and they must not already be in the playing queue! */
45 if ( wave_header
->dwFlags
& WHDR_INQUEUE
)
47 DPRINT("I was given a header for a buffer which is already playing\n");
48 return WAVERR_STILLPLAYING
;
52 wave_header
->dwBytesRecorded
= 0;
54 /* Clear the DONE bit, and mark the buffer as queued */
55 wave_header
->dwFlags
&= ~WHDR_DONE
;
56 wave_header
->dwFlags
|= WHDR_INQUEUE
;
58 /* Save our handle in the header */
59 wave_header
->reserved
= (DWORD_PTR
) session_info
;
61 /* Locate the end of the queue */
63 queue_node
= session_info
->wave_queue
;
67 previous_node
= queue_node
;
68 queue_node
= queue_node
->lpNext
;
71 /* Go back a step to obtain the previous node (non-NULL) */
72 queue_node
= previous_node
;
74 /* Append our buffer here, and terminate the queue */
75 queue_node
->lpNext
= wave_header
;
76 wave_header
->lpNext
= NULL
;
78 /* When no buffers are playing there's no play queue so we start one */
80 if ( ! session_info
->next_buffer
)
82 session_info
->buffer_position
= 0;
83 session_info
->next_buffer
= wave_header
;
87 /* Pass to the driver - happens automatically during playback */
88 // return PerformWaveIO(session_info);
89 return MMSYSERR_NOERROR
;
93 ReturnCompletedBuffers(SessionInfo
* session_info
)
95 PWAVEHDR header
= NULL
;
97 /* Set the current header and test to ensure it's not NULL */
98 while ( ( header
= session_info
->wave_queue
) )
100 if ( header
->dwFlags
& WHDR_DONE
)
104 /* Mark as done, and unqueued */
105 header
->dwFlags
&= ~WHDR_INQUEUE
;
106 header
->dwFlags
|= WHDR_DONE
;
108 /* Trim it from the start of the queue */
109 session_info
->wave_queue
= header
->lpNext
;
111 /* Choose appropriate notification */
112 message
= (session_info
->device_type
== WaveOutDevice
) ? WOM_DONE
:
115 DPRINT("Notifying client that buffer 0x%p is done\n", header
);
117 /* Notify the client */
118 NotifyClient(session_info
, message
, (DWORD_PTR
) header
, 0);
122 /* TODO: Perform I/O as a new buffer may have arrived */
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().
135 ProcessSessionThreadRequest(SessionInfo
* session_info
)
137 MMRESULT result
= MMSYSERR_NOERROR
;
139 switch ( session_info
->thread
.function
)
143 result
= QueueWaveBuffer(session_info
,
144 (LPWAVEHDR
) session_info
->thread
.parameter
);
174 result
= SetDeviceData(session_info
->kernel_device_handle
,
175 IOCTL_WAVE_SET_PITCH
,
176 (PBYTE
) session_info
->thread
.parameter
,
183 result
= GetDeviceData(session_info
->kernel_device_handle
,
184 IOCTL_WAVE_GET_PITCH
,
185 (PBYTE
) session_info
->thread
.parameter
,
190 case WODM_SETVOLUME
:
195 case WODM_GETVOLUME
:
198 result
= GetDeviceData(session_info
->kernel_device_handle
,
199 IOCTL_WAVE_GET_VOLUME
,
200 (PBYTE
) session_info
->thread
.parameter
,);
205 case WODM_SETPLAYBACKRATE
:
207 result
= SetDeviceData(session_info
->kernel_device_handle
,
208 IOCTL_WAVE_SET_PLAYBACK_RATE
,
209 (PBYTE
) session_info
->thread
.parameter
,
214 case WODM_GETPLAYBACKRATE
:
216 result
= GetDeviceData(session_info
->kernel_device_handle
,
217 IOCTL_WAVE_GET_PLAYBACK_RATE
,
218 (PBYTE
) session_info
->thread
.parameter
,
225 DPRINT("Thread was asked if OK to close device\n");
227 if ( session_info
->wave_queue
!= NULL
)
228 result
= WAVERR_STILLPLAYING
;
230 result
= MMSYSERR_NOERROR
;
235 case DRVM_TERMINATE
:
237 DPRINT("Terminating thread...\n");
238 result
= MMSYSERR_NOERROR
;
244 DPRINT("INVALID FUNCTION\n");
245 result
= MMSYSERR_ERROR
;
250 /* We're done with the function now */
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.
265 WaveThread(LPVOID parameter
)
267 MMRESULT result
= MMSYSERR_ERROR
;
268 SessionInfo
* session_info
= (SessionInfo
*) parameter
;
269 BOOL terminate
= FALSE
;
271 /* All your CPU time are belong to us */
272 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL
);
274 DPRINT("Wave processing thread setting ready state\n");
276 SetEvent(session_info
->thread
.ready_event
);
278 while ( ! terminate
)
280 /* Wait for GO event, or IO completion notification */
281 while ( WaitForSingleObjectEx(session_info
->thread
.go_event
,
283 TRUE
) == WAIT_IO_COMPLETION
)
285 /* A buffer has been finished with - pass back to the client */
286 ReturnCompletedBuffers(session_info
);
289 DPRINT("Wave processing thread woken up\n");
291 /* Set the terminate flag if that's what the caller wants */
292 terminate
= (session_info
->thread
.function
== DRVM_TERMINATE
);
294 /* Process the request */
295 DPRINT("Processing thread request\n");
296 result
= ProcessSessionThreadRequest(session_info
);
298 /* Store the result code */
299 session_info
->thread
.result
= result
;
301 /* Submit new buffers and continue existing ones */
302 DPRINT("Performing wave I/O\n");
303 PerformWaveIO(session_info
);
305 /* Now we're ready for more action */
306 DPRINT("Wave processing thread sleeping\n");
307 SetEvent(session_info
->thread
.ready_event
);
315 Convenience function for calculating the size of the WAVEFORMATEX struct.
319 GetWaveFormatExSize(PWAVEFORMATEX format
)
321 if ( format
->wFormatTag
== WAVE_FORMAT_PCM
)
322 return sizeof(PCMWAVEFORMAT
);
324 return sizeof(WAVEFORMATEX
) + format
->cbSize
;
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.
335 DeviceType device_type
,
339 return WAVERR_BADFORMAT
;
344 Set the format to be used.
349 HANDLE device_handle
,
350 PWAVEFORMATEX format
)
352 DWORD bytes_returned
;
355 size
= GetWaveFormatExSize(format
);
357 DPRINT("SetWaveFormat\n");
359 return DeviceIoControl(device_handle
,
360 IOCTL_WAVE_SET_FORMAT
,
372 DWORD_PTR private_handle
,
373 PWAVEHDR wave_header
,
374 DWORD wave_header_size
)
376 SessionInfo
* session_info
= (SessionInfo
*) private_handle
;
377 ASSERT(session_info
);
379 /* Let the processing thread know that it has work to do */
380 return CallSessionThread(session_info
, WODM_WRITE
, wave_header
);