1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include <sys/types.h>
30#include <string.h>
31#include <stdlib.h>
32#include <libintl.h>
33#include <signal.h>
34
35#include "bstream.h"
36#include "util.h"
37#include "misc_scsi.h"
38#include "device.h"
39#include "main.h"
40#include "msgs.h"
41
42#define	BLOCK_SIZE		2352
43#define	READ_BURST_SIZE		200
44#define	SMALL_READ_BURST_SIZE	24	/* < 64K in all cases */
45#define	READ_OVERLAP		7
46#define	BLOCKS_COMPARE		3
47
48static int			abort_read;
49
50/*
51 * These are routines for extracting audio from a cd. During
52 * extraction we will also convert the audio type from the
53 * CD to the audio type specified on the command line. This
54 * handles both newer CD drives which support the MMC2 standard
55 * and older Sun Toshiba drives which need jitter correction.
56 */
57
58static bstreamhandle
59open_audio_for_extraction(char *fname)
60{
61	int at;
62	char *ext;
63
64	if (audio_type == AUDIO_TYPE_NONE) {
65		ext = (char *)(strrchr(fname, '.'));
66		if (ext) {
67			ext++;
68		}
69		if ((ext == NULL) || ((at = get_audio_type(ext)) == -1)) {
70			err_msg(gettext(
71			    "Cannot understand file extension for %s\n"),
72			    fname);
73			exit(1);
74		}
75	} else {
76		at = audio_type;
77	}
78	if (at == AUDIO_TYPE_SUN)
79		return (open_au_write_stream(fname));
80	if (at == AUDIO_TYPE_WAV)
81		return (open_wav_write_stream(fname));
82	if (at == AUDIO_TYPE_CDA)
83		return (open_file_write_stream(fname));
84	if (at == AUDIO_TYPE_AUR)
85		return (open_aur_write_stream(fname));
86	return (NULL);
87}
88
89/* ARGSUSED */
90static void
91extract_signal_handler(int sig, siginfo_t *info, void *context)
92{
93	abort_read = 1;
94}
95
96/*
97 * Older drives use different data buffer and m:s:f channels to transmit audio
98 * information. These channels may not be in sync with each other with the
99 * maximum disparity being the size of the data buffer. So handling is needed
100 * to keep these two channels in sync.
101 */
102
103static int
104handle_jitter(uchar_t *buf, uchar_t *last_end)
105{
106	int i;
107	for (i = BLOCK_SIZE*(READ_OVERLAP - BLOCKS_COMPARE); i >= 0; i -= 4) {
108		if (memcmp(last_end - BLOCK_SIZE * BLOCKS_COMPARE, buf + i,
109		    BLOCK_SIZE * BLOCKS_COMPARE) == 0) {
110			return (i + (BLOCK_SIZE * BLOCKS_COMPARE));
111		}
112	}
113	for (i = BLOCK_SIZE*(READ_OVERLAP - BLOCKS_COMPARE);
114		i < 2*READ_OVERLAP*BLOCK_SIZE; i += 4) {
115		if (memcmp(last_end - BLOCK_SIZE * BLOCKS_COMPARE, buf + i,
116		    BLOCK_SIZE * BLOCKS_COMPARE) == 0) {
117			return (i + (BLOCK_SIZE * BLOCKS_COMPARE));
118		}
119	}
120	return (-1);
121}
122
123int
124read_audio_track(cd_device *dev, struct track_info *ti, bstreamhandle h)
125{
126	uint32_t	blocks_to_write, blocks_to_read, blks_to_overlap;
127	uint32_t	start_blk, end_blk, c_blk;
128	uint32_t	read_burst_size;
129	uchar_t		*tmp, *buf, *prev, *previous_end;
130	int		ret, off;
131	struct sigaction	sv;
132	struct sigaction	oldsv;
133
134	ret = 0;
135	abort_read = 0;
136
137	/*
138	 * It is good to do small sized I/Os as we have seen many devices
139	 * choke with large I/Os. But if the device does not support
140	 * reading accurate CDDA then we have to do overlapped I/Os
141	 * and reducing size might affect performance. So use small
142	 * I/O size if device supports accurate CDDA.
143	 */
144	if (dev->d_cap & DEV_CAP_ACCURATE_CDDA) {
145		read_burst_size = SMALL_READ_BURST_SIZE;
146	} else {
147		read_burst_size = READ_BURST_SIZE;
148	}
149	buf = (uchar_t *)my_zalloc(BLOCK_SIZE * read_burst_size);
150	prev = (uchar_t *)my_zalloc(BLOCK_SIZE * read_burst_size);
151	start_blk = ti->ti_start_address;
152	end_blk = ti->ti_start_address + ti->ti_track_size - 1;
153
154	/* Even when we need jitter correction, this will be 0 1st time */
155	blks_to_overlap = 0;
156	off = 0;
157
158	/* set up signal handler to write audio TOC if ^C is pressed */
159	sv.sa_handler = extract_signal_handler;
160	(void) sigemptyset(&sv.sa_mask);
161	sv.sa_flags = 0;
162	(void) sigaction(SIGINT, &sv, &oldsv);
163
164	if ((dev->d_cap & DEV_CAP_EXTRACT_CDDA) == 0) {
165		err_msg(gettext("Audio extraction method unknown for %s\n"),
166		    dev->d_name ? dev->d_name : gettext("CD drive"));
167		exit(1);
168	}
169
170	/* if the speed option given, try to change the speed */
171	if ((requested_speed != 0) && !cflag) {
172		if (verbose)
173			(void) printf(gettext("Trying to set speed to %dX.\n"),
174			    requested_speed);
175		if (dev->d_speed_ctrl(dev, SET_READ_SPEED,
176		    requested_speed) == 0) {
177
178			err_msg(gettext("Unable to set speed.\n"));
179			exit(1);
180		}
181		if (verbose) {
182			int speed;
183			speed = dev->d_speed_ctrl(dev, GET_READ_SPEED, 0);
184			if (speed == requested_speed) {
185				(void) printf(gettext("Speed set to %dX.\n"),
186				    speed);
187			} else if (speed == 0) {
188				(void) printf(gettext("Could not obtain "
189				    "current Read Speed.\n"));
190			} else {
191				(void) printf(gettext("Speed set to "
192				    "closest approximation of %dX allowed "
193				    "by device (%dX).\n"),
194				    requested_speed, speed);
195			}
196		}
197	}
198
199	print_n_flush(
200	    gettext("Extracting audio from track %d..."), ti->ti_track_no);
201	init_progress();
202
203	if (debug)
204		(void) printf("\nStarting: %d Ending: %d\n",
205		    start_blk, end_blk);
206
207	blocks_to_write = 0;
208
209	for (c_blk = start_blk; c_blk < end_blk; c_blk += blocks_to_write) {
210		/* update progress indicator */
211		(void) progress((end_blk - start_blk),
212		    (int64_t)(c_blk - start_blk));
213		blocks_to_read =  end_blk - c_blk + blks_to_overlap;
214
215		/*
216		 * Make sure we don't read more blocks than the maximum
217		 * burst size.
218		 */
219
220		if (blocks_to_read > read_burst_size)
221			blocks_to_read = read_burst_size;
222
223		if (dev->d_read_audio(dev, c_blk - blks_to_overlap,
224		    blocks_to_read, buf) == 0)
225			goto read_audio_track_done;
226
227		/*
228		 * This drive supports accurate audio extraction don't
229		 * do jitter correction.
230		 */
231		if ((c_blk == start_blk) ||
232		    (dev->d_cap & DEV_CAP_ACCURATE_CDDA)) {
233			blocks_to_write = blocks_to_read;
234			previous_end = buf + (blocks_to_write * BLOCK_SIZE);
235			goto skip_jitter_correction;
236		}
237
238		if (c_blk == start_blk)
239			blks_to_overlap = 0;
240		else
241			blks_to_overlap = READ_OVERLAP;
242		off = handle_jitter(buf, previous_end);
243		if (off == -1) {
244			if (debug)
245				(void) printf(
246				    "jitter control failed\n");
247
248			/* recover if jitter correction failed */
249			off = BLOCK_SIZE * BLOCKS_COMPARE;
250		}
251
252		blocks_to_write = blocks_to_read - blks_to_overlap;
253
254		while ((off + (blocks_to_write*BLOCK_SIZE)) >
255		    (blocks_to_read * BLOCK_SIZE)) {
256			blocks_to_write--;
257		}
258
259		if ((blocks_to_write + c_blk) > end_blk) {
260			blocks_to_write = end_blk - c_blk;
261		}
262
263		if (blocks_to_write == 0) {
264			c_blk = end_blk - 1;
265			blocks_to_write = 1;
266			(void) memset(&buf[off], 0, off % BLOCK_SIZE);
267		}
268
269		previous_end = buf + off + blocks_to_write * BLOCK_SIZE;
270skip_jitter_correction:
271		(void) memcpy(prev, buf, read_burst_size * BLOCK_SIZE);
272		if (h->bstr_write(h, &buf[off], blocks_to_write*BLOCK_SIZE)
273		    < 0)
274			goto read_audio_track_done;
275		tmp = buf;
276		buf = prev;
277		prev = tmp;
278
279		if (abort_read == 1)
280			goto read_audio_track_done;
281	}
282
283	ret = 1;
284	(void) str_print(gettext("done.\n"), progress_pos);
285
286read_audio_track_done:
287	(void) sigaction(SIGINT, &oldsv, (struct sigaction *)0);
288
289	free(buf);
290	free(prev);
291	return (ret);
292}
293
294void
295extract_audio(void)
296{
297	bstreamhandle h;
298	struct track_info *ti;
299
300	(void) check_device(target, CHECK_NO_MEDIA | CHECK_DEVICE_NOT_READY |
301	    EXIT_IF_CHECK_FAILED);
302
303	ti = (struct track_info *)my_zalloc(sizeof (*ti));
304	if (!build_track_info(target, extract_track_no, ti)) {
305		err_msg(gettext("Cannot get track information for track %d\n"),
306		    extract_track_no);
307		exit(1);
308	}
309
310	/* Verify track */
311	if ((ti->ti_track_size == 0) || ((ti->ti_flags & TI_NWA_VALID) &&
312	    (ti->ti_start_address == ti->ti_nwa))) {
313		err_msg(gettext("Track %d is empty\n"), extract_track_no);
314		exit(1);
315	}
316	if (ti->ti_track_mode & 4) {
317		err_msg(gettext("Track %d is not an audio track\n"),
318		    extract_track_no);
319		exit(1);
320	}
321	if (ti->ti_data_mode == 2) {
322		err_msg(gettext("Track format is not supported\n"));
323		exit(1);
324	}
325
326	h = open_audio_for_extraction(extract_file);
327	if (h == NULL) {
328		err_msg(gettext("Cannot open %s:%s\n"), extract_file,
329		    get_err_str());
330		exit(1);
331	}
332	if (read_audio_track(target, ti, h) == 0) {
333		err_msg(gettext("Extract audio failed\n"));
334		h->bstr_close(h);
335		exit(1);
336	}
337	if (h->bstr_close(h) != 0) {
338		err_msg(gettext("Error closing audio stream : %s\n"),
339		    get_err_str());
340		exit(1);
341	}
342	exit(0);
343}
344