OpenSolaris_b135/cmd/cdrw/dae.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <libintl.h>
#include <signal.h>

#include "bstream.h"
#include "util.h"
#include "misc_scsi.h"
#include "device.h"
#include "main.h"
#include "msgs.h"

#define	BLOCK_SIZE		2352
#define	READ_BURST_SIZE		200
#define	SMALL_READ_BURST_SIZE	24	/* < 64K in all cases */
#define	READ_OVERLAP		7
#define	BLOCKS_COMPARE		3

static int			abort_read;

/*
 * These are routines for extracting audio from a cd. During
 * extraction we will also convert the audio type from the
 * CD to the audio type specified on the command line. This
 * handles both newer CD drives which support the MMC2 standard
 * and older Sun Toshiba drives which need jitter correction.
 */

static bstreamhandle
open_audio_for_extraction(char *fname)
{
	int at;
	char *ext;

	if (audio_type == AUDIO_TYPE_NONE) {
		ext = (char *)(strrchr(fname, '.'));
		if (ext) {
			ext++;
		}
		if ((ext == NULL) || ((at = get_audio_type(ext)) == -1)) {
			err_msg(gettext(
			    "Cannot understand file extension for %s\n"),
			    fname);
			exit(1);
		}
	} else {
		at = audio_type;
	}
	if (at == AUDIO_TYPE_SUN)
		return (open_au_write_stream(fname));
	if (at == AUDIO_TYPE_WAV)
		return (open_wav_write_stream(fname));
	if (at == AUDIO_TYPE_CDA)
		return (open_file_write_stream(fname));
	if (at == AUDIO_TYPE_AUR)
		return (open_aur_write_stream(fname));
	return (NULL);
}

/* ARGSUSED */
static void
extract_signal_handler(int sig, siginfo_t *info, void *context)
{
	abort_read = 1;
}

/*
 * Older drives use different data buffer and m:s:f channels to transmit audio
 * information. These channels may not be in sync with each other with the
 * maximum disparity being the size of the data buffer. So handling is needed
 * to keep these two channels in sync.
 */

static int
handle_jitter(uchar_t *buf, uchar_t *last_end)
{
	int i;
	for (i = BLOCK_SIZE*(READ_OVERLAP - BLOCKS_COMPARE); i >= 0; i -= 4) {
		if (memcmp(last_end - BLOCK_SIZE * BLOCKS_COMPARE, buf + i,
		    BLOCK_SIZE * BLOCKS_COMPARE) == 0) {
			return (i + (BLOCK_SIZE * BLOCKS_COMPARE));
		}
	}
	for (i = BLOCK_SIZE*(READ_OVERLAP - BLOCKS_COMPARE);
		i < 2*READ_OVERLAP*BLOCK_SIZE; i += 4) {
		if (memcmp(last_end - BLOCK_SIZE * BLOCKS_COMPARE, buf + i,
		    BLOCK_SIZE * BLOCKS_COMPARE) == 0) {
			return (i + (BLOCK_SIZE * BLOCKS_COMPARE));
		}
	}
	return (-1);
}

