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