1 #include <string.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4
5 #include "AL/alure.h"
6
7 #ifdef __linux__
8 /* Linux implementation for reading CD digital audio */
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <errno.h>
14 #include <sys/ioctl.h>
15 #include <linux/cdrom.h>
16
17
18 typedef struct {
19 int fd;
20 int start;
21 int current;
22 int end;
23 } CDDAData;
24
25 static const char *cd_device = "/dev/cdrom1";
26
cdda_open_file(const char * fname)27 static void *cdda_open_file(const char *fname)
28 {
29 struct cdrom_tochdr cdtochdr;
30 struct cdrom_tocentry cdtocentry;
31 int startaddr=-1, endaddr=-1, idx;
32 CDDAData *dat;
33 int fd, ret;
34
35 /* Make sure the filename has the appropriate URI and extract the track
36 * number */
37 if(strncmp(fname, "cdda://", 7) != 0)
38 return NULL;
39 idx = atoi(fname+7);
40
41 /* Open the block device and get the TOC header */
42 fd = open(cd_device, O_RDONLY | O_NONBLOCK);
43 if(fd == -1) return NULL;
44
45 ret = ioctl(fd, CDROMREADTOCHDR, &cdtochdr);
46 if(ret != -1)
47 {
48 if(idx < cdtochdr.cdth_trk0 || idx >= cdtochdr.cdth_trk1)
49 ret = -1;
50 }
51 if(ret != -1)
52 {
53 /* Get the start address for the requested track, and the start address
54 * of the next track as the end point */
55 cdtocentry.cdte_format = CDROM_LBA;
56 cdtocentry.cdte_track = idx;
57 ret = ioctl(fd, CDROMREADTOCENTRY, &cdtocentry);
58 if(ret != -1 && !(cdtocentry.cdte_ctrl&CDROM_DATA_TRACK))
59 startaddr = cdtocentry.cdte_addr.lba;
60 }
61 if(ret != -1)
62 {
63 cdtocentry.cdte_format = CDROM_LBA;
64 cdtocentry.cdte_track = ++idx;
65 ret = ioctl(fd, CDROMREADTOCENTRY, &cdtocentry);
66 if(ret != -1)
67 endaddr = cdtocentry.cdte_addr.lba;
68 }
69
70 if(ret == -1 || startaddr == -1 || endaddr == -1)
71 {
72 close(fd);
73 return NULL;
74 }
75
76 dat = malloc(sizeof(*dat));
77 dat->fd = fd;
78 dat->start = startaddr;
79 dat->current = startaddr;
80 dat->end = endaddr;
81
82 return dat;
83 }
84
cdda_get_format(void * instance,ALenum * format,ALuint * samplerate,ALuint * blocksize)85 static ALboolean cdda_get_format(void *instance, ALenum *format, ALuint *samplerate, ALuint *blocksize)
86 {
87 /* These values are specified by the red-book audio standard and do not
88 * change */
89 *format = AL_FORMAT_STEREO16;
90 *samplerate = 44100;
91 *blocksize = CD_FRAMESIZE_RAW;
92
93 return AL_TRUE;
94 (void)instance;
95 }
96
cdda_decode(void * instance,ALubyte * data,ALuint bytes)97 static ALuint cdda_decode(void *instance, ALubyte *data, ALuint bytes)
98 {
99 CDDAData *self = instance;
100
101 ALuint got = 0;
102 while(bytes-got >= CD_FRAMESIZE_RAW && self->current < self->end)
103 {
104 struct cdrom_read_audio cdra;
105
106 /* Read as many frames that are left that will fit */
107 cdra.addr.lba = self->current;
108 cdra.addr_format = CDROM_LBA;
109 cdra.nframes = (bytes-got) / CD_FRAMESIZE_RAW;
110 cdra.buf = data;
111
112 if(cdra.nframes > self->end-self->current)
113 cdra.nframes = self->end-self->current;
114
115 if(ioctl(self->fd, CDROMREADAUDIO, &cdra) == -1)
116 {
117 cdra.nframes = 1;
118 memset(cdra.buf, 0, CD_FRAMESIZE_RAW);
119 }
120
121 self->current += cdra.nframes;
122 data += CD_FRAMESIZE_RAW*cdra.nframes;
123 got += CD_FRAMESIZE_RAW*cdra.nframes;
124 }
125
126 return got;
127 }
128
cdda_rewind(void * instance)129 static ALboolean cdda_rewind(void *instance)
130 {
131 CDDAData *self = instance;
132
133 self->current = self->start;
134 return AL_TRUE;
135 }
136
cdda_close(void * instance)137 static void cdda_close(void *instance)
138 {
139 CDDAData *self = instance;
140
141 close(self->fd);
142 free(self);
143 }
144
145 #elif defined(HAVE_DDK_NTDDCDRM_H)
146 /* Windows implementation for reading CD digital audio */
147 #include <windows.h>
148 #include <ddk/ntddcdrm.h>
149
150 /* Defined by red-book standard; do not change! */
151 #define CD_FRAMESIZE_RAW (2352)
152
153 #define CDFRAMES_PERSEC (75)
154 #define CDFRAMES_PERMIN (CDFRAMES_PERSEC * 60)
155 #define FRAME_OF_ADDR(a) ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
156 #define FRAME_OF_TOC(toc, idx) FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)
157
158 /* All of this is pretty similar to the Linux version, except using the WinAPI
159 * device functions instead of POSIX */
160 typedef struct {
161 HANDLE fd;
162 DWORD start;
163 DWORD current;
164 DWORD end;
165 } CDDAData;
166
167 static const char *cd_device = "D";
168
cdda_open_file(const char * fname)169 static void *cdda_open_file(const char *fname)
170 {
171 /* Device filename is of the format "\\.\D:", where "D" is the letter of
172 * the CD drive */
173 const char cd_drv[] = { '\\','\\','.','\\',*cd_device,':', 0 };
174 DWORD br, idx;
175 CDROM_TOC toc;
176 CDDAData *dat;
177 HANDLE fd;
178 int ret;
179
180 if(strncmp(fname, "cdda://", 7) != 0)
181 return NULL;
182 idx = atoi(fname+7);
183
184 fd = CreateFileA(cd_drv, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
185 if(fd == (HANDLE)-1) return NULL;
186
187 ret = DeviceIoControl(fd, IOCTL_CDROM_READ_TOC, NULL, 0,
188 &toc, sizeof(toc), &br, NULL);
189 if(ret == 0 || idx < toc.FirstTrack || idx > toc.LastTrack ||
190 (toc.TrackData[idx-toc.FirstTrack].Control&4))
191 {
192 CloseHandle(fd);
193 return NULL;
194 }
195
196 dat = malloc(sizeof(*dat));
197 dat->fd = fd;
198 dat->start = FRAME_OF_TOC(toc, idx) - FRAME_OF_TOC(toc, toc.FirstTrack);
199 dat->current = dat->start;
200 dat->end = FRAME_OF_TOC(toc, idx+1) - FRAME_OF_TOC(toc, toc.FirstTrack);
201
202 return dat;
203 }
204
cdda_get_format(void * instance,ALenum * format,ALuint * samplerate,ALuint * blocksize)205 static ALboolean cdda_get_format(void *instance, ALenum *format, ALuint *samplerate, ALuint *blocksize)
206 {
207 *format = AL_FORMAT_STEREO16;
208 *samplerate = 44100;
209 *blocksize = CD_FRAMESIZE_RAW;
210
211 return AL_TRUE;
212 (void)instance;
213 }
214
cdda_decode(void * instance,ALubyte * data,ALuint bytes)215 static ALuint cdda_decode(void *instance, ALubyte *data, ALuint bytes)
216 {
217 CDDAData *self = instance;
218
219 ALuint got = 0;
220 while(bytes-got >= CD_FRAMESIZE_RAW && self->current < self->end)
221 {
222 RAW_READ_INFO rdInfo;
223 DWORD br;
224
225 rdInfo.DiskOffset.QuadPart = (LONGLONG)self->current<<11;
226 rdInfo.SectorCount = (bytes-got) / CD_FRAMESIZE_RAW;
227 rdInfo.TrackMode = CDDA;
228
229 if(rdInfo.SectorCount > self->end-self->current)
230 rdInfo.SectorCount = self->end-self->current;
231
232 if(!DeviceIoControl(self->fd, IOCTL_CDROM_RAW_READ,
233 &rdInfo, sizeof(rdInfo), data, bytes-got,
234 &br, NULL))
235 {
236 rdInfo.SectorCount = 1;
237 memset(data, 0, CD_FRAMESIZE_RAW);
238 }
239
240 self->current += rdInfo.SectorCount;
241 data += CD_FRAMESIZE_RAW*rdInfo.SectorCount;
242 got += CD_FRAMESIZE_RAW*rdInfo.SectorCount;
243 }
244
245 return got;
246 }
247
cdda_rewind(void * instance)248 static ALboolean cdda_rewind(void *instance)
249 {
250 CDDAData *self = instance;
251
252 self->current = self->start;
253 return AL_TRUE;
254 }
255
cdda_close(void * instance)256 static void cdda_close(void *instance)
257 {
258 CDDAData *self = instance;
259
260 CloseHandle(self->fd);
261 free(self);
262 }
263
264 #else
265
266 static const char *cd_device = "(unknown)";
267
cdda_open_file(const char * fname)268 static void *cdda_open_file(const char *fname)
269 {
270 if(strncmp(fname, "cdda://", 7) == 0)
271 fprintf(stderr, "CD Digital Audio was not compiled for this system\n");
272 return NULL;
273 }
274
cdda_get_format(void * instance,ALenum * format,ALuint * samplerate,ALuint * blocksize)275 static ALboolean cdda_get_format(void *instance, ALenum *format, ALuint *samplerate, ALuint *blocksize)
276 {
277 return AL_FALSE;
278 (void)instance;
279 }
280
cdda_decode(void * instance,ALubyte * data,ALuint bytes)281 static ALuint cdda_decode(void *instance, ALubyte *data, ALuint bytes)
282 {
283 return 0;
284 (void)instance;
285 (void)data;
286 (void)bytes;
287 }
288
cdda_rewind(void * instance)289 static ALboolean cdda_rewind(void *instance)
290 {
291 return AL_FALSE;
292 (void)instance;
293 }
294
cdda_close(void * instance)295 static void cdda_close(void *instance)
296 {
297 (void)instance;
298 }
299
300 #endif
301
302
eos_callback(void * param,ALuint unused)303 static void eos_callback(void *param, ALuint unused)
304 {
305 *(volatile int*)param = 1;
306 (void)unused;
307 }
308
309 #define NUM_BUFS 3
310
311
main(int argc,char ** argv)312 int main(int argc, char **argv)
313 {
314 volatile int isdone;
315 alureStream *stream;
316 const char *fname;
317 ALuint src;
318
319 if(argc < 2 || (strcmp(argv[1], "-cd-device") == 0 && argc < 4))
320 {
321 fprintf(stderr, "Usage %s [-cd-device <device>] cdda://<tracknum>\n", argv[0]);
322 fprintf(stderr, "Default CD device is %s\n", cd_device);
323 return 1;
324 }
325
326 if(strcmp(argv[1], "-cd-device") != 0)
327 fname = argv[1];
328 else
329 {
330 cd_device = argv[2];
331 fname = argv[3];
332 }
333
334 alureInstallDecodeCallbacks(-1, cdda_open_file, NULL, cdda_get_format,
335 cdda_decode, cdda_rewind, cdda_close);
336
337 if(!alureInitDevice(NULL, NULL))
338 {
339 fprintf(stderr, "Failed to open OpenAL device: %s\n", alureGetErrorString());
340 return 1;
341 }
342
343 alGenSources(1, &src);
344 if(alGetError() != AL_NO_ERROR)
345 {
346 fprintf(stderr, "Failed to create OpenAL source!\n");
347 alureShutdownDevice();
348 return 1;
349 }
350
351 alureStreamSizeIsMicroSec(AL_TRUE);
352
353 stream = alureCreateStreamFromFile(fname, 250000, 0, NULL);
354 if(!stream)
355 {
356 fprintf(stderr, "Could not load %s: %s\n", fname, alureGetErrorString());
357 alDeleteSources(1, &src);
358
359 alureShutdownDevice();
360 return 1;
361 }
362
363 isdone = 0;
364 if(!alurePlaySourceStream(src, stream, NUM_BUFS, 0, eos_callback, (void*)&isdone))
365 {
366 fprintf(stderr, "Failed to play stream: %s\n", alureGetErrorString());
367 isdone = 1;
368 }
369
370 while(!isdone)
371 {
372 alureSleep(0.125);
373 alureUpdate();
374 }
375 alureStopSource(src, AL_FALSE);
376
377 alDeleteSources(1, &src);
378 alureDestroyStream(stream, 0, NULL);
379
380 alureShutdownDevice();
381 return 0;
382 }
383