[asterisk-dev] Simple chan_jack.so where jack_read and jack_write are never called...

Fabien COMTE fabien.comte at ercogener.com
Wed Oct 7 08:26:25 CDT 2009


Hi,
I try to write a very simple chan_jack.so (no resample, jack must run at
8000 Hz...)
The problem is that my functions jack_read and jack_write are never called
during a call. Does anyone have an idea ?

I probably get the same problem like in post "Ways of invoking channel read
function". I don't know how to set readdev fd...

Thank you,

Fabien


extensions.conf :
exten => _0.,1,Dial(console/jack) 

jack.conf :
[general]
autoanswer=yes
;
; Default context (is overridden with @context syntax)
;
context=extensions-out
;
; Default extension to call
;
extension=s


input_device=default
output_device=default

client_name=asterisk
connect_input_port=from_voip:input
connect_output_port=to_voip:output


chan_jack.so :

/*
 *
 * By Fabien Comte based <fabien.comte at ercogener.com> on Matthew Fredrickson
<creslin at digium.com> chan_jack.c and Russell Bryant <russell at digium.com>
app_jack.c
 *
 * 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.
 */

/*! \file 
 * \brief JACK sound card channel driver 
 *
 * \author Fabien Comte based <fabien.comte at ercogener.com> on Matthew
Fredrickson <creslin at digium.com> chan_jack.c and Russell Bryant
<russell at digium.com> app_jack.c
 *
 * \par See also
 * \arg Config_jack
 *
 * \ingroup channel_drivers
 */

/*** MODULEINFO
	<depend>jack</depend>
 ***/

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 000001 $")

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>


#include "asterisk/frame.h"
#include "asterisk/channel.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/config.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/endian.h"
#include "asterisk/stringfields.h"
#include "asterisk/abstract_jb.h"
#include "asterisk/musiconhold.h"
#include "asterisk/poll-compat.h"

/*! Global jitterbuffer configuration - by default, jb is disabled */
static struct ast_jb_conf default_jbconf = {
	.flags = 0,
	.max_size = -1,
	.resync_threshold = -1,
	.impl = ""
};
static struct ast_jb_conf global_jbconf;

#define DEBUG 0
/* Which device to use */
#define JACK_INDEV "default"
#define JACK_OUTDEV "default"

/* Lets use 160 sample frames, just like GSM.  */
#define FRAME_SIZE 160

static char indevname[50] = JACK_INDEV;
static char outdevname[50] = JACK_OUTDEV;

static int silencesuppression = 0;
static int silencethreshold = 1000;

AST_MUTEX_DEFINE_STATIC(jacklock);

static const char tdesc[] = "JACK Console Channel Driver";
static const char config[] = "jack.conf";

static char context[AST_MAX_CONTEXT] = "default";
static char language[MAX_LANGUAGE] = "";
static char exten[AST_MAX_EXTENSION] = "s";
static char mohinterpret[MAX_MUSICCLASS];

static int hookstate = 0;

static struct chan_jack_pvt {
	/* We only have one JACK structure -- near sighted perhaps, but it
	   keeps this driver as simple as possible -- as it should be. */
	struct ast_channel *owner;
	char exten[AST_MAX_EXTENSION];
	char context[AST_MAX_CONTEXT];

} jack;


/* File descriptors for sound device */
static int readdev = -1;

static int autoanswer = 1;

static struct ast_channel *jack_request(const char *type, int format, void
*data, int *cause);
static int jack_digit(struct ast_channel *c, char digit, unsigned int
duration);
static int jack_text(struct ast_channel *c, const char *text);
static int jack_hangup(struct ast_channel *c);
static int jack_answer(struct ast_channel *c);
static struct ast_frame *jack_read(struct ast_channel *chan);
static int jack_call(struct ast_channel *c, char *dest, int timeout);
static int jack_write(struct ast_channel *chan, struct ast_frame *f);
static int jack_indicate(struct ast_channel *chan, int cond, const void
*data, size_t datalen);
static int jack_fixup(struct ast_channel *oldchan, struct ast_channel
*newchan);

static const struct ast_channel_tech jack_tech = {
	.type = "Console",
	.description = tdesc,
	.capabilities = AST_FORMAT_SLINEAR,
	.requester = jack_request,
	.send_digit_end = jack_digit,
	.send_text = jack_text,
	.hangup = jack_hangup,
	.answer = jack_answer,
	.read = jack_read,
	.call = jack_call,
	.write = jack_write,
	.indicate = jack_indicate,
	.fixup = jack_fixup,
};

