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 } 383