Skip to content
Snippets Groups Projects
chan_oss.c 42.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2007, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * FreeBSD changes and multiple device support by Luigi Rizzo, 2005.05.25
     * note-this code best seen with ts=8 (8-spaces tabs) in the editor
    
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
    // #define HAVE_VIDEO_CONSOLE	// uncomment to enable video
    
     * \brief Channel driver for OSS sound cards
    
     * \author Mark Spencer <markster@digium.com>
     * \author Luigi Rizzo
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \par See also
     * \arg \ref Config_oss
     *
     * \ingroup channel_drivers
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include <ctype.h>		/* isalnum() used here */
    
    #include <sys/ioctl.h>		
    
    Mark Spencer's avatar
    Mark Spencer committed
    #ifdef __linux
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <linux/soundcard.h>
    
    Tzafrir Cohen's avatar
    Tzafrir Cohen committed
    #elif defined(__FreeBSD__) || defined(__CYGWIN__) || defined(__GLIBC__)
    
    Mark Spencer's avatar
    Mark Spencer committed
    #else
    #include <soundcard.h>
    #endif
    
    #include "asterisk/file.h"
    #include "asterisk/callerid.h"
    
    #include "asterisk/module.h"
    #include "asterisk/pbx.h"
    #include "asterisk/cli.h"
    #include "asterisk/causes.h"
    
    /*! Global jitterbuffer configuration - by default, jb is disabled */
    
    static struct ast_jb_conf default_jbconf =
    {
    	.flags = 0,
    	.max_size = -1,
    	.resync_threshold = -1,
    
    	.target_extra = -1,
    
    /*
     * Basic mode of operation:
     *
     * we have one keyboard (which receives commands from the keyboard)
     * and multiple headset's connected to audio cards.
     * Cards/Headsets are named as the sections of oss.conf.
     * The section called [general] contains the default parameters.
     *
     * At any time, the keyboard is attached to one card, and you
     * can switch among them using the command 'console foo'
     * where 'foo' is the name of the card you want.
     *
     * oss.conf parameters are
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
        ; General config options, with default values shown.
        ; You should use one section per device, with [general] being used
        ; for the first device and also as a template for other devices.
        ;
        ; All but 'debug' can go also in the device-specific sections.
        ;
        ; debug = 0x0		; misc debug flags, default is 0
    
        ; Set the device to use for I/O
        ; device = /dev/dsp
    
        ; Optional mixer command to run upon startup (e.g. to set
        ; volume levels, mutes, etc.
        ; mixer =
    
        ; Software mic volume booster (or attenuator), useful for sound
        ; cards or microphones with poor sensitivity. The volume level
        ; is in dB, ranging from -20.0 to +20.0
        ; boost = n			; mic volume boost in dB
    
        ; Set the callerid for outgoing calls
        ; callerid = John Doe <555-1234>
    
        ; autoanswer = no		; no autoanswer on call
        ; autohangup = yes		; hangup when other party closes
        ; extension = s		; default extension to call
        ; context = default		; default context for outgoing calls
        ; language = ""		; default language
    
    
        ; Default Music on Hold class to use when this channel is placed on hold in
        ; the case that the music class is not set on the channel with
        ; Set(CHANNEL(musicclass)=whatever) in the dialplan and the peer channel
        ; putting this one on hold did not suggest a class to use.
        ;
        ; mohinterpret=default
    
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
        ; If you set overridecontext to 'yes', then the whole dial string
        ; will be interpreted as an extension, which is extremely useful
        ; to dial SIP, IAX and other extensions which use the '@' character.
        ; The default is 'no' just for backward compatibility, but the
        ; suggestion is to change it.
        ; overridecontext = no	; if 'no', the last @ will start the context
    				; if 'yes' the whole string is an extension.
    
        ; low level device parameters in case you have problems with the
        ; device driver on your operating system. You should not touch these
        ; unless you know what you are doing.
        ; queuesize = 10		; frames in device driver
        ; frags = 8			; argument to SETFRAGMENT
    
        ;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
        ; jbenable = yes              ; Enables the use of a jitterbuffer on the receiving side of an
                                      ; OSS channel. Defaults to "no". An enabled jitterbuffer will
                                      ; be used only if the sending side can create and the receiving
    
                                      ; side can not accept jitter. The OSS channel can't accept jitter,
                                      ; thus an enabled jitterbuffer on the receive OSS side will always
                                      ; be used if the sending side can create jitter.
    
    
        ; jbmaxsize = 200             ; Max length of the jitterbuffer in milliseconds.
    
        ; jbresyncthreshold = 1000    ; Jump in the frame timestamps over which the jitterbuffer is
                                      ; resynchronized. Useful to improve the quality of the voice, with
                                      ; big jumps in/broken timestamps, usualy sent from exotic devices
                                      ; and programs. Defaults to 1000.
    
    
        ; jbimpl = fixed              ; Jitterbuffer implementation, used on the receiving side of an OSS
                                      ; channel. Two implementations are currenlty available - "fixed"
    
                                      ; (with size always equals to jbmax-size) and "adaptive" (with
                                      ; variable size, actually the new jb of IAX2). Defaults to fixed.
    
        ; jblog = no                  ; Enables jitterbuffer frame logging. Defaults to "no".
        ;-----------------------------------------------------------------------------------
    
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
        ; device = /dev/dsp1	; alternate device
    
    
    .. and so on for the other cards.
    
     */
    
    /*
     * The following parameters are used in the driver:
     *
     *  FRAME_SIZE	the size of an audio frame, in samples.
     *		160 is used almost universally, so you should not change it.
     *
     *  FRAGS	the argument for the SETFRAGMENT ioctl.
     *		Overridden by the 'frags' parameter in oss.conf
     *
     *		Bits 0-7 are the base-2 log of the device's block size,
     *		bits 16-31 are the number of blocks in the driver's queue.
     *		There are a lot of differences in the way this parameter
     *		is supported by different drivers, so you may need to
     *		experiment a bit with the value.
     *		A good default for linux is 30 blocks of 64 bytes, which
     *		results in 6 frames of 320 bytes (160 samples).
     *		FreeBSD works decently with blocks of 256 or 512 bytes,
     *		leaving the number unspecified.
     *		Note that this only refers to the device buffer size,
     *		this module will then try to keep the lenght of audio
     *		buffered within small constraints.
     *
     *  QUEUE_SIZE	The max number of blocks actually allowed in the device
     *		driver's buffer, irrespective of the available number.
     *		Overridden by the 'queuesize' parameter in oss.conf
     *
     *		Should be >=2, and at most as large as the hw queue above
     *		(otherwise it will never be full).
     */
    
    #define FRAME_SIZE	160
    #define	QUEUE_SIZE	10
    
    #if defined(__FreeBSD__)
    #define	FRAGS	0x8
    #else
    #define	FRAGS	( ( (6 * 5) << 16 ) | 0x6 )
    #endif
    
    /*
     * XXX text message sizes are probably 256 chars, but i am
     * not sure if there is a suitable definition anywhere.
     */
    #define TEXT_SIZE	256
    
    #if 0
    
    #define	TRYOPEN	1				/* try to open on startup */
    
    #define	O_CLOSE	0x444			/* special 'close' mode for device */
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* Which device to use */
    
    #if defined( __OpenBSD__ ) || defined( __NetBSD__ )
    
    #define DEV_DSP "/dev/audio"
    #else
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define DEV_DSP "/dev/dsp"
    
    static char *config = "oss.conf";	/* default config file */
    
    /*!
     * \brief descriptor for one of our channels.
     *
    
     * There is one used for 'default' values (from the [general] entry in
     * the configuration file), and then one instance for each device
     * (the default is cloned from [general], others are only created
     * if the relevant section exists).
     */
    struct chan_oss_pvt {
    	struct chan_oss_pvt *next;
    
    	char *name;
    
    	int total_blocks;			/*!< total blocks in the output device */
    
    	int sounddev;
    	enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex;
    
    	int autoanswer;             /*!< Boolean: whether to answer the immediately upon calling */
    	int autohangup;             /*!< Boolean: whether to hangup the call when the remote end hangs up */
    	int hookstate;              /*!< Boolean: 1 if offhook; 0 if onhook */
    
    	char *mixer_cmd;			/*!< initial command to issue to the mixer */
    	unsigned int queuesize;		/*!< max fragments in queue */
    	unsigned int frags;			/*!< parameter for SETFRAGMENT */
    
    	int warned;					/*!< various flags used for warnings */
    
    #define WARN_used_blocks	1
    #define WARN_speed		2
    #define WARN_frag		4
    
    	int w_errors;				/*!< overfull in the write path */
    
    	/*! boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must
    	 *  be representable in 16 bits to avoid overflows.
    
    #define	BOOST_MAX	40			/*!< slightly less than 7 bits */
    	int boost;					/*!< input boost, scaled by BOOST_SCALE */
    	char device[64];			/*!< device to open */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *owner;
    
    
    	struct video_desc *env;			/*!< parameters for video support */
    
    
    	char ext[AST_MAX_EXTENSION];
    	char ctx[AST_MAX_CONTEXT];
    	char language[MAX_LANGUAGE];
    
    	char cid_name[256];         /*!< Initial CallerID name */
    	char cid_num[256];          /*!< Initial CallerID number  */
    
    	/*! buffers used in oss_write */
    
    	char oss_write_buf[FRAME_SIZE * 2];
    
    	/*! buffers used in oss_read - AST_FRIENDLY_OFFSET space for headers
    	 *  plus enough room for a full frame
    
    	 */
    	char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
    
    	int readpos;				/*!< read position above */
    	struct ast_frame read_f;	/*!< returned by oss_read */
    
    /*! forward declaration */
    
    static struct chan_oss_pvt *find_desc(const char *dev);
    
    static char *oss_active;	 /*!< the active device */
    
    
    /*! \brief return the pointer to the video descriptor */
    
    struct video_desc *get_video_desc(struct ast_channel *c)
    
    	struct chan_oss_pvt *o = c ? c->tech_pvt : find_desc(oss_active);
    
    static struct chan_oss_pvt oss_default = {
    	.sounddev = -1,
    
    	.duplex = M_UNSET,			/* XXX check this */
    
    	.autoanswer = 1,
    	.autohangup = 1,
    	.queuesize = QUEUE_SIZE,
    	.frags = FRAGS,
    	.ext = "s",
    	.ctx = "default",
    	.readpos = AST_FRIENDLY_OFFSET,	/* start here on reads */
    	.lastopen = { 0, 0 },
    
    static struct ast_channel *oss_request(const char *type, format_t format, const struct ast_channel *requestor,
    
    									   void *data, int *cause);
    
    static int oss_digit_begin(struct ast_channel *c, char digit);
    
    static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration);
    
    static int oss_text(struct ast_channel *c, const char *text);
    
    static int oss_hangup(struct ast_channel *c);
    static int oss_answer(struct ast_channel *c);
    static struct ast_frame *oss_read(struct ast_channel *chan);
    static int oss_call(struct ast_channel *c, char *dest, int timeout);
    static int oss_write(struct ast_channel *chan, struct ast_frame *f);
    
    static int oss_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
    
    static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
    
    static char tdesc[] = "OSS Console Channel Driver";
    
    /* cannot do const because need to update some fields at runtime */
    static struct ast_channel_tech oss_tech = {
    
    	.type = "Console",
    	.description = tdesc,
    
    	.capabilities = AST_FORMAT_SLINEAR, /* overwritten later */
    
    	.requester = oss_request,
    
    	.send_digit_begin = oss_digit_begin,
    	.send_digit_end = oss_digit_end,
    
    	.send_text = oss_text,
    	.hangup = oss_hangup,
    	.answer = oss_answer,
    	.read = oss_read,
    	.call = oss_call,
    	.write = oss_write,
    
    	.write_video = console_write_video,
    
    	.indicate = oss_indicate,
    	.fixup = oss_fixup,
    
    /*!
     * \brief returns a pointer to the descriptor with the given name
    
    static struct chan_oss_pvt *find_desc(const char *dev)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct chan_oss_pvt *o = NULL;
    
    	if (!dev)
    
    		ast_log(LOG_WARNING, "null dev\n");
    
    	for (o = oss_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
    
    	if (!o)
    
    		ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
    
    /* !
     * \brief split a string in extension-context, returns pointers to malloc'ed
     *        strings.
     *
    
     * If we do not have 'overridecontext' then the last @ is considered as
    
     * a context separator, and the context is overridden.
     * This is usually not very necessary as you can play with the dialplan,
     * and it is nice not to need it because you have '@' in SIP addresses.
    
     *
     * \return the buffer address.
    
     */
    static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
    {
    	struct chan_oss_pvt *o = find_desc(oss_active);
    
    	if (ext == NULL || ctx == NULL)
    
    	if (!o->overridecontext) {
    		/* parse from the right */
    		*ctx = strrchr(*ext, '@');
    		if (*ctx)
    			*(*ctx)++ = '\0';
    	}
    
    /*!
     * \brief Returns the number of blocks used in the audio output channel
    
     */
    static int used_blocks(struct chan_oss_pvt *o)
    {
    	struct audio_buf_info info;
    
    	if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
    
    		if (!(o->warned & WARN_used_blocks)) {
    
    			ast_log(LOG_WARNING, "Error reading output space\n");
    			o->warned |= WARN_used_blocks;
    		}
    		return 1;
    	}
    
    		if (0)					/* debugging */
    			ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
    
    /*! Write an exactly FRAME_SIZE sized frame */
    
    static int soundcard_writeframe(struct chan_oss_pvt *o, short *data)
    
    	if (o->sounddev < 0)
    		setformat(o, O_RDWR);
    	if (o->sounddev < 0)
    
    	/*
    	 * Nothing complex to manage the audio device queue.
    	 * If the buffer is full just drop the extra, otherwise write.
    	 * XXX in some cases it might be useful to write anyways after
    	 * a number of failures, to restart the output chain.
    	 */
    	res = used_blocks(o);
    	if (res > o->queuesize) {	/* no room to write a block */
    		if (o->w_errors++ == 0 && (oss_debug & 0x4))
    
    			ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	return write(o->sounddev, (void *)data, FRAME_SIZE * 2);
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
     * reset and close the device if opened,
     * then open and initialize it in the desired mode,
     * trigger reads and writes so we can start using it.
     */
    static int setformat(struct chan_oss_pvt *o, int mode)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	int fmt, desired, res, fd;
    
    	if (o->sounddev >= 0) {
    		ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
    		close(o->sounddev);
    		o->duplex = M_UNSET;
    		o->sounddev = -1;
    	}
    
    	if (mode == O_CLOSE)		/* we are done */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return 0;
    
    	if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
    
    		return -1;				/* don't open too often */
    
    	fd = o->sounddev = open(o->device, mode | O_NONBLOCK);
    
    		ast_log(LOG_WARNING, "Unable to re-open DSP device %s: %s\n", o->device, strerror(errno));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    
    #if __BYTE_ORDER == __LITTLE_ENDIAN
    
    Mark Spencer's avatar
    Mark Spencer committed
    	fmt = AFMT_S16_LE;
    
    #else
    	fmt = AFMT_S16_BE;
    #endif
    
    Mark Spencer's avatar
    Mark Spencer committed
    	res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
    	if (res < 0) {
    		ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
    		return -1;
    	}
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	case O_RDWR:
    		res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
    		/* Check to see if duplex set (FreeBSD Bug) */
    		res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
    		if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
    
    			ast_verb(2, "Console is full duplex\n");
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			o->duplex = M_FULL;
    		};
    		break;
    
    	case O_WRONLY:
    		o->duplex = M_WRITE;
    		break;
    
    	case O_RDONLY:
    		o->duplex = M_READ;
    		break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	fmt = 0;
    	res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
    	if (res < 0) {
    		ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
    		return -1;
    	}
    
    	fmt = desired = DEFAULT_SAMPLE_RATE;	/* 8000 Hz desired */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (res < 0) {
    		ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
    		return -1;
    	}
    	if (fmt != desired) {
    
    		if (!(o->warned & WARN_speed)) {
    			ast_log(LOG_WARNING,
    			    "Requested %d Hz, got %d Hz -- sound may be choppy\n",
    			    desired, fmt);
    			o->warned |= WARN_speed;
    
    	/*
    	 * on Freebsd, SETFRAGMENT does not work very well on some cards.
    	 * Default to use 256 bytes, let the user override
    	 */
    	if (o->frags) {
    		fmt = o->frags;
    		res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
    		if (res < 0) {
    			if (!(o->warned & WARN_frag)) {
    				ast_log(LOG_WARNING,
    					"Unable to set fragment size -- sound may be choppy\n");
    				o->warned |= WARN_frag;
    			}
    
    	/* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */
    	res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
    	res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
    	/* it may fail if we are in half duplex, never mind */
    	return 0;
    
    /*
     * some of the standard methods supported by channels.
     */
    
    static int oss_digit_begin(struct ast_channel *c, char digit)
    {
    	return 0;
    }
    
    
    static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	/* no better use for received digits than print them */
    
    	ast_verbose(" << Console Received digit %c of duration %u ms >> \n", 
    		digit, duration);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int oss_text(struct ast_channel *c, const char *text)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	ast_verbose(" << Console Received text %s >> \n", text);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    /*!
     * \brief handler for incoming calls. Either autoanswer, or start ringing
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int oss_call(struct ast_channel *c, char *dest, int timeout)
    {
    
    	struct ast_frame f = { AST_FRAME_CONTROL, };
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(name);
    		AST_APP_ARG(flags);
    	);
    	char *parse = ast_strdupa(dest);
    
    	AST_NONSTANDARD_APP_ARGS(args, parse, '/');
    
    	ast_verbose(" << Call to device '%s' dnid '%s' rdnis '%s' on console from '%s' <%s> >>\n",
    
    		dest,
    		S_OR(c->dialed.number.str, ""),
    		S_COR(c->redirecting.from.number.valid, c->redirecting.from.number.str, ""),
    		S_COR(c->caller.id.name.valid, c->caller.id.name.str, ""),
    		S_COR(c->caller.id.number.valid, c->caller.id.number.str, ""));
    
    	if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "answer") == 0) {
    
    		f.subclass.integer = AST_CONTROL_ANSWER;
    
    		ast_queue_frame(c, &f);
    	} else if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "noanswer") == 0) {
    
    		f.subclass.integer = AST_CONTROL_RINGING;
    
    		ast_verbose(" << Auto-answered >> \n");
    
    		f.subclass.integer = AST_CONTROL_ANSWER;
    
    		ast_queue_frame(c, &f);
    
    		o->hookstate = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		ast_verbose("<< Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
    
    		f.subclass.integer = AST_CONTROL_RINGING;
    
    		ast_queue_frame(c, &f);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	return 0;
    }
    
    
    /*!
     * \brief remote side answered the phone
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int oss_answer(struct ast_channel *c)
    {
    
    	struct chan_oss_pvt *o = c->tech_pvt;
    
    	ast_verbose(" << Console call has been answered >> \n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	ast_setstate(c, AST_STATE_UP);
    
    	o->hookstate = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    static int oss_hangup(struct ast_channel *c)
    {
    
    	ast_verbose(" << Hangup on console >> \n");
    
    	console_video_uninit(o->env);
    
    	ast_module_unref(ast_module_info->self);
    
    	if (o->hookstate) {
    		if (o->autoanswer || o->autohangup) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Assume auto-hangup too */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    /*! \brief used for data coming from the network */
    
    static int oss_write(struct ast_channel *c, struct ast_frame *f)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	int src;
    	struct chan_oss_pvt *o = c->tech_pvt;
    
    	/*
    	 * we could receive a block which is not a multiple of our
    	 * FRAME_SIZE, so buffer it locally and write to the device
    	 * in FRAME_SIZE chunks.
    	 * Keep the residue stored for future use.
    	 */
    
    	src = 0;					/* read position into f->data */
    	while (src < f->datalen) {
    
    		/* Compute spare room in the buffer */
    		int l = sizeof(o->oss_write_buf) - o->oss_write_dst;
    
    		if (f->datalen - src >= l) {	/* enough to fill a frame */
    
    			memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
    
    			soundcard_writeframe(o, (short *) o->oss_write_buf);
    
    			memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
    
    			src += l;			/* but really, we are done */
    
    static struct ast_frame *oss_read(struct ast_channel *c)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	int res;
    
    	struct chan_oss_pvt *o = c->tech_pvt;
    	struct ast_frame *f = &o->read_f;
    
    
    	/* XXX can be simplified returning &ast_null_frame */
    
    	/* prepare a NULL frame in case we don't have enough data to return */
    
    Steve Murphy's avatar
    Steve Murphy committed
    	memset(f, '\0', sizeof(struct ast_frame));
    
    	f->src = oss_tech.type;
    
    	res = read(o->sounddev, o->oss_read_buf + o->readpos, sizeof(o->oss_read_buf) - o->readpos);
    	if (res < 0)				/* audio data not ready, return a NULL frame */
    
    		return f;
    
    	o->readpos += res;
    	if (o->readpos < sizeof(o->oss_read_buf))	/* not enough samples */
    		return f;
    
    	if (o->mute)
    		return f;
    
    	o->readpos = AST_FRIENDLY_OFFSET;	/* reset read pointer for next frame */
    	if (c->_state != AST_STATE_UP)	/* drop data if frame is not up */
    		return f;
    	/* ok we can build and deliver the frame to the caller */
    	f->frametype = AST_FRAME_VOICE;
    
    	f->subclass.codec = AST_FORMAT_SLINEAR;
    
    	f->samples = FRAME_SIZE;
    	f->datalen = FRAME_SIZE * 2;
    
    	f->data.ptr = o->oss_read_buf + AST_FRIENDLY_OFFSET;
    
    	if (o->boost != BOOST_SCALE) {	/* scale and clip values */
    
    		for (i = 0; i < f->samples; i++) {
    			x = (p[i] * o->boost) / BOOST_SCALE;
    			if (x > 32767)
    				x = 32767;
    			else if (x < -32768)
    				x = -32768;
    			p[i] = x;
    		}
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct chan_oss_pvt *o = newchan->tech_pvt;
    	o->owner = newchan;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int oss_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	case AST_CONTROL_BUSY:
    	case AST_CONTROL_CONGESTION:
    	case AST_CONTROL_RINGING:
    	case -1:
    		res = -1;
    		break;
    
    	case AST_CONTROL_PROGRESS:
    	case AST_CONTROL_PROCEEDING:
    	case AST_CONTROL_VIDUPDATE:
    
    	case AST_CONTROL_SRCUPDATE:
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	case AST_CONTROL_HOLD:
    		ast_verbose(" << Console Has Been Placed on Hold >> \n");
    		ast_moh_start(c, data, o->mohinterpret);
    		break;
    	case AST_CONTROL_UNHOLD:
    		ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
    		ast_moh_stop(c);
    		break;
    	default:
    		ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name);
    		return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    /*!
     * \brief allocate a new channel.
    
    static struct ast_channel *oss_new(struct chan_oss_pvt *o, char *ext, char *ctx, int state, const char *linkedid)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, linkedid, 0, "Console/%s", o->device + 5);
    
    	if (o->sounddev < 0)
    		setformat(o, O_RDWR);
    
    	ast_channel_set_fd(c, 0, o->sounddev); /* -1 if device closed, override later */
    
    	/* if the console makes the call, add video to the offer */
    	if (state == AST_STATE_RINGING)
    
    		c->nativeformats |= console_video_formats;
    
    	c->readformat = AST_FORMAT_SLINEAR;
    	c->writeformat = AST_FORMAT_SLINEAR;
    	c->tech_pvt = o;
    
    
    		ast_string_field_set(c, language, o->language);
    
    	/* Don't use ast_set_callerid() here because it will
    	 * generate a needless NewCallerID event */
    
    	if (!ast_strlen_zero(o->cid_num)) {
    		c->caller.ani.number.valid = 1;
    		c->caller.ani.number.str = ast_strdup(o->cid_num);
    	}
    
    	if (!ast_strlen_zero(ext)) {
    		c->dialed.number.str = ast_strdup(ext);
    	}
    
    	ast_module_ref(ast_module_info->self);
    
    	if (state != AST_STATE_DOWN) {
    		if (ast_pbx_start(c)) {
    			ast_log(LOG_WARNING, "Unable to start PBX on %s\n", c->name);
    			ast_hangup(c);
    
    	console_video_start(get_video_desc(c), c); /* XXX cleanup */
    
    static struct ast_channel *oss_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct chan_oss_pvt *o;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(name);
    		AST_APP_ARG(flags);
    	);
    	char *parse = ast_strdupa(data);
    
    
    	AST_NONSTANDARD_APP_ARGS(args, parse, '/');
    	o = find_desc(args.name);
    
    	ast_log(LOG_WARNING, "oss_request ty <%s> data 0x%p <%s>\n", type, data, (char *) data);
    
    		ast_log(LOG_NOTICE, "Device %s not found\n", args.name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return NULL;
    	}
    
    		ast_log(LOG_NOTICE, "Format %s unsupported\n", ast_getformatname_multiple(buf, sizeof(buf), format));
    
    		return NULL;
    	}
    	if (o->owner) {
    		ast_log(LOG_NOTICE, "Already have a call (chan %p) on the OSS channel\n", o->owner);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return NULL;
    	}
    
    	c = oss_new(o, NULL, NULL, AST_STATE_DOWN, requestor ? requestor->linkedid : NULL);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "Unable to create new OSS channel\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value);
    
    /*! Generic console command handler. Basically a wrapper for a subset
     *  of config file options which are also available from the CLI
     */
    static char *console_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct chan_oss_pvt *o = find_desc(oss_active);
    	const char *var, *value;
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = CONSOLE_VIDEO_CMDS;
    
    		e->usage = 
    			"Usage: " CONSOLE_VIDEO_CMDS "...\n"
    			"       Generic handler for console commands.\n";
    
    		return NULL;
    
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc < e->args)
    		return CLI_SHOWUSAGE;
    	if (o == NULL) {
    		ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
    			oss_active);
    		return CLI_FAILURE;
    	}
    	var = a->argv[e->args-1];
    	value = a->argc > e->args ? a->argv[e->args] : NULL;
    	if (value)      /* handle setting */
    		store_config_core(o, var, value);
    
    	if (!console_video_cli(o->env, var, a->fd))	/* print video-related values */
    
    		return CLI_SUCCESS;
    	/* handle other values */
    	if (!strcasecmp(var, "device")) {
    		ast_cli(a->fd, "device is [%s]\n", o->device);
    	}
    	return CLI_SUCCESS;
    }
    
    
    static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    {
    	struct chan_oss_pvt *o = find_desc(oss_active);
    
    
    	switch (cmd) {
    	case CLI_INIT:
    
    		e->command = "console {set|show} autoanswer [on|off]";
    
    		e->usage =
    
    			"Usage: console {set|show} autoanswer [on|off]\n"
    
    			"       Enables or disables autoanswer feature.  If used without\n"
    			"       argument, displays the current on/off status of autoanswer.\n"
    			"       The default value of autoanswer is in 'oss.conf'.\n";
    		return NULL;
    
    	case CLI_GENERATE:
    
    	if (a->argc == e->args - 1) {
    
    		ast_cli(a->fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
    		return CLI_SUCCESS;
    	}
    
    	if (a->argc != e->args)
    
    		return CLI_SHOWUSAGE;
    
    	if (o == NULL) {
    		ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
    		    oss_active);
    
    		return CLI_FAILURE;
    
    	if (!strcasecmp(a->argv[e->args-1], "on"))
    
    		o->autoanswer = 1;
    
    	else if (!strcasecmp(a->argv[e->args - 1], "off"))
    
    		o->autoanswer = 0;
    	else
    
    		return CLI_SHOWUSAGE;
    	return CLI_SUCCESS;
    
    /*! \brief helper function for the answer key/cli command */
    
    	struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
    
    	struct chan_oss_pvt *o = find_desc(oss_active);
    	if (!o->owner) {
    		if (fd > -1)
    			ast_cli(fd, "No one is calling us\n");
    		return CLI_FAILURE;
    	}
    	o->hookstate = 1;
    	ast_queue_frame(o->owner, &f);
    	return CLI_SUCCESS;
    }
    
    
    /*!
     * \brief answer command from the console
    
    static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "console answer";
    		e->usage =
    			"Usage: console answer\n"
    			"       Answers an incoming call on the console (OSS) channel.\n";
    		return NULL;
    
    	case CLI_GENERATE:
    		return NULL;	/* no completion */
    	}
    	if (a->argc != e->args)
    		return CLI_SHOWUSAGE;
    
    	return console_do_answer(a->fd);
    
    /*!
     * \brief Console send text CLI command
     *
     * \note concatenate all arguments into a single string. argv is NULL-terminated
    
    static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    
    {
    	struct chan_oss_pvt *o = find_desc(oss_active);
    	char buf[TEXT_SIZE];
    
    	if (cmd == CLI_INIT) {
    		e->command = "console send text";
    		e->usage =
    			"Usage: console send text <message>\n"
    			"       Sends a text message for display on the remote terminal.\n";