// jack customs start
************************************************************
#include <jack/jack.h>
#include <jack/ringbuffer.h>

#define CHAN_JACK 1
#define RINGBUFFER_SIZE 16384

struct jack_data 
{
	char server_name[128];
	char client_name[128];
	char connect_input_port[128];
	char connect_output_port[128];
	jack_client_t *client;
	jack_port_t *input_port;
	jack_port_t *output_port;
	jack_ringbuffer_t *input_rb;
	jack_ringbuffer_t *output_rb;
	void *output_resampler;
	double output_resample_factor;
	void *input_resampler;
	double input_resample_factor;
	unsigned int stop:1;
	unsigned int no_start_server:1;
};

struct jack_data *jack_data;

static const struct {
	jack_status_t status;
	const char *str;
} jack_status_table[] = {
	{ JackFailure,        "Failure" },
	{ JackInvalidOption,  "Invalid Option" },
	{ JackNameNotUnique,  "Name Not Unique" },
	{ JackServerStarted,  "Server Started" },
	{ JackServerFailed,   "Server Failed" },
	{ JackServerError,    "Server Error" },
	{ JackNoSuchClient,   "No Such Client" },
	{ JackLoadFailure,    "Load Failure" },
	{ JackInitFailure,    "Init Failure" },
	{ JackShmFailure,     "Shared Memory Access Failure" },
	{ JackVersionError,   "Version Mismatch" },
};

static const char *jack_status_to_str(jack_status_t status)
{
	int i;

	for (i = 0; i < ARRAY_LEN(jack_status_table); i++) {
		if (jack_status_table[i].status == status)
			return jack_status_table[i].str;
	}

	return "Unknown Error";
}


static void log_jack_status(const char *prefix, jack_status_t status)
{
	struct ast_str *str = ast_str_alloca(512);
	int i, first = 0;

	for (i = 0; i < (sizeof(status) * 8); i++) {
		if (!(status & (1 << i)))
			continue;

		if (!first) {
			ast_str_set(&str, 0, "%s", jack_status_to_str((1 <<
i)));
			first = 1;
		} else
			ast_str_append(&str, 0, ", %s",
jack_status_to_str((1 << i)));
	}
	
	ast_log(LOG_NOTICE, "%s: %s\n", prefix, str->str);
}


/*!
 * \brief Handle jack input port
 *
 * Read nframes number of samples from the input buffer, resample it
 * if necessary, and write it into the appropriate ringbuffer. 
 */
static void handle_input(void *buf, jack_nframes_t nframes)
{
	if (hookstate == 1)
	{
		//ast_log(LOG_WARNING, "i\n");
		short s_buf[nframes];
		float *in_buf = buf;
		size_t res;
		int i;
		size_t write_len = sizeof(s_buf);

		for (i = 0; i < nframes; i++)
			s_buf[i] = ((float) in_buf[i]) * ((float) SHRT_MAX);
		
		res = jack_ringbuffer_write(jack_data->input_rb, (const char
*) s_buf, write_len);
		if (res != write_len) {
			ast_debug(2, "Tried to write %d bytes to the
ringbuffer, but only wrote %d\n",
				(int) sizeof(s_buf), (int) res);
		}
	}
}

/*!
 * \brief Handle jack output port
 *
 * Read nframes number of samples from the ringbuffer and write it out to
the
 * output port buffer.
 */
static void handle_output(void *buf, jack_nframes_t nframes)
{	
	if (hookstate == 1)
	{
		//ast_log(LOG_WARNING, "o\n");
		size_t res, len;

		len = nframes * sizeof(float);

		res = jack_ringbuffer_read(jack_data->output_rb, buf, len);

		if (len != res) {
			ast_debug(2, "Wanted %d bytes to send to the output
port, "
				"but only got %d\n", (int) len, (int) res);
		}
	}
	else
	{
		int i;
		float * buf_f = (float *) buf;
		for (i = 0; i < nframes; i++)
		{
			buf_f[i] = 0.0;
		}
	}
}

