[MCICDA] Sync with Wine Staging 3.3. CORE-14434
[reactos.git] / dll / win32 / mcicda / mcicda.c
1 /*
2 * MCI driver for audio CD (MCICDA)
3 *
4 * Copyright 1994 Martin Ayotte
5 * Copyright 1998-99 Eric Pouech
6 * Copyright 2000 Andreas Mohr
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include "config.h"
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #define WIN32_NO_STATUS
29 #include "windef.h"
30 #include "winbase.h"
31 #include "wingdi.h"
32 #include "winuser.h"
33 #include "wownt32.h"
34 #include "mmddk.h"
35 #include "winioctl.h"
36 #include "ntddcdrm.h"
37 #include "wine/winternl.h"
38 #include "wine/debug.h"
39 #include "wine/unicode.h"
40 #include "dsound.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(mcicda);
43
44 #define CDFRAMES_PERSEC 75
45 #define CDFRAMES_PERMIN (CDFRAMES_PERSEC * 60)
46 #define FRAME_OF_ADDR(a) ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
47 #define FRAME_OF_TOC(toc, idx) FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)
48
49 /* Defined by red-book standard; do not change! */
50 #define RAW_SECTOR_SIZE (2352)
51
52 /* Must be >= RAW_SECTOR_SIZE */
53 #define CDDA_FRAG_SIZE (32768)
54 /* Must be >= 2 */
55 #define CDDA_FRAG_COUNT (3)
56
57 typedef struct {
58 UINT wDevID;
59 int nUseCount; /* Incremented for each shared open */
60 BOOL fShareable; /* TRUE if first open was shareable */
61 MCIDEVICEID wNotifyDeviceID; /* MCI device ID with a pending notification */
62 HANDLE hCallback; /* Callback handle for pending notification */
63 DWORD dwTimeFormat;
64 HANDLE handle;
65
66 /* The following are used for digital playback only */
67 HANDLE hThread;
68 HANDLE stopEvent;
69 DWORD start, end;
70
71 IDirectSound *dsObj;
72 IDirectSoundBuffer *dsBuf;
73
74 CRITICAL_SECTION cs;
75 } WINE_MCICDAUDIO;
76
77 /*-----------------------------------------------------------------------*/
78
79 typedef HRESULT(WINAPI*LPDIRECTSOUNDCREATE)(LPCGUID,LPDIRECTSOUND*,LPUNKNOWN);
80 static LPDIRECTSOUNDCREATE pDirectSoundCreate;
81
82 static BOOL device_io(HANDLE dev, DWORD code, void *inbuffer, DWORD insize, void *outbuffer, DWORD outsize, DWORD *retsize, OVERLAPPED *overlapped)
83 {
84 const char *str;
85 BOOL ret = DeviceIoControl(dev, code, inbuffer, insize, outbuffer, outsize, retsize, overlapped);
86
87 #define XX(x) case (x): str = #x; break
88 switch (code)
89 {
90 XX(IOCTL_CDROM_RAW_READ);
91 XX(IOCTL_CDROM_READ_TOC);
92 XX(IOCTL_CDROM_READ_Q_CHANNEL);
93 XX(IOCTL_CDROM_SEEK_AUDIO_MSF);
94 XX(IOCTL_CDROM_PLAY_AUDIO_MSF);
95 XX(IOCTL_CDROM_STOP_AUDIO);
96 XX(IOCTL_CDROM_PAUSE_AUDIO);
97 XX(IOCTL_CDROM_RESUME_AUDIO);
98 XX(IOCTL_STORAGE_EJECT_MEDIA);
99 XX(IOCTL_STORAGE_LOAD_MEDIA);
100 default: str = wine_dbg_sprintf("UNKNOWN (0x%x)", code);
101 };
102 #undef XX
103 TRACE("Device %p, Code %s -> Return %d, Bytes %u\n", dev, str, ret, *retsize);
104 return ret;
105 }
106
107 static DWORD CALLBACK MCICDA_playLoop(void *ptr)
108 {
109 WINE_MCICDAUDIO *wmcda = (WINE_MCICDAUDIO*)ptr;
110 DWORD lastPos, curPos, endPos, br;
111 void *cdData;
112 DWORD lockLen, fragLen;
113 DSBCAPS caps;
114 RAW_READ_INFO rdInfo;
115 HRESULT hr = DS_OK;
116
117 memset(&caps, 0, sizeof(caps));
118 caps.dwSize = sizeof(caps);
119 hr = IDirectSoundBuffer_GetCaps(wmcda->dsBuf, &caps);
120
121 fragLen = caps.dwBufferBytes/CDDA_FRAG_COUNT;
122 curPos = lastPos = 0;
123 endPos = ~0u;
124 while (SUCCEEDED(hr) && endPos != lastPos &&
125 WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0) {
126 hr = IDirectSoundBuffer_GetCurrentPosition(wmcda->dsBuf, &curPos, NULL);
127 if ((curPos-lastPos+caps.dwBufferBytes)%caps.dwBufferBytes < fragLen) {
128 Sleep(1);
129 continue;
130 }
131
132 EnterCriticalSection(&wmcda->cs);
133 rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
134 rdInfo.SectorCount = min(fragLen/RAW_SECTOR_SIZE, wmcda->end-wmcda->start);
135 rdInfo.TrackMode = CDDA;
136
137 hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
138 if (hr == DSERR_BUFFERLOST) {
139 if(FAILED(IDirectSoundBuffer_Restore(wmcda->dsBuf)) ||
140 FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING))) {
141 LeaveCriticalSection(&wmcda->cs);
142 break;
143 }
144 hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
145 }
146
147 if (SUCCEEDED(hr)) {
148 if (rdInfo.SectorCount > 0) {
149 if (!device_io(wmcda->handle, IOCTL_CDROM_RAW_READ, &rdInfo, sizeof(rdInfo), cdData, lockLen, &br, NULL))
150 WARN("CD read failed at sector %d: 0x%x\n", wmcda->start, GetLastError());
151 }
152 if (rdInfo.SectorCount*RAW_SECTOR_SIZE < lockLen) {
153 if(endPos == ~0u) endPos = lastPos;
154 memset((BYTE*)cdData + rdInfo.SectorCount*RAW_SECTOR_SIZE, 0,
155 lockLen - rdInfo.SectorCount*RAW_SECTOR_SIZE);
156 }
157 hr = IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);
158 }
159
160 lastPos += fragLen;
161 lastPos %= caps.dwBufferBytes;
162 wmcda->start += rdInfo.SectorCount;
163
164 LeaveCriticalSection(&wmcda->cs);
165 }
166 IDirectSoundBuffer_Stop(wmcda->dsBuf);
167 SetEvent(wmcda->stopEvent);
168
169 /* A design bug in native: the independent CD player called by the
170 * MCI has no means to signal end of playing, therefore the MCI
171 * notification is left hanging. MCI_NOTIFY_SUPERSEDED will be
172 * signaled by the next command that has MCI_NOTIFY set (or
173 * MCI_NOTIFY_ABORTED for MCI_PLAY). */
174
175 return 0;
176 }
177
178
179
180 /**************************************************************************
181 * MCICDA_drvOpen [internal]
182 */
183 static DWORD MCICDA_drvOpen(LPCWSTR str, LPMCI_OPEN_DRIVER_PARMSW modp)
184 {
185 static HMODULE dsHandle;
186 WINE_MCICDAUDIO* wmcda;
187
188 if (!modp) return 0xFFFFFFFF;
189 /* FIXME: MCIERR_CANNOT_LOAD_DRIVER if there's no drive of type CD-ROM */
190
191 wmcda = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_MCICDAUDIO));
192
193 if (!wmcda)
194 return 0;
195
196 if (!dsHandle) {
197 dsHandle = LoadLibraryA("dsound.dll");
198 if(dsHandle)
199 pDirectSoundCreate = (LPDIRECTSOUNDCREATE)GetProcAddress(dsHandle, "DirectSoundCreate");
200 }
201
202 wmcda->wDevID = modp->wDeviceID;
203 mciSetDriverData(wmcda->wDevID, (DWORD_PTR)wmcda);
204 modp->wCustomCommandTable = MCI_NO_COMMAND_TABLE;
205 modp->wType = MCI_DEVTYPE_CD_AUDIO;
206 InitializeCriticalSection(&wmcda->cs);
207 wmcda->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": WINE_MCICDAUDIO.cs");
208 return modp->wDeviceID;
209 }
210
211 /**************************************************************************
212 * MCICDA_drvClose [internal]
213 */
214 static DWORD MCICDA_drvClose(DWORD dwDevID)
215 {
216 WINE_MCICDAUDIO* wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(dwDevID);
217
218 if (wmcda) {
219 wmcda->cs.DebugInfo->Spare[0] = 0;
220 DeleteCriticalSection(&wmcda->cs);
221 HeapFree(GetProcessHeap(), 0, wmcda);
222 mciSetDriverData(dwDevID, 0);
223 }
224 return (dwDevID == 0xFFFFFFFF) ? 1 : 0;
225 }
226
227 /**************************************************************************
228 * MCICDA_GetOpenDrv [internal]
229 */
230 static WINE_MCICDAUDIO* MCICDA_GetOpenDrv(UINT wDevID)
231 {
232 WINE_MCICDAUDIO* wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
233
234 if (wmcda == NULL || wmcda->nUseCount == 0) {
235 WARN("Invalid wDevID=%u\n", wDevID);
236 return 0;
237 }
238 return wmcda;
239 }
240
241 /**************************************************************************
242 * MCICDA_mciNotify [internal]
243 *
244 * Notifications in MCI work like a 1-element queue.
245 * Each new notification request supersedes the previous one.
246 */
247 static void MCICDA_Notify(DWORD_PTR hWndCallBack, WINE_MCICDAUDIO* wmcda, UINT wStatus)
248 {
249 MCIDEVICEID wDevID = wmcda->wNotifyDeviceID;
250 HANDLE old = InterlockedExchangePointer(&wmcda->hCallback, NULL);
251 if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_SUPERSEDED);
252 mciDriverNotify(HWND_32(LOWORD(hWndCallBack)), wDevID, wStatus);
253 }
254
255 /**************************************************************************
256 * MCICDA_ReadTOC [internal]
257 */
258 static BOOL MCICDA_ReadTOC(WINE_MCICDAUDIO* wmcda, CDROM_TOC *toc, DWORD *br)
259 {
260 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0, toc, sizeof(*toc), br, NULL)) {
261 WARN("error reading TOC !\n");
262 return FALSE;
263 }
264 return TRUE;
265 }
266
267 /**************************************************************************
268 * MCICDA_GetStatus [internal]
269 */
270 static DWORD MCICDA_GetStatus(WINE_MCICDAUDIO* wmcda)
271 {
272 CDROM_SUB_Q_DATA_FORMAT fmt;
273 SUB_Q_CHANNEL_DATA data;
274 DWORD br;
275 DWORD mode = MCI_MODE_NOT_READY;
276
277 fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
278 if(wmcda->hThread != 0) {
279 DWORD status;
280 HRESULT hr;
281
282 hr = IDirectSoundBuffer_GetStatus(wmcda->dsBuf, &status);
283 if(SUCCEEDED(hr)) {
284 if(!(status&DSBSTATUS_PLAYING)) {
285 if(WaitForSingleObject(wmcda->stopEvent, 0) == WAIT_OBJECT_0)
286 mode = MCI_MODE_STOP;
287 else
288 mode = MCI_MODE_PAUSE;
289 }
290 else
291 mode = MCI_MODE_PLAY;
292 }
293 }
294 else if (!device_io(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
295 &data, sizeof(data), &br, NULL)) {
296 if (GetLastError() == ERROR_NOT_READY) mode = MCI_MODE_OPEN;
297 } else {
298 switch (data.CurrentPosition.Header.AudioStatus)
299 {
300 case AUDIO_STATUS_IN_PROGRESS: mode = MCI_MODE_PLAY; break;
301 case AUDIO_STATUS_PAUSED: mode = MCI_MODE_PAUSE; break;
302 case AUDIO_STATUS_NO_STATUS:
303 case AUDIO_STATUS_PLAY_COMPLETE: mode = MCI_MODE_STOP; break;
304 case AUDIO_STATUS_PLAY_ERROR:
305 case AUDIO_STATUS_NOT_SUPPORTED:
306 default:
307 break;
308 }
309 }
310 return mode;
311 }
312
313 /**************************************************************************
314 * MCICDA_GetError [internal]
315 */
316 static int MCICDA_GetError(WINE_MCICDAUDIO* wmcda)
317 {
318 switch (GetLastError())
319 {
320 case ERROR_NOT_READY: return MCIERR_DEVICE_NOT_READY;
321 case ERROR_NOT_SUPPORTED:
322 case ERROR_IO_DEVICE: return MCIERR_HARDWARE;
323 default:
324 FIXME("Unknown mode %u\n", GetLastError());
325 }
326 return MCIERR_DRIVER_INTERNAL;
327 }
328
329 /**************************************************************************
330 * MCICDA_CalcFrame [internal]
331 */
332 static DWORD MCICDA_CalcFrame(WINE_MCICDAUDIO* wmcda, DWORD dwTime)
333 {
334 DWORD dwFrame = 0;
335 UINT wTrack;
336 CDROM_TOC toc;
337 DWORD br;
338 BYTE* addr;
339
340 TRACE("(%p, %08X, %u);\n", wmcda, wmcda->dwTimeFormat, dwTime);
341
342 switch (wmcda->dwTimeFormat) {
343 case MCI_FORMAT_MILLISECONDS:
344 dwFrame = ((dwTime - 1) * CDFRAMES_PERSEC + 500) / 1000;
345 TRACE("MILLISECONDS %u\n", dwFrame);
346 break;
347 case MCI_FORMAT_MSF:
348 TRACE("MSF %02u:%02u:%02u\n",
349 MCI_MSF_MINUTE(dwTime), MCI_MSF_SECOND(dwTime), MCI_MSF_FRAME(dwTime));
350 dwFrame += CDFRAMES_PERMIN * MCI_MSF_MINUTE(dwTime);
351 dwFrame += CDFRAMES_PERSEC * MCI_MSF_SECOND(dwTime);
352 dwFrame += MCI_MSF_FRAME(dwTime);
353 break;
354 case MCI_FORMAT_TMSF:
355 default: /* unknown format ! force TMSF ! ... */
356 wTrack = MCI_TMSF_TRACK(dwTime);
357 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
358 &toc, sizeof(toc), &br, NULL))
359 return 0;
360 if (wTrack < toc.FirstTrack || wTrack > toc.LastTrack)
361 return 0;
362 TRACE("MSF %02u-%02u:%02u:%02u\n",
363 MCI_TMSF_TRACK(dwTime), MCI_TMSF_MINUTE(dwTime),
364 MCI_TMSF_SECOND(dwTime), MCI_TMSF_FRAME(dwTime));
365 addr = toc.TrackData[wTrack - toc.FirstTrack].Address;
366 TRACE("TMSF trackpos[%u]=%d:%d:%d\n",
367 wTrack, addr[1], addr[2], addr[3]);
368 dwFrame = CDFRAMES_PERMIN * (addr[1] + MCI_TMSF_MINUTE(dwTime)) +
369 CDFRAMES_PERSEC * (addr[2] + MCI_TMSF_SECOND(dwTime)) +
370 addr[3] + MCI_TMSF_FRAME(dwTime);
371 break;
372 }
373 return dwFrame;
374 }
375
376 /**************************************************************************
377 * MCICDA_CalcTime [internal]
378 */
379 static DWORD MCICDA_CalcTime(WINE_MCICDAUDIO* wmcda, DWORD tf, DWORD dwFrame, LPDWORD lpRet)
380 {
381 DWORD dwTime = 0;
382 UINT wTrack;
383 UINT wMinutes;
384 UINT wSeconds;
385 UINT wFrames;
386 CDROM_TOC toc;
387 DWORD br;
388
389 TRACE("(%p, %08X, %u);\n", wmcda, tf, dwFrame);
390
391 switch (tf) {
392 case MCI_FORMAT_MILLISECONDS:
393 dwTime = (dwFrame * 1000) / CDFRAMES_PERSEC + 1;
394 TRACE("MILLISECONDS %u\n", dwTime);
395 *lpRet = 0;
396 break;
397 case MCI_FORMAT_MSF:
398 wMinutes = dwFrame / CDFRAMES_PERMIN;
399 wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
400 wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
401 dwTime = MCI_MAKE_MSF(wMinutes, wSeconds, wFrames);
402 TRACE("MSF %02u:%02u:%02u -> dwTime=%u\n",
403 wMinutes, wSeconds, wFrames, dwTime);
404 *lpRet = MCI_COLONIZED3_RETURN;
405 break;
406 case MCI_FORMAT_TMSF:
407 default: /* unknown format ! force TMSF ! ... */
408 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
409 &toc, sizeof(toc), &br, NULL))
410 return 0;
411 if (dwFrame < FRAME_OF_TOC(toc, toc.FirstTrack) ||
412 dwFrame > FRAME_OF_TOC(toc, toc.LastTrack + 1)) {
413 ERR("Out of range value %u [%u,%u]\n",
414 dwFrame, FRAME_OF_TOC(toc, toc.FirstTrack),
415 FRAME_OF_TOC(toc, toc.LastTrack + 1));
416 *lpRet = 0;
417 return 0;
418 }
419 for (wTrack = toc.FirstTrack; wTrack <= toc.LastTrack; wTrack++) {
420 if (FRAME_OF_TOC(toc, wTrack) > dwFrame)
421 break;
422 }
423 wTrack--;
424 dwFrame -= FRAME_OF_TOC(toc, wTrack);
425 wMinutes = dwFrame / CDFRAMES_PERMIN;
426 wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
427 wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
428 dwTime = MCI_MAKE_TMSF(wTrack, wMinutes, wSeconds, wFrames);
429 TRACE("%02u-%02u:%02u:%02u\n", wTrack, wMinutes, wSeconds, wFrames);
430 *lpRet = MCI_COLONIZED4_RETURN;
431 break;
432 }
433 return dwTime;
434 }
435
436 static DWORD MCICDA_Stop(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms);
437
438 /**************************************************************************
439 * MCICDA_Open [internal]
440 */
441 static DWORD MCICDA_Open(UINT wDevID, DWORD dwFlags, LPMCI_OPEN_PARMSW lpOpenParms)
442 {
443 MCIDEVICEID dwDeviceID;
444 DWORD ret;
445 WINE_MCICDAUDIO* wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
446 WCHAR root[7], drive = 0;
447
448 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpOpenParms);
449
450 if (lpOpenParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
451 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
452
453 dwDeviceID = lpOpenParms->wDeviceID;
454
455 if (wmcda->nUseCount > 0) {
456 /* The driver is already open on this channel */
457 /* If the driver was opened shareable before and this open specifies */
458 /* shareable then increment the use count */
459 if (wmcda->fShareable && (dwFlags & MCI_OPEN_SHAREABLE))
460 ++wmcda->nUseCount;
461 else
462 return MCIERR_MUST_USE_SHAREABLE;
463 } else {
464 wmcda->nUseCount = 1;
465 wmcda->fShareable = dwFlags & MCI_OPEN_SHAREABLE;
466 }
467 if (dwFlags & MCI_OPEN_ELEMENT) {
468 if (dwFlags & MCI_OPEN_ELEMENT_ID) {
469 WARN("MCI_OPEN_ELEMENT_ID %p! Abort\n", lpOpenParms->lpstrElementName);
470 ret = MCIERR_FLAGS_NOT_COMPATIBLE;
471 goto the_error;
472 }
473 TRACE("MCI_OPEN_ELEMENT element name: %s\n", debugstr_w(lpOpenParms->lpstrElementName));
474 /* Only the first letter counts since w2k
475 * Win9x-NT accept only d: and w98SE accepts d:\foobar as well.
476 * Play d:\Track03.cda plays from the first track, not #3. */
477 if (!isalpha(lpOpenParms->lpstrElementName[0]))
478 {
479 ret = MCIERR_INVALID_FILE;
480 goto the_error;
481 }
482 drive = toupper(lpOpenParms->lpstrElementName[0]);
483 root[0] = drive; root[1] = ':'; root[2] = '\\'; root[3] = '\0';
484 if (GetDriveTypeW(root) != DRIVE_CDROM)
485 {
486 ret = MCIERR_INVALID_FILE;
487 goto the_error;
488 }
489 }
490 else
491 {
492 root[0] = 'A'; root[1] = ':'; root[2] = '\\'; root[3] = '\0';
493 for ( ; root[0] <= 'Z'; root[0]++)
494 {
495 if (GetDriveTypeW(root) == DRIVE_CDROM)
496 {
497 drive = root[0];
498 break;
499 }
500 }
501 if (!drive)
502 {
503 ret = MCIERR_CANNOT_LOAD_DRIVER; /* drvOpen should return this */
504 goto the_error;
505 }
506 }
507
508 wmcda->wNotifyDeviceID = dwDeviceID;
509 wmcda->dwTimeFormat = MCI_FORMAT_MSF;
510
511 /* now, open the handle */
512 root[0] = root[1] = '\\'; root[2] = '.'; root[3] = '\\'; root[4] = drive; root[5] = ':'; root[6] = '\0';
513 wmcda->handle = CreateFileW(root, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
514 if (wmcda->handle == INVALID_HANDLE_VALUE)
515 {
516 ret = MCIERR_MUST_USE_SHAREABLE;
517 goto the_error;
518 }
519
520 if (dwFlags & MCI_NOTIFY) {
521 mciDriverNotify(HWND_32(LOWORD(lpOpenParms->dwCallback)),
522 dwDeviceID, MCI_NOTIFY_SUCCESSFUL);
523 }
524 return 0;
525
526 the_error:
527 --wmcda->nUseCount;
528 return ret;
529 }
530
531 /**************************************************************************
532 * MCICDA_Close [internal]
533 */
534 static DWORD MCICDA_Close(UINT wDevID, DWORD dwParam, LPMCI_GENERIC_PARMS lpParms)
535 {
536 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
537
538 TRACE("(%04X, %08X, %p);\n", wDevID, dwParam, lpParms);
539
540 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
541
542 MCICDA_Stop(wDevID, MCI_WAIT, NULL);
543
544 if (--wmcda->nUseCount == 0) {
545 CloseHandle(wmcda->handle);
546 }
547 if ((dwParam & MCI_NOTIFY) && lpParms)
548 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
549 return 0;
550 }
551
552 /**************************************************************************
553 * MCICDA_GetDevCaps [internal]
554 */
555 static DWORD MCICDA_GetDevCaps(UINT wDevID, DWORD dwFlags,
556 LPMCI_GETDEVCAPS_PARMS lpParms)
557 {
558 WINE_MCICDAUDIO* wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
559 DWORD ret = 0;
560
561 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
562
563 if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
564 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
565
566 if (dwFlags & MCI_GETDEVCAPS_ITEM) {
567 TRACE("MCI_GETDEVCAPS_ITEM dwItem=%08X;\n", lpParms->dwItem);
568
569 switch (lpParms->dwItem) {
570 case MCI_GETDEVCAPS_CAN_RECORD:
571 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
572 ret = MCI_RESOURCE_RETURNED;
573 break;
574 case MCI_GETDEVCAPS_HAS_AUDIO:
575 lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
576 ret = MCI_RESOURCE_RETURNED;
577 break;
578 case MCI_GETDEVCAPS_HAS_VIDEO:
579 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
580 ret = MCI_RESOURCE_RETURNED;
581 break;
582 case MCI_GETDEVCAPS_DEVICE_TYPE:
583 lpParms->dwReturn = MAKEMCIRESOURCE(MCI_DEVTYPE_CD_AUDIO, MCI_DEVTYPE_CD_AUDIO);
584 ret = MCI_RESOURCE_RETURNED;
585 break;
586 case MCI_GETDEVCAPS_USES_FILES:
587 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
588 ret = MCI_RESOURCE_RETURNED;
589 break;
590 case MCI_GETDEVCAPS_COMPOUND_DEVICE:
591 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
592 ret = MCI_RESOURCE_RETURNED;
593 break;
594 case MCI_GETDEVCAPS_CAN_EJECT:
595 lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
596 ret = MCI_RESOURCE_RETURNED;
597 break;
598 case MCI_GETDEVCAPS_CAN_PLAY:
599 lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
600 ret = MCI_RESOURCE_RETURNED;
601 break;
602 case MCI_GETDEVCAPS_CAN_SAVE:
603 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
604 ret = MCI_RESOURCE_RETURNED;
605 break;
606 default:
607 WARN("Unsupported %x devCaps item\n", lpParms->dwItem);
608 return MCIERR_UNSUPPORTED_FUNCTION;
609 }
610 } else {
611 TRACE("No GetDevCaps-Item !\n");
612 return MCIERR_MISSING_PARAMETER;
613 }
614 TRACE("lpParms->dwReturn=%08X;\n", lpParms->dwReturn);
615 if (dwFlags & MCI_NOTIFY) {
616 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
617 }
618 return ret;
619 }
620
621 static DWORD CDROM_Audio_GetSerial(CDROM_TOC* toc)
622 {
623 DWORD serial = 0;
624 int i;
625 WORD wMagic;
626 DWORD dwStart, dwEnd;
627
628 /*
629 * wMagic collects the wFrames from track 1
630 * dwStart, dwEnd collect the beginning and end of the disc respectively, in
631 * frames.
632 * There it is collected for correcting the serial when there are less than
633 * 3 tracks.
634 */
635 wMagic = toc->TrackData[0].Address[3];
636 dwStart = FRAME_OF_TOC(*toc, toc->FirstTrack);
637
638 for (i = 0; i <= toc->LastTrack - toc->FirstTrack; i++) {
639 serial += (toc->TrackData[i].Address[1] << 16) |
640 (toc->TrackData[i].Address[2] << 8) | toc->TrackData[i].Address[3];
641 }
642 dwEnd = FRAME_OF_TOC(*toc, toc->LastTrack + 1);
643
644 if (toc->LastTrack - toc->FirstTrack + 1 < 3)
645 serial += wMagic + (dwEnd - dwStart);
646
647 return serial;
648 }
649
650
651 /**************************************************************************
652 * MCICDA_Info [internal]
653 */
654 static DWORD MCICDA_Info(UINT wDevID, DWORD dwFlags, LPMCI_INFO_PARMSW lpParms)
655 {
656 LPCWSTR str = NULL;
657 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
658 DWORD ret = 0;
659 WCHAR buffer[16];
660
661 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
662
663 if (lpParms == NULL || lpParms->lpstrReturn == NULL)
664 return MCIERR_NULL_PARAMETER_BLOCK;
665 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
666
667 TRACE("buf=%p, len=%u\n", lpParms->lpstrReturn, lpParms->dwRetSize);
668
669 if (dwFlags & MCI_INFO_PRODUCT) {
670 static const WCHAR wszAudioCd[] = {'W','i','n','e','\'','s',' ','a','u','d','i','o',' ','C','D',0};
671 str = wszAudioCd;
672 } else if (dwFlags & MCI_INFO_MEDIA_UPC) {
673 ret = MCIERR_NO_IDENTITY;
674 } else if (dwFlags & MCI_INFO_MEDIA_IDENTITY) {
675 DWORD res = 0;
676 CDROM_TOC toc;
677 DWORD br;
678 static const WCHAR wszLu[] = {'%','l','u',0};
679
680 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
681 &toc, sizeof(toc), &br, NULL)) {
682 return MCICDA_GetError(wmcda);
683 }
684
685 res = CDROM_Audio_GetSerial(&toc);
686 sprintfW(buffer, wszLu, res);
687 str = buffer;
688 } else {
689 WARN("Don't know this info command (%u)\n", dwFlags);
690 ret = MCIERR_MISSING_PARAMETER;
691 }
692 if (!ret) {
693 TRACE("=> %s\n", debugstr_w(str));
694 if (lpParms->dwRetSize) {
695 /* FIXME? Since NT, mciwave, mciseq and mcicda set dwRetSize
696 * to the number of characters written, excluding \0. */
697 lstrcpynW(lpParms->lpstrReturn, str, lpParms->dwRetSize);
698 } else ret = MCIERR_PARAM_OVERFLOW;
699 }
700 if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
701 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
702 return ret;
703 }
704
705 /**************************************************************************
706 * MCICDA_Status [internal]
707 */
708 static DWORD MCICDA_Status(UINT wDevID, DWORD dwFlags, LPMCI_STATUS_PARMS lpParms)
709 {
710 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
711 DWORD ret = 0;
712 CDROM_SUB_Q_DATA_FORMAT fmt;
713 SUB_Q_CHANNEL_DATA data;
714 CDROM_TOC toc;
715 DWORD br;
716
717 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
718
719 if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
720 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
721
722 if (dwFlags & MCI_STATUS_ITEM) {
723 TRACE("dwItem = %x\n", lpParms->dwItem);
724 switch (lpParms->dwItem) {
725 case MCI_STATUS_CURRENT_TRACK:
726 fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
727 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
728 &data, sizeof(data), &br, NULL))
729 {
730 return MCICDA_GetError(wmcda);
731 /* alt. data.CurrentPosition.TrackNumber = 1; -- what native yields */
732 }
733 lpParms->dwReturn = data.CurrentPosition.TrackNumber;
734 TRACE("CURRENT_TRACK=%lu\n", lpParms->dwReturn);
735 break;
736 case MCI_STATUS_LENGTH:
737 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
738 return MCICDA_GetError(wmcda);
739
740 if (dwFlags & MCI_TRACK) {
741 TRACE("MCI_TRACK #%u LENGTH=??? !\n", lpParms->dwTrack);
742 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
743 return MCIERR_OUTOFRANGE;
744 lpParms->dwReturn = FRAME_OF_TOC(toc, lpParms->dwTrack + 1) -
745 FRAME_OF_TOC(toc, lpParms->dwTrack);
746 /* Windows returns one frame less than the total track length for the
747 last track on the CD. See CDDB HOWTO. Verified on Win95OSR2. */
748 if (lpParms->dwTrack == toc.LastTrack)
749 lpParms->dwReturn--;
750 } else {
751 /* Sum of the lengths of all of the tracks. Inherits the
752 'off by one frame' behavior from the length of the last track.
753 See above comment. */
754 lpParms->dwReturn = FRAME_OF_TOC(toc, toc.LastTrack + 1) -
755 FRAME_OF_TOC(toc, toc.FirstTrack) - 1;
756 }
757 lpParms->dwReturn = MCICDA_CalcTime(wmcda,
758 (wmcda->dwTimeFormat == MCI_FORMAT_TMSF)
759 ? MCI_FORMAT_MSF : wmcda->dwTimeFormat,
760 lpParms->dwReturn,
761 &ret);
762 TRACE("LENGTH=%lu\n", lpParms->dwReturn);
763 break;
764 case MCI_STATUS_MODE:
765 lpParms->dwReturn = MCICDA_GetStatus(wmcda);
766 TRACE("MCI_STATUS_MODE=%08lX\n", lpParms->dwReturn);
767 lpParms->dwReturn = MAKEMCIRESOURCE(lpParms->dwReturn, lpParms->dwReturn);
768 ret = MCI_RESOURCE_RETURNED;
769 break;
770 case MCI_STATUS_MEDIA_PRESENT:
771 lpParms->dwReturn = (MCICDA_GetStatus(wmcda) == MCI_MODE_OPEN) ?
772 MAKEMCIRESOURCE(FALSE, MCI_FALSE) : MAKEMCIRESOURCE(TRUE, MCI_TRUE);
773 TRACE("MCI_STATUS_MEDIA_PRESENT =%c!\n", LOWORD(lpParms->dwReturn) ? 'Y' : 'N');
774 ret = MCI_RESOURCE_RETURNED;
775 break;
776 case MCI_STATUS_NUMBER_OF_TRACKS:
777 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
778 return MCICDA_GetError(wmcda);
779
780 lpParms->dwReturn = toc.LastTrack - toc.FirstTrack + 1;
781 TRACE("MCI_STATUS_NUMBER_OF_TRACKS = %lu\n", lpParms->dwReturn);
782 if (lpParms->dwReturn == (WORD)-1)
783 return MCICDA_GetError(wmcda);
784 break;
785 case MCI_STATUS_POSITION:
786 switch (dwFlags & (MCI_STATUS_START | MCI_TRACK)) {
787 case MCI_STATUS_START:
788 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
789 return MCICDA_GetError(wmcda);
790
791 lpParms->dwReturn = FRAME_OF_TOC(toc, toc.FirstTrack);
792 TRACE("get MCI_STATUS_START !\n");
793 break;
794 case MCI_TRACK:
795 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
796 return MCICDA_GetError(wmcda);
797
798 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
799 return MCIERR_OUTOFRANGE;
800 lpParms->dwReturn = FRAME_OF_TOC(toc, lpParms->dwTrack);
801 TRACE("get MCI_TRACK #%u !\n", lpParms->dwTrack);
802 break;
803 case 0:
804 fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
805 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
806 &data, sizeof(data), &br, NULL)) {
807 return MCICDA_GetError(wmcda);
808 }
809 lpParms->dwReturn = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
810 break;
811 default:
812 return MCIERR_FLAGS_NOT_COMPATIBLE;
813 }
814 lpParms->dwReturn = MCICDA_CalcTime(wmcda, wmcda->dwTimeFormat, lpParms->dwReturn, &ret);
815 TRACE("MCI_STATUS_POSITION=%08lX\n", lpParms->dwReturn);
816 break;
817 case MCI_STATUS_READY:
818 TRACE("MCI_STATUS_READY !\n");
819 switch (MCICDA_GetStatus(wmcda))
820 {
821 case MCI_MODE_NOT_READY:
822 case MCI_MODE_OPEN:
823 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
824 break;
825 default:
826 lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
827 break;
828 }
829 TRACE("MCI_STATUS_READY=%u!\n", LOWORD(lpParms->dwReturn));
830 ret = MCI_RESOURCE_RETURNED;
831 break;
832 case MCI_STATUS_TIME_FORMAT:
833 lpParms->dwReturn = MAKEMCIRESOURCE(wmcda->dwTimeFormat, MCI_FORMAT_RETURN_BASE + wmcda->dwTimeFormat);
834 TRACE("MCI_STATUS_TIME_FORMAT=%08x!\n", LOWORD(lpParms->dwReturn));
835 ret = MCI_RESOURCE_RETURNED;
836 break;
837 case 4001: /* FIXME: for bogus FullCD */
838 case MCI_CDA_STATUS_TYPE_TRACK:
839 if (!(dwFlags & MCI_TRACK))
840 ret = MCIERR_MISSING_PARAMETER;
841 else {
842 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
843 return MCICDA_GetError(wmcda);
844
845 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
846 ret = MCIERR_OUTOFRANGE;
847 else
848 lpParms->dwReturn = (toc.TrackData[lpParms->dwTrack - toc.FirstTrack].Control & 0x04) ?
849 MCI_CDA_TRACK_OTHER : MCI_CDA_TRACK_AUDIO;
850 /* FIXME: MAKEMCIRESOURCE "audio" | "other", localised */
851 }
852 TRACE("MCI_CDA_STATUS_TYPE_TRACK[%d]=%ld\n", lpParms->dwTrack, lpParms->dwReturn);
853 break;
854 default:
855 FIXME("unknown command %08X !\n", lpParms->dwItem);
856 return MCIERR_UNSUPPORTED_FUNCTION;
857 }
858 } else return MCIERR_MISSING_PARAMETER;
859 if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
860 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
861 return ret;
862 }
863
864 /**************************************************************************
865 * MCICDA_SkipDataTracks [internal]
866 */
867 static DWORD MCICDA_SkipDataTracks(WINE_MCICDAUDIO* wmcda,DWORD *frame)
868 {
869 int i;
870 DWORD br;
871 CDROM_TOC toc;
872 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
873 return MCICDA_GetError(wmcda);
874
875 if (*frame < FRAME_OF_TOC(toc,toc.FirstTrack) ||
876 *frame >= FRAME_OF_TOC(toc,toc.LastTrack+1)) /* lead-out */
877 return MCIERR_OUTOFRANGE;
878 for(i=toc.LastTrack+1;i>toc.FirstTrack;i--)
879 if ( FRAME_OF_TOC(toc, i) <= *frame ) break;
880 /* i points to last track whose start address is not greater than frame.
881 * Now skip non-audio tracks */
882 for(;i<=toc.LastTrack;i++)
883 if ( ! (toc.TrackData[i-toc.FirstTrack].Control & 4) )
884 break;
885 /* The frame will be an address in the next audio track or
886 * address of lead-out. */
887 if ( FRAME_OF_TOC(toc, i) > *frame )
888 *frame = FRAME_OF_TOC(toc, i);
889 /* Lead-out is an invalid seek position (on Linux as well). */
890 if (*frame == FRAME_OF_TOC(toc,toc.LastTrack+1))
891 (*frame)--;
892 return 0;
893 }
894
895 /**************************************************************************
896 * MCICDA_Play [internal]
897 */
898 static DWORD MCICDA_Play(UINT wDevID, DWORD dwFlags, LPMCI_PLAY_PARMS lpParms)
899 {
900 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
901 DWORD ret = 0, start, end;
902 HANDLE oldcb;
903 DWORD br;
904 CDROM_PLAY_AUDIO_MSF play;
905 CDROM_SUB_Q_DATA_FORMAT fmt;
906 SUB_Q_CHANNEL_DATA data;
907 CDROM_TOC toc;
908
909 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
910
911 if (lpParms == NULL)
912 return MCIERR_NULL_PARAMETER_BLOCK;
913
914 if (wmcda == NULL)
915 return MCIERR_INVALID_DEVICE_ID;
916
917 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
918 return MCICDA_GetError(wmcda);
919
920 if (dwFlags & MCI_FROM) {
921 start = MCICDA_CalcFrame(wmcda, lpParms->dwFrom);
922 if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
923 return ret;
924 TRACE("MCI_FROM=%08X -> %u\n", lpParms->dwFrom, start);
925 } else {
926 fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
927 if (!device_io(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
928 &data, sizeof(data), &br, NULL)) {
929 return MCICDA_GetError(wmcda);
930 }
931 start = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
932 if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
933 return ret;
934 }
935 if (dwFlags & MCI_TO) {
936 end = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
937 if ( (ret=MCICDA_SkipDataTracks(wmcda, &end)) )
938 return ret;
939 TRACE("MCI_TO=%08X -> %u\n", lpParms->dwTo, end);
940 } else {
941 end = FRAME_OF_TOC(toc, toc.LastTrack + 1) - 1;
942 }
943 if (end < start) return MCIERR_OUTOFRANGE;
944 TRACE("Playing from %u to %u\n", start, end);
945
946 oldcb = InterlockedExchangePointer(&wmcda->hCallback,
947 (dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
948 if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
949
950 if (start == end || start == FRAME_OF_TOC(toc,toc.LastTrack+1)-1) {
951 if (dwFlags & MCI_NOTIFY) {
952 oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
953 if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_SUCCESSFUL);
954 }
955 return MMSYSERR_NOERROR;
956 }
957
958 if (wmcda->hThread != 0) {
959 SetEvent(wmcda->stopEvent);
960 WaitForSingleObject(wmcda->hThread, INFINITE);
961
962 CloseHandle(wmcda->hThread);
963 wmcda->hThread = 0;
964 CloseHandle(wmcda->stopEvent);
965 wmcda->stopEvent = 0;
966
967 IDirectSoundBuffer_Stop(wmcda->dsBuf);
968 IDirectSoundBuffer_Release(wmcda->dsBuf);
969 wmcda->dsBuf = NULL;
970 IDirectSound_Release(wmcda->dsObj);
971 wmcda->dsObj = NULL;
972 }
973
974 if (pDirectSoundCreate) {
975 WAVEFORMATEX format;
976 DSBUFFERDESC desc;
977 DWORD lockLen;
978 void *cdData;
979 HRESULT hr;
980
981 hr = pDirectSoundCreate(NULL, &wmcda->dsObj, NULL);
982 if (SUCCEEDED(hr)) {
983 IDirectSound_SetCooperativeLevel(wmcda->dsObj, GetDesktopWindow(), DSSCL_PRIORITY);
984
985 /* The "raw" frame is relative to the start of the first track */
986 wmcda->start = start - FRAME_OF_TOC(toc, toc.FirstTrack);
987 wmcda->end = end - FRAME_OF_TOC(toc, toc.FirstTrack);
988
989 memset(&format, 0, sizeof(format));
990 format.wFormatTag = WAVE_FORMAT_PCM;
991 format.nChannels = 2;
992 format.nSamplesPerSec = 44100;
993 format.wBitsPerSample = 16;
994 format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
995 format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
996 format.cbSize = 0;
997
998 memset(&desc, 0, sizeof(desc));
999 desc.dwSize = sizeof(desc);
1000 desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
1001 desc.dwBufferBytes = (CDDA_FRAG_SIZE - (CDDA_FRAG_SIZE%RAW_SECTOR_SIZE)) * CDDA_FRAG_COUNT;
1002 desc.lpwfxFormat = &format;
1003
1004 hr = IDirectSound_CreateSoundBuffer(wmcda->dsObj, &desc, &wmcda->dsBuf, NULL);
1005 }
1006 if (SUCCEEDED(hr)) {
1007 hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, 0, 0, &cdData, &lockLen,
1008 NULL, NULL, DSBLOCK_ENTIREBUFFER);
1009 }
1010 if (SUCCEEDED(hr)) {
1011 RAW_READ_INFO rdInfo;
1012 int readok;
1013
1014 rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
1015 rdInfo.SectorCount = min(desc.dwBufferBytes/RAW_SECTOR_SIZE,
1016 wmcda->end-wmcda->start);
1017 rdInfo.TrackMode = CDDA;
1018
1019 readok = device_io(wmcda->handle, IOCTL_CDROM_RAW_READ,
1020 &rdInfo, sizeof(rdInfo), cdData, lockLen,
1021 &br, NULL);
1022 IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);
1023
1024 if (readok) {
1025 wmcda->start += rdInfo.SectorCount;
1026 wmcda->stopEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
1027 }
1028 if (wmcda->stopEvent != 0)
1029 wmcda->hThread = CreateThread(NULL, 0, MCICDA_playLoop, wmcda, 0, &br);
1030 if (wmcda->hThread != 0) {
1031 hr = IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING);
1032 if (SUCCEEDED(hr)) {
1033 /* FIXME: implement MCI_WAIT and send notification only in that case */
1034 if (0) {
1035 oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1036 if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID,
1037 FAILED(hr) ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
1038 }
1039 return ret;
1040 }
1041
1042 SetEvent(wmcda->stopEvent);
1043 WaitForSingleObject(wmcda->hThread, INFINITE);
1044 CloseHandle(wmcda->hThread);
1045 wmcda->hThread = 0;
1046 }
1047 }
1048
1049 if (wmcda->stopEvent != 0) {
1050 CloseHandle(wmcda->stopEvent);
1051 wmcda->stopEvent = 0;
1052 }
1053 if (wmcda->dsBuf) {
1054 IDirectSoundBuffer_Release(wmcda->dsBuf);
1055 wmcda->dsBuf = NULL;
1056 }
1057 if (wmcda->dsObj) {
1058 IDirectSound_Release(wmcda->dsObj);
1059 wmcda->dsObj = NULL;
1060 }
1061 }
1062
1063 play.StartingM = start / CDFRAMES_PERMIN;
1064 play.StartingS = (start / CDFRAMES_PERSEC) % 60;
1065 play.StartingF = start % CDFRAMES_PERSEC;
1066 play.EndingM = end / CDFRAMES_PERMIN;
1067 play.EndingS = (end / CDFRAMES_PERSEC) % 60;
1068 play.EndingF = end % CDFRAMES_PERSEC;
1069 if (!device_io(wmcda->handle, IOCTL_CDROM_PLAY_AUDIO_MSF, &play, sizeof(play),
1070 NULL, 0, &br, NULL)) {
1071 wmcda->hCallback = NULL;
1072 ret = MCIERR_HARDWARE;
1073 }
1074 /* The independent CD player has no means to signal MCI_NOTIFY when it's done.
1075 * Native sends a notification with MCI_WAIT only. */
1076 return ret;
1077 }
1078
1079 /**************************************************************************
1080 * MCICDA_Stop [internal]
1081 */
1082 static DWORD MCICDA_Stop(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1083 {
1084 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1085 HANDLE oldcb;
1086 DWORD br;
1087
1088 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1089
1090 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1091
1092 oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1093 if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
1094
1095 if (wmcda->hThread != 0) {
1096 SetEvent(wmcda->stopEvent);
1097 WaitForSingleObject(wmcda->hThread, INFINITE);
1098
1099 CloseHandle(wmcda->hThread);
1100 wmcda->hThread = 0;
1101 CloseHandle(wmcda->stopEvent);
1102 wmcda->stopEvent = 0;
1103
1104 IDirectSoundBuffer_Release(wmcda->dsBuf);
1105 wmcda->dsBuf = NULL;
1106 IDirectSound_Release(wmcda->dsObj);
1107 wmcda->dsObj = NULL;
1108 }
1109 else if (!device_io(wmcda->handle, IOCTL_CDROM_STOP_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1110 return MCIERR_HARDWARE;
1111
1112 if ((dwFlags & MCI_NOTIFY) && lpParms)
1113 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1114 return 0;
1115 }
1116
1117 /**************************************************************************
1118 * MCICDA_Pause [internal]
1119 */
1120 static DWORD MCICDA_Pause(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1121 {
1122 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1123 HANDLE oldcb;
1124 DWORD br;
1125
1126 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1127
1128 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1129
1130 oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1131 if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
1132
1133 if (wmcda->hThread != 0) {
1134 /* Don't bother calling stop if the playLoop thread has already stopped */
1135 if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
1136 FAILED(IDirectSoundBuffer_Stop(wmcda->dsBuf)))
1137 return MCIERR_HARDWARE;
1138 }
1139 else if (!device_io(wmcda->handle, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1140 return MCIERR_HARDWARE;
1141
1142 if ((dwFlags & MCI_NOTIFY) && lpParms)
1143 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1144 return 0;
1145 }
1146
1147 /**************************************************************************
1148 * MCICDA_Resume [internal]
1149 */
1150 static DWORD MCICDA_Resume(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1151 {
1152 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1153 DWORD br;
1154
1155 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1156
1157 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1158
1159 if (wmcda->hThread != 0) {
1160 /* Don't restart if the playLoop thread has already stopped */
1161 if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
1162 FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING)))
1163 return MCIERR_HARDWARE;
1164 }
1165 else if (!device_io(wmcda->handle, IOCTL_CDROM_RESUME_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1166 return MCIERR_HARDWARE;
1167
1168 if ((dwFlags & MCI_NOTIFY) && lpParms)
1169 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1170 return 0;
1171 }
1172
1173 /**************************************************************************
1174 * MCICDA_Seek [internal]
1175 */
1176 static DWORD MCICDA_Seek(UINT wDevID, DWORD dwFlags, LPMCI_SEEK_PARMS lpParms)
1177 {
1178 DWORD at;
1179 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1180 CDROM_SEEK_AUDIO_MSF seek;
1181 DWORD br, position, ret;
1182 CDROM_TOC toc;
1183
1184 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1185
1186 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1187 if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
1188
1189 position = dwFlags & (MCI_SEEK_TO_START|MCI_SEEK_TO_END|MCI_TO);
1190 if (!position) return MCIERR_MISSING_PARAMETER;
1191 if (position&(position-1)) return MCIERR_FLAGS_NOT_COMPATIBLE;
1192
1193 /* Stop sends MCI_NOTIFY_ABORTED when needed.
1194 * Tests show that native first sends ABORTED and reads the TOC,
1195 * then only checks the position flags, then stops and seeks. */
1196 MCICDA_Stop(wDevID, MCI_WAIT, 0);
1197
1198 if (!MCICDA_ReadTOC(wmcda, &toc, &br))
1199 return MCICDA_GetError(wmcda);
1200
1201 switch (position) {
1202 case MCI_SEEK_TO_START:
1203 TRACE("Seeking to start\n");
1204 at = FRAME_OF_TOC(toc,toc.FirstTrack);
1205 if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1206 return ret;
1207 break;
1208 case MCI_SEEK_TO_END:
1209 TRACE("Seeking to end\n");
1210 /* End is prior to lead-out
1211 * yet Win9X seeks to even one frame less than that. */
1212 at = FRAME_OF_TOC(toc, toc.LastTrack + 1) - 1;
1213 if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1214 return ret;
1215 break;
1216 case MCI_TO:
1217 TRACE("Seeking to %u\n", lpParms->dwTo);
1218 at = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
1219 if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1220 return ret;
1221 break;
1222 default:
1223 return MCIERR_FLAGS_NOT_COMPATIBLE;
1224 }
1225
1226 {
1227 seek.M = at / CDFRAMES_PERMIN;
1228 seek.S = (at / CDFRAMES_PERSEC) % 60;
1229 seek.F = at % CDFRAMES_PERSEC;
1230 if (!device_io(wmcda->handle, IOCTL_CDROM_SEEK_AUDIO_MSF, &seek, sizeof(seek),
1231 NULL, 0, &br, NULL))
1232 return MCIERR_HARDWARE;
1233 }
1234
1235 if (dwFlags & MCI_NOTIFY)
1236 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1237 return 0;
1238 }
1239
1240 /**************************************************************************
1241 * MCICDA_SetDoor [internal]
1242 */
1243 static DWORD MCICDA_SetDoor(UINT wDevID, BOOL open)
1244 {
1245 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1246 DWORD br;
1247
1248 TRACE("(%04x, %s) !\n", wDevID, (open) ? "OPEN" : "CLOSE");
1249
1250 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1251
1252 if (!device_io(wmcda->handle,
1253 (open) ? IOCTL_STORAGE_EJECT_MEDIA : IOCTL_STORAGE_LOAD_MEDIA,
1254 NULL, 0, NULL, 0, &br, NULL))
1255 return MCIERR_HARDWARE;
1256
1257 return 0;
1258 }
1259
1260 /**************************************************************************
1261 * MCICDA_Set [internal]
1262 */
1263 static DWORD MCICDA_Set(UINT wDevID, DWORD dwFlags, LPMCI_SET_PARMS lpParms)
1264 {
1265 WINE_MCICDAUDIO* wmcda = MCICDA_GetOpenDrv(wDevID);
1266
1267 TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1268
1269 if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1270
1271 if (dwFlags & MCI_SET_DOOR_OPEN) {
1272 MCICDA_SetDoor(wDevID, TRUE);
1273 }
1274 if (dwFlags & MCI_SET_DOOR_CLOSED) {
1275 MCICDA_SetDoor(wDevID, FALSE);
1276 }
1277
1278 /* only functions which require valid lpParms below this line ! */
1279 if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
1280 /*
1281 TRACE("dwTimeFormat=%08lX\n", lpParms->dwTimeFormat);
1282 */
1283 if (dwFlags & MCI_SET_TIME_FORMAT) {
1284 switch (lpParms->dwTimeFormat) {
1285 case MCI_FORMAT_MILLISECONDS:
1286 TRACE("MCI_FORMAT_MILLISECONDS !\n");
1287 break;
1288 case MCI_FORMAT_MSF:
1289 TRACE("MCI_FORMAT_MSF !\n");
1290 break;
1291 case MCI_FORMAT_TMSF:
1292 TRACE("MCI_FORMAT_TMSF !\n");
1293 break;
1294 default:
1295 return MCIERR_BAD_TIME_FORMAT;
1296 }
1297 wmcda->dwTimeFormat = lpParms->dwTimeFormat;
1298 }
1299 if (dwFlags & MCI_SET_AUDIO) /* one xp machine ignored it */
1300 TRACE("SET_AUDIO %X %x\n", dwFlags, lpParms->dwAudio);
1301
1302 if (dwFlags & MCI_NOTIFY)
1303 MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1304 return 0;
1305 }
1306
1307 /**************************************************************************
1308 * DriverProc (MCICDA.@)
1309 */
1310 LRESULT CALLBACK MCICDA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
1311 LPARAM dwParam1, LPARAM dwParam2)
1312 {
1313 switch(wMsg) {
1314 case DRV_LOAD: return 1;
1315 case DRV_FREE: return 1;
1316 case DRV_OPEN: return MCICDA_drvOpen((LPCWSTR)dwParam1, (LPMCI_OPEN_DRIVER_PARMSW)dwParam2);
1317 case DRV_CLOSE: return MCICDA_drvClose(dwDevID);
1318 case DRV_ENABLE: return 1;
1319 case DRV_DISABLE: return 1;
1320 case DRV_QUERYCONFIGURE: return 1;
1321 case DRV_CONFIGURE: MessageBoxA(0, "MCI audio CD driver !", "Wine Driver", MB_OK); return 1;
1322 case DRV_INSTALL: return DRVCNF_RESTART;
1323 case DRV_REMOVE: return DRVCNF_RESTART;
1324 }
1325
1326 if (dwDevID == 0xFFFFFFFF) return MCIERR_UNSUPPORTED_FUNCTION;
1327
1328 switch (wMsg) {
1329 case MCI_OPEN_DRIVER: return MCICDA_Open(dwDevID, dwParam1, (LPMCI_OPEN_PARMSW)dwParam2);
1330 case MCI_CLOSE_DRIVER: return MCICDA_Close(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1331 case MCI_GETDEVCAPS: return MCICDA_GetDevCaps(dwDevID, dwParam1, (LPMCI_GETDEVCAPS_PARMS)dwParam2);
1332 case MCI_INFO: return MCICDA_Info(dwDevID, dwParam1, (LPMCI_INFO_PARMSW)dwParam2);
1333 case MCI_STATUS: return MCICDA_Status(dwDevID, dwParam1, (LPMCI_STATUS_PARMS)dwParam2);
1334 case MCI_SET: return MCICDA_Set(dwDevID, dwParam1, (LPMCI_SET_PARMS)dwParam2);
1335 case MCI_PLAY: return MCICDA_Play(dwDevID, dwParam1, (LPMCI_PLAY_PARMS)dwParam2);
1336 case MCI_STOP: return MCICDA_Stop(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1337 case MCI_PAUSE: return MCICDA_Pause(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1338 case MCI_RESUME: return MCICDA_Resume(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1339 case MCI_SEEK: return MCICDA_Seek(dwDevID, dwParam1, (LPMCI_SEEK_PARMS)dwParam2);
1340 /* commands that should report an error as they are not supported in
1341 * the native version */
1342 case MCI_RECORD:
1343 case MCI_LOAD:
1344 case MCI_SAVE:
1345 return MCIERR_UNSUPPORTED_FUNCTION;
1346 case MCI_BREAK:
1347 case MCI_FREEZE:
1348 case MCI_PUT:
1349 case MCI_REALIZE:
1350 case MCI_UNFREEZE:
1351 case MCI_UPDATE:
1352 case MCI_WHERE:
1353 case MCI_STEP:
1354 case MCI_SPIN:
1355 case MCI_ESCAPE:
1356 case MCI_COPY:
1357 case MCI_CUT:
1358 case MCI_DELETE:
1359 case MCI_PASTE:
1360 case MCI_WINDOW:
1361 TRACE("Unsupported command [0x%x]\n", wMsg);
1362 break;
1363 case MCI_OPEN:
1364 case MCI_CLOSE:
1365 ERR("Shouldn't receive a MCI_OPEN or CLOSE message\n");
1366 break;
1367 default:
1368 TRACE("Sending msg [0x%x] to default driver proc\n", wMsg);
1369 return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
1370 }
1371 return MCIERR_UNRECOGNIZED_COMMAND;
1372 }
1373
1374 /*-----------------------------------------------------------------------*/