int
read_audio_track(cd_device *dev, struct track_info *ti, bstreamhandle h)
{
	uint32_t	blocks_to_write, blocks_to_read, blks_to_overlap;
	uint32_t	start_blk, end_blk, c_blk;
	uint32_t	read_burst_size;
	uchar_t		*tmp, *buf, *prev, *previous_end;
	int		ret, off;
	struct sigaction	sv;
	struct sigaction	oldsv;

	ret = 0;
	abort_read = 0;

	/*
	 * It is good to do small sized I/Os as we have seen many devices
	 * choke with large I/Os. But if the device does not support
	 * reading accurate CDDA then we have to do overlapped I/Os
	 * and reducing size might affect performance. So use small
	 * I/O size if device supports accurate CDDA.
	 */
	if (dev->d_cap & DEV_CAP_ACCURATE_CDDA) {
		read_burst_size = SMALL_READ_BURST_SIZE;
	} else {
		read_burst_size = READ_BURST_SIZE;
	}
	buf = (uchar_t *)my_zalloc(BLOCK_SIZE * read_burst_size);
	prev = (uchar_t *)my_zalloc(BLOCK_SIZE * read_burst_size);
	start_blk = ti->ti_start_address;
	end_blk = ti->ti_start_address + ti->ti_track_size - 1;

	/* Even when we need jitter correction, this will be 0 1st time */
	blks_to_overlap = 0;
	off = 0;

	/* set up signal handler to write audio TOC if ^C is pressed */
	sv.sa_handler = extract_signal_handler;
	(void) sigemptyset(&sv.sa_mask);
	sv.sa_flags = 0;
	(void) sigaction(SIGINT, &sv, &oldsv);

	if ((dev->d_cap & DEV_CAP_EXTRACT_CDDA) == 0) {
		err_msg(gettext("Audio extraction method unknown for %s\n"),
		    dev->d_name ? dev->d_name : gettext("CD drive"));
		exit(1);
	}

	/* if the speed option given, try to change the speed */
	if ((requested_speed != 0) && !cflag) {
		if (verbose)
			(void) printf(gettext("Trying to set speed to %dX.\n"),
			    requested_speed);
		if (dev->d_speed_ctrl(dev, SET_READ_SPEED,
		    requested_speed) == 0) {

			err_msg(gettext("Unable to set speed.\n"));
			exit(1);
		}
		if (verbose) {
			int speed;
			speed = dev->d_speed_ctrl(dev, GET_READ_SPEED, 0);
			if (speed == requested_speed) {
				(void) printf(gettext("Speed set to %dX.\n"),
				    speed);
			} else if (speed == 0) {
				(void) printf(gettext("Could not obtain "
				    "current Read Speed.\n"));
			} else {
				(void) printf(gettext("Speed set to "
				    "closest approximation of %dX allowed "
				    "by device (%dX).\n"),
				    requested_speed, speed);
			}
		}
	}

	print_n_flush(
	    gettext("Extracting audio from track %d..."), ti->ti_track_no);
	init_progress();

	if (debug)
		(void) printf("\nStarting: %d Ending: %d\n",
		    start_blk, end_blk);

	blocks_to_write = 0;

	for (c_blk = start_blk; c_blk < end_blk; c_blk += blocks_to_write) {
		/* update progress indicator */
		(void) progress((end_blk - start_blk),
		    (int64_t)(c_blk - start_blk));
		blocks_to_read =  end_blk - c_blk + blks_to_overlap;

		/*
		 * Make sure we don't read more blocks than the maximum
		 * burst size.
		 */

		if (blocks_to_read > read_burst_size)
			blocks_to_read = read_burst_size;

		if (dev->d_read_audio(dev, c_blk - blks_to_overlap,
		    blocks_to_read, buf) == 0)
			goto read_audio_track_done;

		/*
		 * This drive supports accurate audio extraction don't
		 * do jitter correction.
		 */
		if ((c_blk == start_blk) ||
		    (dev->d_cap & DEV_CAP_ACCURATE_CDDA)) {
			blocks_to_write = blocks_to_read;
			previous_end = buf + (blocks_to_write * BLOCK_SIZE);
			goto skip_jitter_correction;
		}

		if (c_blk == start_blk)
			blks_to_overlap = 0;
		else
			blks_to_overlap = READ_OVERLAP;
		off = handle_jitter(buf, previous_end);
		if (off == -1) {
			if (debug)
				(void) printf(
				    "jitter control failed\n");

			/* recover if jitter correction failed */
			off = BLOCK_SIZE * BLOCKS_COMPARE;
		}

		blocks_to_write = blocks_to_read - blks_to_overlap;

		while ((off + (blocks_to_write*BLOCK_SIZE)) >
		    (blocks_to_read * BLOCK_SIZE)) {
			blocks_to_write--;
		}

		if ((blocks_to_write + c_blk) > end_blk) {
			blocks_to_write = end_blk - c_blk;
		}

		if (blocks_to_write == 0) {
			c_blk = end_blk - 1;
			blocks_to_write = 1;
			(void) memset(&buf[off], 0, off % BLOCK_SIZE);
		}

		previous_end = buf + off + blocks_to_write * BLOCK_SIZE;
skip_jitter_correction:
		(void) memcpy(prev, buf, read_burst_size * BLOCK_SIZE);
		if (h->bstr_write(h, &buf[off], blocks_to_write*BLOCK_SIZE)
		    < 0)
			goto read_audio_track_done;
		tmp = buf;
		buf = prev;
		prev = tmp;

		if (abort_read == 1)
			goto read_audio_track_done;
	}

	ret = 1;
	(void) str_print(gettext("done.\n"), progress_pos);

read_audio_track_done:
	(void) sigaction(SIGINT, &oldsv, (struct sigaction *)0);

	free(buf);
	free(prev);
	return (ret);
}

void
extract_audio(void)
{
	bstreamhandle h;
	struct track_info *ti;

	(void) check_device(target, CHECK_NO_MEDIA | CHECK_DEVICE_NOT_READY |
	    EXIT_IF_CHECK_FAILED);

	ti = (struct track_info *)my_zalloc(sizeof (*ti));
	if (!build_track_info(target, extract_track_no, ti)) {
		err_msg(gettext("Cannot get track information for track %d\n"),
		    extract_track_no);
		exit(1);
	}

	/* Verify track */
	if ((ti->ti_track_size == 0) || ((ti->ti_flags & TI_NWA_VALID) &&
	    (ti->ti_start_address == ti->ti_nwa))) {
		err_msg(gettext("Track %d is empty\n"), extract_track_no);
		exit(1);
	}
	if (ti->ti_track_mode & 4) {
		err_msg(gettext("Track %d is not an audio track\n"),
		    extract_track_no);
		exit(1);
	}
	if (ti->ti_data_mode == 2) {
		err_msg(gettext("Track format is not supported\n"));
		exit(1);
	}

	h = open_audio_for_extraction(extract_file);
	if (h == NULL) {
		err_msg(gettext("Cannot open %s:%s\n"), extract_file,
		    get_err_str());
		exit(1);
	}
	if (read_audio_track(target, ti, h) == 0) {
		err_msg(gettext("Extract audio failed\n"));
		h->bstr_close(h);
		exit(1);
	}
	if (h->bstr_close(h) != 0) {
		err_msg(gettext("Error closing audio stream : %s\n"),
		    get_err_str());
		exit(1);
	}
	exit(0);
}