static int jack_process(jack_nframes_t nframes, void *arg)
{
	if (jack.owner)
	{
		ast_log(LOG_WARNING, "p\n");
	}
	void *input_port_buf, *output_port_buf;

	input_port_buf = jack_port_get_buffer(jack_data->input_port,
nframes);
	handle_input(input_port_buf, nframes);

	output_port_buf = jack_port_get_buffer(jack_data->output_port,
nframes);
	handle_output(output_port_buf, nframes);

	return 0;
}

static void jack_shutdown()
{
	jack_data->stop = 1;
}

static struct jack_data *jack_data_alloc(void)
{
	if (!(jack_data = ast_calloc(1, sizeof(struct jack_data))))
		return NULL;
	
	return jack_data;
}

static struct jack_data *destroy_jack_data()
{
	if (jack_data == NULL)
	{
		return NULL;
	}
	
	if (jack_data->input_port) {
		jack_port_unregister(jack_data->client,
jack_data->input_port);
		jack_data->input_port = NULL;
	}

	if (jack_data->output_port) {
		jack_port_unregister(jack_data->client,
jack_data->output_port);
		jack_data->output_port = NULL;
	}

	if (jack_data->client) {
		jack_client_close(jack_data->client);
		jack_data->client = NULL;
	}

	if (jack_data->input_rb) {
		jack_ringbuffer_free(jack_data->input_rb);
		jack_data->input_rb = NULL;
	}

	if (jack_data->output_rb) {
		jack_ringbuffer_free(jack_data->output_rb);
		jack_data->output_rb = NULL;
	}

	//ast_string_field_free_memory(jack_data);

	ast_free(jack_data);

	return NULL;
}

// jack customs end
************************************************************




static int jack_init(void)
{
	jack_status_t status = 0;
	jack_options_t jack_options = JackNullOption;


	if (!(jack_data->output_rb = jack_ringbuffer_create(RINGBUFFER_SIZE
* 2)))
		return -1;

	if (!(jack_data->input_rb =
jack_ringbuffer_create(RINGBUFFER_SIZE)))
		return -1;

	if (jack_data->no_start_server)
		jack_options |= JackNoStartServer;

	if (!ast_strlen_zero(jack_data->server_name)) {
		jack_options |= JackServerName;
		jack_data->client = jack_client_open(jack_data->client_name,
jack_options, &status,
			jack_data->server_name);
	} else {
		jack_data->client = jack_client_open(jack_data->client_name,
jack_options, &status);
	}

	if (status)
		log_jack_status("Client Open Status", status);

	if (!jack_data->client)
		return -1;

	jack_data->input_port = jack_port_register(jack_data->client,
"input",
		JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput |
JackPortIsTerminal, 0);
	if (!jack_data->input_port) {
		ast_log(LOG_ERROR, "Failed to create input port for jack
port\n");
		return -1;
	}

	jack_data->output_port = jack_port_register(jack_data->client,
"output",
		JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput |
JackPortIsTerminal, 0);
	if (!jack_data->output_port) {
		ast_log(LOG_ERROR, "Failed to create output port for jack
port\n");
		return -1;
	}

	if (jack_set_process_callback(jack_data->client, jack_process, (void
*) NULL)) {
		ast_log(LOG_ERROR, "Failed to register process callback with
jack client\n");
		return -1;
	}

	jack_on_shutdown(jack_data->client, jack_shutdown, NULL);

	if (jack_activate(jack_data->client)) {
		ast_log(LOG_ERROR, "Unable to activate jack client\n");
		return -1;
	}

	while (!ast_strlen_zero(jack_data->connect_input_port)) {
		const char **ports;
		int i;

		ports = jack_get_ports(jack_data->client,
jack_data->connect_input_port,
			NULL, JackPortIsInput);

		if (!ports) {
			ast_log(LOG_ERROR, "No input port matching '%s' was
found\n",
				jack_data->connect_input_port);
			break;
		}

		for (i = 0; ports[i]; i++) {
			ast_debug(1, "Found port '%s' that matched specified
input port '%s'\n",
				ports[i], jack_data->connect_input_port);
		}

		if (jack_connect(jack_data->client,
jack_port_name(jack_data->output_port), ports[0])) {
			ast_log(LOG_ERROR, "Failed to connect '%s' to
'%s'\n", ports[0],
				jack_port_name(jack_data->output_port));
		} else {
			ast_debug(1, "Connected '%s' to '%s'\n", ports[0],
				jack_port_name(jack_data->output_port));
		}

		free((void *) ports);

		break;
	}

	while (!ast_strlen_zero(jack_data->connect_output_port)) {
		const char **ports;
		int i;

		ports = jack_get_ports(jack_data->client,
jack_data->connect_output_port,
			NULL, JackPortIsOutput);

		if (!ports) {
			ast_log(LOG_ERROR, "No output port matching '%s' was
found\n",
				jack_data->connect_output_port);
			break;
		}

		for (i = 0; ports[i]; i++) {
			ast_debug(1, "Found port '%s' that matched specified
output port '%s'\n",
				ports[i], jack_data->connect_output_port);
		}

		if (jack_connect(jack_data->client, ports[0],
jack_port_name(jack_data->input_port))) {
			ast_log(LOG_ERROR, "Failed to connect '%s' to
'%s'\n", ports[0],
				jack_port_name(jack_data->input_port));
		} else {
			ast_debug(1, "Connected '%s' to '%s'\n", ports[0],
				jack_port_name(jack_data->input_port));
		}

		free((void *) ports);

		break;
	}
	return 0;
}

static int jack_digit(struct ast_channel *c, char digit, unsigned int
duration)
{
	ast_mutex_lock(&jacklock);
	ast_verbose(" << Console Received digit %c of duration %u ms >> \n",

		digit, duration);
	ast_mutex_unlock(&jacklock);

	return 0;
}

static int jack_text(struct ast_channel *c, const char *text)
{
	ast_mutex_lock(&jacklock);
	ast_verbose(" << Console Received text %s >> \n", text);
	ast_mutex_unlock(&jacklock);

	return 0;
}

static void grab_owner(void)
{
	while (jack.owner && ast_channel_trylock(jack.owner)) {
		DEADLOCK_AVOIDANCE(&jacklock);
	}
}

static int jack_call(struct ast_channel *c, char *dest, int timeout)
{
	struct ast_frame f = { AST_FRAME_CONTROL };

	ast_mutex_lock(&jacklock);
	ast_verbose(" << Call placed to '%s' on console >> \n", dest);
	if (autoanswer) {
		ast_verbose(" << Auto-answered >> \n");
		grab_owner();
		if (jack.owner) {
			f.subclass = AST_CONTROL_ANSWER;
			ast_queue_frame(jack.owner, &f);
			ast_channel_unlock(jack.owner);
		}
	} else {
		ast_verbose(" << Type 'answer' to answer, or use
'autoanswer' for future calls >> \n");
		grab_owner();
		if (jack.owner) {
			f.subclass = AST_CONTROL_RINGING;
			ast_queue_frame(jack.owner, &f);
			ast_channel_unlock(jack.owner);
			ast_indicate(jack.owner, AST_CONTROL_RINGING);
		}
	}
	
	# if CHAN_JACK
	// TODO
	# else
	snd_pcm_prepare(jack.icard);
	snd_pcm_start(jack.icard);
	# endif
	
	ast_mutex_unlock(&jacklock);

	return 0;
}

static int jack_answer(struct ast_channel *c)
{
	ast_mutex_lock(&jacklock);
	ast_verbose(" << Console call has been answered >> \n");
	ast_setstate(c, AST_STATE_UP);
	
	# if CHAN_JACK
	// TODO
	# else
	snd_pcm_prepare(jack.icard);
	snd_pcm_start(jack.icard);
	# endif
	
	ast_mutex_unlock(&jacklock);

	return 0;
}

static int jack_hangup(struct ast_channel *c)
{
	ast_mutex_lock(&jacklock);
	c->tech_pvt = NULL;
	jack.owner = NULL;
	ast_verbose(" << Hangup on console >> \n");
	ast_module_unref(ast_module_info->self);
	hookstate = 0;
	
	# if CHAN_JACK
	// TODO
	# else
	snd_pcm_drop(jack.icard);
	ast_mutex_unlock(&jacklock);
	# endif
	
	return 0;
}


static int jack_write(struct ast_channel *chan, struct ast_frame *f)
{
	ast_log(LOG_WARNING, "w\n");
	ast_mutex_lock(&jacklock);
	
	float f_buf[f->samples];
	size_t f_buf_used = 0;
	int i;
	int16_t *s_buf = f->data.ptr;
	size_t res;

	for (i = 0; i < f->samples; i++)
	{
		f_buf[i] = ((float) s_buf[i]) / ((float) SHRT_MAX);
	}

	f_buf_used = f->samples;
	
	res = jack_ringbuffer_write(jack_data->output_rb, (const char *)
f_buf, f_buf_used * sizeof(float));
	
	
	
	if (res != (f_buf_used * sizeof(float))) 
	{
		ast_debug(2, "Tried to write %d bytes to the ringbuffer, but
only wrote %d\n", (int) (f_buf_used * sizeof(float)), (int) res);
	}
	
	ast_mutex_unlock(&jacklock);
	return 0;
}


static struct ast_frame *jack_read(struct ast_channel *chan)
{
	ast_log(LOG_WARNING, "r\n");
	ast_mutex_lock(&jacklock);
	
	static short buf[FRAME_SIZE];
	static struct ast_frame f;
		
	f.frametype = AST_FRAME_NULL;
	f.subclass = 0;
	f.samples = 0;
	f.datalen = 0;
	f.data.ptr = NULL;
	f.offset = 0;
	f.src = "Console";
	f.mallocd = 0;
	f.delivery.tv_sec = 0;
	f.delivery.tv_usec = 0;


	size_t res;

	res = jack_ringbuffer_read_space(jack_data->input_rb);

	if (res < FRAME_SIZE) {
		/* Not enough data ready for another frame, move on ... */
		ast_debug(1, "Sending an empty frame for JACK\n");
		memset(buf, 0, FRAME_SIZE);
	}
	else
	{
		res = jack_ringbuffer_read(jack_data->input_rb, (char *)
buf, FRAME_SIZE);

		if (res < FRAME_SIZE) {
			ast_log(LOG_ERROR, "Error reading from ringbuffer,
even though it said there was enough data\n");
		}
	}
	
	f.frametype = AST_FRAME_VOICE;
	f.subclass = AST_FORMAT_SLINEAR;
	f.samples = FRAME_SIZE;
	f.datalen = FRAME_SIZE * 2;
	f.data.ptr = buf;
	f.offset = 0;
	f.src = "Console";
	f.mallocd = 0;

	ast_mutex_unlock(&jacklock);

	return &f;
}

static int jack_fixup(struct ast_channel *oldchan, struct ast_channel
*newchan)
{
	struct chan_jack_pvt *p = newchan->tech_pvt;

	ast_mutex_lock(&jacklock);
	p->owner = newchan; // TODO
	ast_mutex_unlock(&jacklock);

	return 0;
}

static int jack_indicate(struct ast_channel *chan, int cond, const void
*data, size_t datalen)
{
	int res = 0;

	ast_mutex_lock(&jacklock);

	switch (cond) {
	case AST_CONTROL_BUSY:
	case AST_CONTROL_CONGESTION:
	case AST_CONTROL_RINGING:
	case -1:
		res = -1;  /* Ask for inband indications */
		break;
	case AST_CONTROL_PROGRESS:
	case AST_CONTROL_PROCEEDING:
	case AST_CONTROL_VIDUPDATE:
	case AST_CONTROL_SRCUPDATE:
		break;
	case AST_CONTROL_HOLD:
		ast_verbose(" << Console Has Been Placed on Hold >> \n");
		ast_moh_start(chan, data, mohinterpret);
		break;
	case AST_CONTROL_UNHOLD:
		ast_verbose(" << Console Has Been Retrieved from Hold >>
\n");
		ast_moh_stop(chan);
		break;
	default:
		ast_log(LOG_WARNING, "Don't know how to display condition %d
on %s\n", cond, chan->name);
		res = -1;
	}

	ast_mutex_unlock(&jacklock);

	return res;
}

static struct ast_channel *jack_new(struct chan_jack_pvt *p, int state)
{
	struct ast_channel *tmp = NULL;

	if (!(tmp = ast_channel_alloc(1, state, 0, 0, "", p->exten,
p->context, 0, "JACK/%s", indevname)))
	{
		ast_log(LOG_ERROR, "ast_channel_alloc() failed\n");
		return NULL;
	}

	tmp->tech = &jack_tech;
	ast_channel_set_fd(tmp, 0, readdev); // TODO how to set readdev
	tmp->nativeformats = AST_FORMAT_SLINEAR;
	tmp->readformat = AST_FORMAT_SLINEAR;
	tmp->writeformat = AST_FORMAT_SLINEAR;
	tmp->tech_pvt = p;
	if (!ast_strlen_zero(p->context))
		ast_copy_string(tmp->context, p->context,
sizeof(tmp->context));
	if (!ast_strlen_zero(p->exten))
		ast_copy_string(tmp->exten, p->exten, sizeof(tmp->exten));
	if (!ast_strlen_zero(language))
		ast_string_field_set(tmp, language, language);
	p->owner = tmp;
	ast_module_ref(ast_module_info->self);
	ast_jb_configure(tmp, &global_jbconf);
	if (state != AST_STATE_DOWN) {
		if (ast_pbx_start(tmp)) {
			ast_log(LOG_WARNING, "Unable to start PBX on %s\n",
tmp->name);
			ast_hangup(tmp);
			tmp = NULL;
		}
	}

	return tmp;
}

static struct ast_channel *jack_request(const char *type, int fmt, void
*data, int *cause)
{
	int oldformat = fmt;
	struct ast_channel *tmp = NULL;

	if (!(fmt &= AST_FORMAT_SLINEAR)) {
		ast_log(LOG_NOTICE, "Asked to get a channel of format
'%d'\n", oldformat);
		return NULL;
	}

	ast_mutex_lock(&jacklock);

	if (jack.owner) {
		ast_log(LOG_NOTICE, "Already have a call on the JACK
channel\n");
		*cause = AST_CAUSE_BUSY;
	} else if (!(tmp = jack_new(&jack, AST_STATE_DOWN))) {
		ast_log(LOG_WARNING, "Unable to create new JACK channel\n");
	}

	ast_mutex_unlock(&jacklock);
	
	ast_log(LOG_WARNING, "jack_request() OK\n");

	return tmp;
}

static char *autoanswer_complete(const char *line, const char *word, int
pos, int state)
{
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
	switch (state) {
		case 0:
			if (!ast_strlen_zero(word) && !strncasecmp(word,
"on", MIN(strlen(word), 2)))
				return ast_strdup("on");
		case 1:
			if (!ast_strlen_zero(word) && !strncasecmp(word,
"off", MIN(strlen(word), 3)))
				return ast_strdup("off");
		default:
			return NULL;
	}

	return NULL;
}

static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct
ast_cli_args *a)
{
	char *res = CLI_SUCCESS;

	switch (cmd) {
	case CLI_INIT:
		e->command = "console autoanswer";
		e->usage =
			"Usage: console 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
'jack.conf'.\n";
		return NULL;
	case CLI_GENERATE:
		return autoanswer_complete(a->line, a->word, a->pos, a->n);
	}

	if ((a->argc != 2) && (a->argc != 3))
		return CLI_SHOWUSAGE;

	ast_mutex_lock(&jacklock);
	if (a->argc == 2) {
		ast_cli(a->fd, "Auto answer is %s.\n", autoanswer ? "on" :
"off");
	} else {
		if (!strcasecmp(a->argv[2], "on"))
			autoanswer = -1;
		else if (!strcasecmp(a->argv[2], "off"))
			autoanswer = 0;
		else
			res = CLI_SHOWUSAGE;
	}
	ast_mutex_unlock(&jacklock);

	return res;
}

static char *console_answer(struct ast_cli_entry *e, int cmd, struct
ast_cli_args *a)
{
	char *res = CLI_SUCCESS;

	switch (cmd) {
	case CLI_INIT:
		e->command = "console answer";
		e->usage =
			"Usage: console answer\n"
			"       Answers an incoming call on the console
(JACK) channel.\n";

		return NULL;
	case CLI_GENERATE:
		return NULL; 
	}

	if (a->argc != 2)
		return CLI_SHOWUSAGE;

	ast_mutex_lock(&jacklock);

	if (!jack.owner) {
		ast_cli(a->fd, "No one is calling us\n");
		res = CLI_FAILURE;
	} else {
		hookstate = 1;
		grab_owner();
		if (jack.owner) {
			struct ast_frame f = { AST_FRAME_CONTROL,
AST_CONTROL_ANSWER };

			ast_queue_frame(jack.owner, &f);
			ast_channel_unlock(jack.owner);
		}
	}


	# if CHAN_JACK
	// TODO
	# else
	snd_pcm_prepare(jack.icard);
	snd_pcm_start(jack.icard);
	# endif
	

	ast_mutex_unlock(&jacklock);

	return res;
}

static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct
ast_cli_args *a)
{
	int tmparg = 3;
	char *res = CLI_SUCCESS;

	switch (cmd) {
	case 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";
		return NULL;
	case CLI_GENERATE:
		return NULL; 
	}

	if (a->argc < 3)
		return CLI_SHOWUSAGE;

	ast_mutex_lock(&jacklock);

	if (!jack.owner) {
		ast_cli(a->fd, "No channel active\n");
		res = CLI_FAILURE;
	} else {
		struct ast_frame f = { AST_FRAME_TEXT, 0 };
		char text2send[256] = "";

		while (tmparg < a->argc) {
			strncat(text2send, a->argv[tmparg++],
sizeof(text2send) - strlen(text2send) - 1);
			strncat(text2send, " ", sizeof(text2send) -
strlen(text2send) - 1);
		}

		text2send[strlen(text2send) - 1] = '\n';
		f.data.ptr = text2send;
		f.datalen = strlen(text2send) + 1;
		grab_owner();
		if (jack.owner) {
			ast_queue_frame(jack.owner, &f);
			f.frametype = AST_FRAME_CONTROL;
			f.subclass = AST_CONTROL_ANSWER;
			f.data.ptr = NULL;
			f.datalen = 0;
			ast_queue_frame(jack.owner, &f);
			ast_channel_unlock(jack.owner);
		}
	}

	ast_mutex_unlock(&jacklock);

	return res;
}

static char *console_hangup(struct ast_cli_entry *e, int cmd, struct
ast_cli_args *a)
{
	char *res = CLI_SUCCESS;

	switch (cmd) {
	case CLI_INIT:
		e->command = "console hangup";
		e->usage =
			"Usage: console hangup\n"
			"       Hangs up any call currently placed on the
console.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL; 
	}
 

	if (a->argc != 2)
		return CLI_SHOWUSAGE;

	ast_mutex_lock(&jacklock);

	if (!jack.owner && !hookstate) {
		ast_cli(a->fd, "No call to hangup\n");
		res = CLI_FAILURE;
	} else {
		hookstate = 0;
		grab_owner();
		if (jack.owner) {
			ast_queue_hangup_with_cause(jack.owner,
AST_CAUSE_NORMAL_CLEARING);
			ast_channel_unlock(jack.owner);
		}
	}

	ast_mutex_unlock(&jacklock);

	return res;
}

static char *console_dial(struct ast_cli_entry *e, int cmd, struct
ast_cli_args *a)
{
	char tmp[256], *tmp2;
	char *mye, *myc;
	char *d;
	char *res = CLI_SUCCESS;

	switch (cmd) {
	case CLI_INIT:
		e->command = "console dial";
		e->usage =
			"Usage: console dial [extension[@context]]\n"
			"       Dials a given extension (and context if
specified)\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if ((a->argc != 2) && (a->argc != 3))
		return CLI_SHOWUSAGE;

	ast_mutex_lock(&jacklock);

	if (jack.owner) {
		if (a->argc == 3) {
			if (jack.owner) {
				for (d = a->argv[2]; *d; d++) {
					struct ast_frame f = { .frametype =
AST_FRAME_DTMF, .subclass = *d };

					ast_queue_frame(jack.owner, &f);
				}
			}
		} else {
			ast_cli(a->fd, "You're already in a call.  You can
use this only to dial digits until you hangup\n");
			res = CLI_FAILURE;
		}
	} else {
		mye = exten;
		myc = context;
		if (a->argc == 3) {
			char *stringp = NULL;

			ast_copy_string(tmp, a->argv[2], sizeof(tmp));
			stringp = tmp;
			strsep(&stringp, "@");
			tmp2 = strsep(&stringp, "@");
			if (!ast_strlen_zero(tmp))
				mye = tmp;
			if (!ast_strlen_zero(tmp2))
				myc = tmp2;
		}
		if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
			ast_copy_string(jack.exten, mye,
sizeof(jack.exten));
			ast_copy_string(jack.context, myc,
sizeof(jack.context));
			hookstate = 1;
			jack_new(&jack, AST_STATE_RINGING);
		} else
			ast_cli(a->fd, "No such extension '%s' in context
'%s'\n", mye, myc);
	}

	ast_mutex_unlock(&jacklock);

	return res;
}

static struct ast_cli_entry cli_jack[] = {
	AST_CLI_DEFINE(console_answer, "Answer an incoming console call"),
	AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"),
	AST_CLI_DEFINE(console_dial, "Dial an extension on the console"),
	AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"),
	AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"),
};

static int load_module(void)
{
	struct ast_config *cfg;
	struct ast_variable *v;
	struct ast_flags config_flags = { 0 };

	/* Copy the default jb config over global_jbconf */
	memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));

	strcpy(mohinterpret, "default");

	if (!(cfg = ast_config_load(config, config_flags))) {
		ast_log(LOG_ERROR, "Unable to read JACK configuration file
%s.  Aborting.\n", config);
		return AST_MODULE_LOAD_DECLINE;
	}
	
	if (!(jack_data = jack_data_alloc()))
	{
		ast_verb(2, "jack_data_alloc() failed\n");
		return AST_MODULE_LOAD_FAILURE;
	}	
	
	if (readdev == -1)
	{
		// TODO
		//readdev = open("/dev/null", O_RDWR); // fake fd...
	}

	v = ast_variable_browse(cfg, "general");
	for (; v; v = v->next) {
		/* handle jb conf */
		if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
				continue;
		
		if (!strcasecmp(v->name, "autoanswer"))
			autoanswer = ast_true(v->value);
		else if (!strcasecmp(v->name, "silencesuppression"))
			silencesuppression = ast_true(v->value);
		else if (!strcasecmp(v->name, "silencethreshold"))
			silencethreshold = atoi(v->value);
		else if (!strcasecmp(v->name, "context"))
			ast_copy_string(context, v->value, sizeof(context));
		else if (!strcasecmp(v->name, "language"))
			ast_copy_string(language, v->value,
sizeof(language));
		else if (!strcasecmp(v->name, "extension"))
			ast_copy_string(exten, v->value, sizeof(exten));
		else if (!strcasecmp(v->name, "input_device"))
			ast_copy_string(indevname, v->value,
sizeof(indevname));
		else if (!strcasecmp(v->name, "output_device"))
			ast_copy_string(outdevname, v->value,
sizeof(outdevname));
		else if (!strcasecmp(v->name, "mohinterpret"))
			ast_copy_string(mohinterpret, v->value,
sizeof(mohinterpret));
			
			
		else if (!strcasecmp(v->name, "server_name"))    
			ast_copy_string(jack_data->server_name, v->value,
128);
		else if (!strcasecmp(v->name, "client_name"))
			ast_copy_string(jack_data->client_name, v->value,
128);
		else if (!strcasecmp(v->name, "connect_input_port"))
			ast_copy_string(jack_data->connect_input_port,
v->value, 128);
		else if (!strcasecmp(v->name, "connect_output_port"))
			ast_copy_string(jack_data->connect_output_port,
v->value, 128);
	}
	ast_config_destroy(cfg);
	
	if (jack_init() < 0) {
		ast_verb(2, "No sound card detected -- console channel will
be unavailable\n");
		ast_verb(2, "Turn off JACK support by adding
'noload=chan_jack.so' in /etc/asterisk/modules.conf\n");
		jack_data = destroy_jack_data();
		return AST_MODULE_LOAD_DECLINE;
	}
	

	if (ast_channel_register(&jack_tech)) {
		ast_log(LOG_ERROR, "Unable to register channel class
'Console'\n");
		jack_data = destroy_jack_data();
		return AST_MODULE_LOAD_FAILURE;
	}

	ast_cli_register_multiple(cli_jack, sizeof(cli_jack) / sizeof(struct
ast_cli_entry));
	
	
	ast_log(LOG_ERROR, "load_module() OK\n");

	return AST_MODULE_LOAD_SUCCESS;
}

static int unload_module(void)
{
	ast_channel_unregister(&jack_tech);
	ast_cli_unregister_multiple(cli_jack, sizeof(cli_jack) /
sizeof(struct ast_cli_entry));


	jack_data = destroy_jack_data();

	if (jack.owner)
		ast_softhangup(jack.owner, AST_SOFTHANGUP_APPUNLOAD);
		
	if (readdev != -1)
	{
		close(readdev);
		readdev = -1;
	}
		
	if (jack.owner)
		return -1;

	return 0;
}

AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "JACK Console Channel Driver");




More information about the asterisk-dev mailing list