diff --git a/AUTHORS b/AUTHORS index 59e362d..65fcd22 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,8 @@ Lars Volkhardt (save callerid list to file) Mario Andrés Pagella (project logo) +Ivan Schreter (CAPI 2.0 support, ALSA support, low-latency) + Translators: FACORAT Fabrice (French) Roel Koelewijn (Dutch) diff --git a/README b/README index 764708e..0827957 100644 --- a/README +++ b/README @@ -6,20 +6,20 @@ Copyright 2002, 2003, 2004, 2005 Roland Stigge WHAT IT IS ========== -ANT is a telephone application written for GNU/Linux, I4L (ISDN4Linux, -integrated in the kernel), GTK+ (GIMP Toolkit) and OSS (Open Sound System). +ANT is a telephone application written for GNU/Linux, CAPI 2.0 (ISDN +interface), GTK+ (GIMP Toolkit) and ALSA (sound system). ABOUT ANT ========= -It directly interfaces OSS and ISDN devices, so there is no need to install +It directly interfaces ALSA and CAPI devices, so there is no need to install extra software or hardware like PBX (Private Branch Exchange) or telephony -cards, if you've got direct access to an audio capable ISDN Card and full +cards, if you've got direct access to an audio capable ISDN card and full duplex (or multiple) soundcard. For the soundcard interface you can use headphones (or speakers) and a -microphone, or a headset instead of all this. +microphone, or a headset. Currently, there is no direct mixer integration planned for ANT for several reasons. For GNU/Linux, there are quite a lot of good mixer programs available @@ -36,16 +36,16 @@ WHAT YOU WILL NEED to run: * a computer -* an ISDN card with audio support for Linux (ISDN4Linux driver) - (teles or HiSax drivers are known to work) +* an ISDN card with CAPI 2.0 telephony support for Linux * GNU/Linux (as far as nobody has tried it on another platform yet) -* OSS support * a full duplex sound card or two sound devices (one for input, another for output) * a microphone (or your sound source of choice) * speakers, or better: headphones * instead of the last two points, you could also use a headset * GTK+ 2.x +* libcapi20 (CAPI 2.0 support) +* libasound (ALSA support) * libsndfile to compile: @@ -53,8 +53,9 @@ to compile: * GNU make * GCC * GTK+ 2.x development files -* OSS development files (actually, it's mostly just one header file) -* libsndfile development files +* libcapi20 development files (CAPI 2.0 support) +* libasound development files (ALSA support) +* libsndfile development files (for recording) WHAT YOU MIGHT NEED @@ -76,7 +77,7 @@ FEATURES * Setting outgoing (identifying) MSN (Multiple Subscriber Number) and MSNs to listen on * Line Level Checker -* Works with ALSA (OSS emulation) +* Works with ALSA * Saved config file * Saved Caller ID history * Option to run an external command on incoming call (useful for external diff --git a/TODO b/TODO index 1848b4a..ceea00a 100644 --- a/TODO +++ b/TODO @@ -1,17 +1,22 @@ Bugs: ===== -* Recording chopped up (only recorded file) -* Incoming delay -* Kernel cpu consumption in conversation mode. This is a kernel-internal - problem. While reading blocks from kernel OSS devices and ttyI, select(s) - seems to consume lot's of cpu time in kernel space while NOT BLOCKING! - -> not a big problem for now, just less idle tasks ;) - -> The problem doesn't seem to appear with ALSA +* Due to unsynchronized ISDN and ALSA clocks, there are still some issues with + audio overruns/unterruns, which may cause slight distortions in the sound + quality. This can only by addressed by stretching/contracting sound segments. + Currently it is handled by duplicating small frames (~20ms) on underruns or + skipping small frames (~20ms) on overruns. In some rare cases, however, + it happens frequently for up to a minute. Hanging up and redialing is one + option how to get rid of this problem. +* Audio device choosing is still done via textbox. With ALSA now, it should be + done via combobox, as device names can be queried. +* Surely some new ones after rewrite of large parts of the code... Feature requests: ================= * Real time support + => not really necessary anymore, but can be easily implemented now * client/server architecture (ttyI network forward) (Sven Geggus , Arne Börs , martin@stigge.org) + => can be now handled by remote CAPI, no need for special code * Makeln (Joerg Brueggemann ) * database connection (caller id, times of incoming / outgoing, ...) @@ -51,6 +56,7 @@ Daniel N Philipp Thomas : * CAPI support, native ALSA support (both in Linux 2.6) (needed for SuSE 9.1 prepared for 02/2004) + => both done wolfgang@rohdewald.de: * "execute on recorded message" option diff --git a/configure.ac b/configure.ac index 38cf921..6b2bff4 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # Process this file with autoconf to produce a configure script. AC_INIT(ant-phone) -AM_INIT_AUTOMAKE(ant-phone, "0.1.13", ant-phone-devel@nongnu.org) +AM_INIT_AUTOMAKE(ant-phone, "0.2.0", ant-phone-devel@nongnu.org) AC_PREREQ(2.53) AC_CONFIG_SRCDIR([config.h.in]) AM_CONFIG_HEADER([config.h]) @@ -53,7 +53,7 @@ AC_SUBST(DEPS_LIBS) # AC_MSG_CHECKING(to see if we can add '-Wall -W' to CFLAGS) if test x$GCC != x ; then - CFLAGS="$CFLAGS -D_U_=\"__attribute__((unused))\" -Wall -W -D_GNU_SOURCE -O3" + CFLAGS="$CFLAGS -D_U_=\"__attribute__((unused))\" -Wall -W -D_GNU_SOURCE" AC_MSG_RESULT(yes) else CFLAGS="-D_U_=\"\" $CFLAGS" diff --git a/src/Makefile.am b/src/Makefile.am index 74ddbf5..b3d8af3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,9 @@ ant_phone_SOURCES = \ server.c \ client.c \ recording.c \ - isdntree.c + isdntree.c \ + thread.c \ + globals.c noinst_HEADERS = \ callerid.h \ @@ -49,7 +51,8 @@ noinst_HEADERS = \ globals.h \ gettext.h \ isdnlexer.h \ - isdntree.h + isdntree.h \ + thread.h EXTRA_DIST = \ pickup.xpm \ @@ -73,7 +76,7 @@ datadir = @datadir@ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ -LIBS = @LIBINTL@ @LIBS@ +LIBS = @LIBINTL@ @LIBS@ -lgthread-2.0 -lasound -lcapi20 INCLUDES = -I../intl -I$(top_srcdir)/intl @DEPS_CFLAGS@ AM_CFLAGS = -DVERSION='"@VERSION@"' -DPACKAGE='"@PACKAGE@"' @CFLAGS@ diff --git a/src/ant-phone.c b/src/ant-phone.c index 809f6a7..b70cdcd 100644 --- a/src/ant-phone.c +++ b/src/ant-phone.c @@ -102,9 +102,11 @@ int main(int argc, char *argv[]) { {"msn", required_argument, 0, 'm'}, {"msns", required_argument, 0, 'l'}, {"call", required_argument, 0, 'c'}, + {"sleep", no_argument, 0, 's'}, + {"wakeup", no_argument, 0, 'w'}, {0, 0, 0, 0} }; - char *short_options = "hvrd::i:o:m:l:c:"; + char *short_options = "hvrswd::i:o:m:l:c:"; int option_index = 0; int c; @@ -126,11 +128,11 @@ int main(int argc, char *argv[]) { #ifdef ENABLE_NLS setlocale(LC_ALL, ""); if (!bindtextdomain(PACKAGE, LOCALEDIR)) { - fprintf(stderr, "Error setting directory for textdomain (i18n).\n"); + errprintf("Error setting directory for textdomain (i18n).\n"); } output_codeset_save(); if (!textdomain(PACKAGE)) { - fprintf(stderr, "Error setting domainname for gettext() " + errprintf("Error setting domainname for gettext() " "(internationalization).\n"); } #endif @@ -186,6 +188,9 @@ Options:\n\ -l, --msns=MSNS MSNs to listen on, semicolon-separated list or '*'\n\ default: *\n\ -c, --call=NUMBER Call specified number\n\ + -s, --sleep Put ISDN thread to sleep (to be able to remove CAPI\n\ + modules before suspending the computer).\n\ + -w, --wakeup Restart ISDN thread after sleep.\n\ \n\ Note: If arguments of --soundin and --soundout are equal, a full duplex\n\ sound device is needed.\n"), argv[0]); @@ -219,7 +224,25 @@ Note: If arguments of --soundin and --soundout are equal, a full duplex\n\ break; case 'c': printf(_("Calling %s... "), optarg); - if (client_make_call(optarg)) { + if (client_make_call(LOCAL_MSG_CALL, optarg)) { + printf("\nAn error occured while calling a running " PACKAGE ".\n"); + } else { + printf(_("successful.\n")); + } + exit(0); + break; + case 's': + printf(_("Suspending ISDN thread... ")); + if (client_make_call(LOCAL_MSG_SUSPEND, "")) { + printf("\nAn error occured while calling a running " PACKAGE ".\n"); + } else { + printf(_("successful.\n")); + } + exit(0); + break; + case 'w': + printf(_("Waking up ISDN thread... ")); + if (client_make_call(LOCAL_MSG_WAKEUP, "")) { printf("\nAn error occured while calling a running " PACKAGE ".\n"); } else { printf(_("successful.\n")); @@ -237,22 +260,20 @@ Note: If arguments of --soundin and --soundout are equal, a full duplex\n\ if (session_init(&session, audio_device_name_in, audio_device_name_out, msn, msns)) { - fprintf(stderr, "Error at session init.\n"); + errprintf("Error at session init.\n"); exit(1); } else { - if (debug) - fprintf(stderr, "Init OK.\n"); + dbgprintf(1, "Init OK.\n"); } - + /* gtk stuff, main loop */ gtk_result = main_gtk(&session); if (session_deinit(&session)) { - fprintf(stderr, "Error at session exit\n"); + errprintf("Error at session exit\n"); exit(1); } else { - if (debug) - fprintf(stderr, "Quit OK.\n"); + dbgprintf(1, "Quit OK.\n"); } output_codeset_set(NULL); /* restore saved codeset */ diff --git a/src/callerid.c b/src/callerid.c index 87aa6d4..34e6a12 100644 --- a/src/callerid.c +++ b/src/callerid.c @@ -69,7 +69,7 @@ static gpointer cid_row_new(void) { */ static void cid_row_destroy(gpointer rowdata) { if (debug > 1) - fprintf(stderr, "debug: destroying rowdata at %p.\n", rowdata); + errprintf("debug: destroying rowdata at %p.\n", rowdata); free(rowdata); } @@ -192,6 +192,25 @@ static void cid_playback(GtkWidget *widget _U_, gpointer data, guint row) { session_set_state(session, STATE_PLAYBACK); } +/* + * Callback: called on call request + */ +static void cid_call(GtkWidget *widget _U_, gpointer data, guint row) { + session_t *session = (session_t *) data; + + char *typestr; + char *numberstr; + + gtk_clist_get_pixtext(GTK_CLIST(session->cid_list), row, + CID_COL_TYPE, &typestr, NULL, NULL, NULL); + + gtk_clist_get_text(GTK_CLIST(session->cid_list), row, + strcmp(typestr, "IN") == 0 ? + CID_COL_FROM : CID_COL_TO, &numberstr); + + gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box)->entry), numberstr); +} + /* * Callback: called on "OK" click in "Save as..." file selection */ @@ -225,26 +244,24 @@ static void cid_save_as_filename_cb(GtkWidget *fs) { } } while (just_read); if (close(fd_out)) { - fprintf(stderr, "Error on closing destination file.\n"); + errprintf("Error on closing destination file.\n"); } } else { - fprintf(stderr, - "Error on destination file (%s) open().\n", destination); + errprintf("Error on destination file (%s) open().\n", destination); } if (close(fd_in)) { - fprintf(stderr, "Error on source file.\n"); + errprintf("Error on source file.\n"); } } else { - fprintf(stderr, - "Error on source file (%s) open().\n", sourcename); + errprintf("Error on source file (%s) open().\n", sourcename); } free(buffer); } else { - fprintf(stderr, "Error on malloc().\n"); + errprintf("Error on malloc().\n"); } free(destination); } else { - fprintf(stderr, "Error on asprintf().\n"); + errprintf("Error on asprintf().\n"); } } free(sourcename); @@ -266,7 +283,7 @@ static void cid_save_as(GtkWidget *widget _U_, gpointer data, guint row) { if (0 > asprintf(&title, _("Enter the base filename for %s file"), extension)) { - fprintf(stderr, "Error on asprintf().\n"); + errprintf("Error on asprintf().\n"); return; } fs = gtk_file_selection_new(title); @@ -279,7 +296,7 @@ static void cid_save_as(GtkWidget *widget _U_, gpointer data, guint row) { "clicked", G_CALLBACK(cid_save_as_filename_cb), fs); gtk_widget_show(fs); } else { - fprintf(stderr, "Error: no filename extension found.\n"); + errprintf("Error: no filename extension found.\n"); } } @@ -333,6 +350,7 @@ static gint cid_mouse_cb(GtkWidget *widget _U_, GtkItemFactoryEntry menu_items[] = { /*path accel. callb. cb param. kind extra */ +{_("/_Call"), NULL, cid_call, row, "", NULL}, {_("/_Playback"), NULL, cid_playback, row, "", NULL}, {_("/_Save as..."), NULL, cid_save_as, row, "", NULL}, {_("/Delete _Recording"),NULL, cid_delete_rec,row, "", NULL}, @@ -345,6 +363,7 @@ static gint cid_mouse_cb(GtkWidget *widget _U_, gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); + GtkWidget *call_item; GtkWidget *playback_item; GtkWidget *save_as_item; GtkWidget *delete_record_item; @@ -358,21 +377,25 @@ static gint cid_mouse_cb(GtkWidget *widget _U_, gtk_item_factory_create_items_ac(item_factory, nmenu_items, menu_items, session, 2); + if (!(call_item = gtk_item_factory_get_item(item_factory, + temp = stripchr(_("/_Call"), '_')))) + errprintf("Error getting call_item.\n"); + free(temp); if (!(playback_item = gtk_item_factory_get_item(item_factory, temp = stripchr(_("/_Playback"), '_')))) - fprintf(stderr, "Error getting playback_item.\n"); + errprintf("Error getting playback_item.\n"); free(temp); if (!(save_as_item = gtk_item_factory_get_item(item_factory, temp = stripchr(_("/_Save as..."), '_')))) - fprintf(stderr, "Error getting save_as_item.\n"); + errprintf("Error getting save_as_item.\n"); free(temp); if (!(delete_record_item = gtk_item_factory_get_item(item_factory, temp = stripchr(_("/Delete _Recording"), '_')))) - fprintf(stderr, "Error getting delete_record_item_item.\n"); + errprintf("Error getting delete_record_item_item.\n"); free(temp); if (!(delete_item = gtk_item_factory_get_item(item_factory, temp = stripchr(_("/_Delete Row"), '_')))) - fprintf(stderr, "Error getting delete_item.\n"); + errprintf("Error getting delete_item.\n"); free(temp); if (!(session->state == STATE_READY && @@ -383,6 +406,9 @@ static gint cid_mouse_cb(GtkWidget *widget _U_, } else { free(fn); } + if (session->state != STATE_READY) { + gtk_widget_set_sensitive(call_item, FALSE); + } menu = GTK_MENU(gtk_item_factory_get_widget(item_factory, "")); gtk_menu_set_accel_group(menu, accel_group); @@ -528,7 +554,7 @@ static char *cid_timestring(time_t t) { date[0] = '\1'; len = strftime(date, 20, "%Y-%m-%d %H:%M:%S", localtime(&t)); if (len == 0 && date[0] != '\0') { - fprintf(stderr, "cid: Error calculating time with strftime.\n"); + errprintf("cid: Error calculating time with strftime.\n"); return NULL; } @@ -635,7 +661,7 @@ void cid_set_duration(session_t *session, gchar *message) { void cid_add_saved_line(session_t *session, char *date, char *type, char *from, char *to, char *duration) { if (debug > 1) - fprintf(stderr, "Caller ID add:\n" + errprintf("Caller ID add:\n" "Date: |%s|, Type: |%s|, From: |%s|, To: |%s|, Dur: |%s|\n", date, type, from, to, duration); @@ -683,13 +709,11 @@ void cid_calls_merge(session_t *session) { /* try to find isdnlog data file */ calls_filename = isdn_get_calls_filename(); if (calls_filename && (f = fopen(calls_filename, "r"))) { - if (debug) { - fprintf(stderr, "Using %s as source for isdnlog data.\n", calls_filename); - } + dbgprintf(1, "Using %s as source for isdnlog data.\n", calls_filename); if (session->option_calls_merge_max_days) { /* binary search on the file for the desired starting time if needed */ if (debug >= 3) { - fprintf(stderr, "Binary search in calls file...\n"); + errprintf("Binary search in calls file...\n"); } low = 0; fseek(f, 0, SEEK_END); @@ -697,7 +721,7 @@ void cid_calls_merge(session_t *session) { while (high - low > 200) { if (debug >= 3) { - fprintf(stderr, "low = %d, high = %d\n", low, high); + errprintf("low = %d, high = %d\n", low, high); } mid = (low + high) / 2; fseek(f, mid, SEEK_SET); @@ -726,7 +750,7 @@ void cid_calls_merge(session_t *session) { if (sscanf(line, "%*40[^|]|%40[^|]|%40[^|]|%d|%*40[^|]|%d|%*40[^|]|%1c", from, to, &duration, &date, type) != 5) { - fprintf(stderr, "Warning: Incomplete data input from calls file.\n"); + errprintf("Warning: Incomplete data input from calls file.\n"); break; } if ((temp = strchr(from, ' '))) *temp = '\0'; @@ -791,9 +815,9 @@ void cid_calls_merge(session_t *session) { cid_normalize(session); if (fclose(f)) - fprintf(stderr, "Error closing %s.\n", calls_filename); + errprintf("Error closing %s.\n", calls_filename); } else { /* error on fopen() */ - fprintf(stderr, + errprintf( "Warning: Couldn't open isdnlog calls logfile. Proceeding without it.\n"); } free(line); @@ -876,13 +900,13 @@ char* cid_get_record_filename(session_t* session, int row) { timestr = cid_purify_timestring(timestr); if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return NULL; } if (asprintf(&pattern, "%s/." PACKAGE "/recordings/%s.*", homedir, timestr) < 0) { - fprintf(stderr, "Warning: " + errprintf("Warning: " "Couldn't allocate memory for filename globbing pattern.\n"); return NULL; } @@ -895,7 +919,7 @@ char* cid_get_record_filename(session_t* session, int row) { result = NULL; break; default: - fprintf(stderr, "Warning: " + errprintf("Warning: " "globbing error while looking up recorded conversation.\n"); return NULL; } diff --git a/src/calleridparser.y b/src/calleridparser.y index cf3c2d9..36d7727 100644 --- a/src/calleridparser.y +++ b/src/calleridparser.y @@ -64,7 +64,7 @@ line : '\n' free($1); free($3); free($5); free($7); free($9); } | error '\n' { if (debug) - fprintf(stderr, + errprintf( "Warning: Parsing callerid history file:%d, " "recovering after error.\n", @1.last_line); } @@ -79,7 +79,7 @@ line : '\n' */ void callerid_error(const char *message) { if (debug) - fprintf(stderr, + errprintf( "Warning: Parsing callerid history file line %d: %s.\n", callerid_lloc.first_line, message); } diff --git a/src/client.c b/src/client.c index 3eb6b15..b49a682 100644 --- a/src/client.c +++ b/src/client.c @@ -42,7 +42,7 @@ * * returns 0 on success, -1 otherwise */ -int client_make_call(char *number) { +int client_make_call(char message, char *number) { int sock; struct sockaddr_un local_name; size_t size; @@ -67,8 +67,8 @@ int client_make_call(char *number) { return -1; } - if (asprintf(&msg, "%c%s", LOCAL_MSG_CALL, number) < 0) { - fprintf(stderr, "asprintf error"); + if (asprintf(&msg, "%c%s", message, number) < 0) { + errprintf("asprintf error"); return -1; } bytes = write(sock, msg, 1 + strlen(number) + 1); diff --git a/src/client.h b/src/client.h index edf9dee..afda113 100644 --- a/src/client.h +++ b/src/client.h @@ -22,4 +22,4 @@ * */ -int client_make_call(char *number); +int client_make_call(char message, char *number); diff --git a/src/controlpad.c b/src/controlpad.c index 2f1d650..00fd84b 100644 --- a/src/controlpad.c +++ b/src/controlpad.c @@ -252,34 +252,19 @@ static void controlpad_mute_cb(GtkWidget *button, gpointer data) { */ static void controlpad_record_cb(GtkWidget *button, gpointer data) { session_t *session = (session_t *) data; - char *digits = NULL; if (button == session->record_checkbutton) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) { /* record! */ - session->option_record = 1; if (session->state == STATE_CONVERSATION) { - if (recording_open(session->recorder, - digits = util_digitstime(&session->vcon_time), - session->option_recording_format)) - { - fprintf(stderr, "Error opening audio file.\n"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( - session->record_checkbutton), FALSE); - return; - } - free(digits); - cid_row_mark_record(session, session->cid_num - 1); + if (session_start_recording(session) == 0) { + session->option_record = 1; + cid_row_mark_record(session, session->cid_num - 1); + } } } else { /* don't record! */ session->option_record = 0; if (session->state == STATE_CONVERSATION) { - recording_write(session->recorder, session->rec_buf_local, - session->rec_buf_local_index, RECORDING_LOCAL); - recording_write(session->recorder, session->rec_buf_remote, - session->rec_buf_remote_index, RECORDING_REMOTE); recording_close(session->recorder); - session->rec_buf_local_index = 0; - session->rec_buf_remote_index = 0; } } gtk_widget_set_sensitive(session->record_checkbutton_local, diff --git a/src/fxgenerator.c b/src/fxgenerator.c index 65cd68b..836f88e 100644 --- a/src/fxgenerator.c +++ b/src/fxgenerator.c @@ -136,8 +136,11 @@ unsigned char fxgenerate(session_t *session, enum effect_t effect, (int)((sin(seconds * 2 * M_PI * f1) + sin(seconds * 2 * M_PI * f2)) / 2 * 127.5 * 0.7 + 127.5)]; break; + case EFFECT_EMPTY: + x = session->audio_LUT_generate[128]; + break; default: - fprintf(stderr, "fxgenerate: Unknown effect.\n"); + errprintf("fxgenerate: Unknown effect %d.\n", effect); x = 0; } diff --git a/src/globals.h b/src/globals.h index 6f04395..6d43ad1 100644 --- a/src/globals.h +++ b/src/globals.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,4 +35,19 @@ extern int debug; +/*! + * @brief Output a message. + * + * @param level message level (0=error, 1..n=debug). + * @param format printf-like format for following arguments. + */ +void msgprintf(int level, const char *format, ...) + __attribute__ ((format (printf, 2, 3))); + +#define dbgprintf(level, ...) \ + if (level <= debug) msgprintf(level, __VA_ARGS__) + +#define errprintf(...) \ + msgprintf(0, __VA_ARGS__) + #endif /* globals.h */ diff --git a/src/gtk.c b/src/gtk.c index df2eee3..5fa7b06 100644 --- a/src/gtk.c +++ b/src/gtk.c @@ -82,6 +82,7 @@ static void quit(GtkWidget *widget _U_, gpointer data, guint action _U_) { settings_callerid_write(session); /* write callerid history */ gtk_handle_hang_up_button(NULL, data); /* simulate hang_up_button */ + session_io_handlers_stop(session); /* make sure GTK handlers are stopped */ /* some (GUI) de-initialization, not directly session related */ llcheck_bar_deinit(session->llcheck_in); @@ -117,15 +118,13 @@ gint timeout_callback(gpointer data) { if (interrupted) { switch(interrupted) { case SIGINT: - if (debug) - fprintf(stderr, "Ctrl-C caught.\n"); + dbgprintf(1, "Ctrl-C caught.\n"); break; case SIGTERM: - if (debug) - fprintf(stderr, "SIGTERM caught.\n"); + dbgprintf(1, "SIGTERM caught.\n"); break; default: - fprintf(stderr, "Warning: Unknown signal caught.\n"); + errprintf("Warning: Unknown signal caught.\n"); } quit(NULL, data, 0); } @@ -136,7 +135,7 @@ gint timeout_callback(gpointer data) { if (0 > asprintf(&buf, "%s %s", state_data[session->state].status_bar, timediff)) - fprintf(stderr, "Warning: timeout_callback: asprintf error.\n"); + errprintf("Warning: timeout_callback: asprintf error.\n"); gtk_statusbar_pop(GTK_STATUSBAR(session->status_bar), session->phone_context_id); @@ -154,7 +153,7 @@ gint timeout_callback(gpointer data) { if (0 > asprintf(&buf, "%s %s", state_data[session->state].status_bar, timediff)) - fprintf(stderr, "Warning: timeout_callback: asprintf error.\n"); + errprintf("Warning: timeout_callback: asprintf error.\n"); gtk_statusbar_pop(GTK_STATUSBAR(session->status_bar), session->phone_context_id); @@ -277,10 +276,10 @@ static void cb_info_window(GtkWidget *widget _U_, gpointer data, { N_("Output channels:"), inactive ? strdup(_("[inactive]")) : ltostr(1) }, { "", strdup("") }, - { N_("ISDN device:"), strdup(session->isdn_device_name) }, +/* { N_("ISDN device:"), strdup(session->isdn_device_name) }, */ { N_("ISDN speed (samples):"), ltostr(8000) }, { N_("ISDN sample size (bits):"), ltostr(8) }, - { N_("ISDN fragment size (bytes):"), ltostr(255) } +/* { N_("ISDN fragment size (bytes):"), ltostr(255) } */ }; unsigned int i; diff --git a/src/gtksettings.c b/src/gtksettings.c index 930d30a..02ff99d 100644 --- a/src/gtksettings.c +++ b/src/gtksettings.c @@ -54,7 +54,7 @@ static int gtksettings_try(GtkWidget *widget) { free(session->exec_on_incoming); session->exec_on_incoming = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); } else - fprintf(stderr, "gtksettings_cb_ok: Error getting exec_on_incoming.\n"); + errprintf("gtksettings_cb_ok: Error getting exec_on_incoming.\n"); /* popup checkbutton */ button = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), @@ -63,7 +63,7 @@ static int gtksettings_try(GtkWidget *widget) { session->option_popup = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); else - fprintf(stderr, "gtksettings_cb_ok: Error getting popup state.\n"); + errprintf("gtksettings_cb_ok: Error getting popup state.\n"); /* recording_format */ list = (GSList *) gtk_object_get_data(GTK_OBJECT(widget), "recording_format"); @@ -76,21 +76,21 @@ static int gtksettings_try(GtkWidget *widget) { list = list->next; } } else - fprintf(stderr, "gtksettings_cb_ok: Error getting recording_format.\n"); + errprintf("gtksettings_cb_ok: Error getting recording_format.\n"); /* msn */ entry = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), "msn_entry"); if (entry) session->msn = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); else - fprintf(stderr, "gtksettings_cb_ok: Error getting msn.\n"); + errprintf("gtksettings_cb_ok: Error getting msn.\n"); /* msns */ entry = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), "msns_entry"); if (entry) session->msns = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); else - fprintf(stderr, "gtksettings_cb_ok: Error getting msns.\n"); + errprintf("gtksettings_cb_ok: Error getting msns.\n"); /* history_entry */ entry = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), @@ -99,7 +99,7 @@ static int gtksettings_try(GtkWidget *widget) { session->dial_number_history_maxlen = strtol(gtk_entry_get_text(GTK_ENTRY(entry)), NULL, 0); else - fprintf(stderr, "gtksettings_cb_ok: Error getting history.\n"); + errprintf("gtksettings_cb_ok: Error getting history.\n"); session->dial_number_history_pointer = 0; /* cid_max_entry */ @@ -109,7 +109,7 @@ static int gtksettings_try(GtkWidget *widget) { session->cid_num_max = strtol(gtk_entry_get_text(GTK_ENTRY(entry)), NULL, 0); else - fprintf(stderr, "gtksettings_cb_ok: " + errprintf("gtksettings_cb_ok: " "Error getting caller id maximum rows.\n"); /* cid_calls_merge_checkbutton */ @@ -119,7 +119,7 @@ static int gtksettings_try(GtkWidget *widget) { session->option_calls_merge = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); else - fprintf(stderr, + errprintf( "gtksettings_cb_ok: Error getting isdnlog calls_merge state.\n"); /* cid_calls_merge_max_entry */ entry = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), @@ -128,7 +128,7 @@ static int gtksettings_try(GtkWidget *widget) { session->option_calls_merge_max_days = strtol(gtk_entry_get_text(GTK_ENTRY(entry)), NULL, 0); else - fprintf(stderr, "gtksettings_cb_ok: " + errprintf("gtksettings_cb_ok: " "Error getting maximum number of days for isdnlog retrieval.\n"); /* save checkbutton */ @@ -138,7 +138,7 @@ static int gtksettings_try(GtkWidget *widget) { session->option_save_options = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); else - fprintf(stderr, + errprintf( "gtksettings_cb_ok: Error getting save_options state.\n"); /* @@ -147,8 +147,7 @@ static int gtksettings_try(GtkWidget *widget) { if (session->audio_device_name_in) { /* shut down if defined */ session_io_handlers_stop(session); - if (!session->option_release_devices) /* audio_close if normal mode */ - session_audio_deinit(session); + session_set_audio_state(session, AUDIO_DISCONNECTED); free(session->audio_device_name_in); free(session->audio_device_name_out); } @@ -160,7 +159,7 @@ static int gtksettings_try(GtkWidget *widget) { session->audio_device_name_in = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); else - fprintf(stderr,"gtksettings_cb_ok: Error getting audio_device_name_in.\n"); + errprintf("gtksettings_cb_ok: Error getting audio_device_name_in.\n"); /* audio_device_name_out */ entry = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(widget), @@ -169,7 +168,7 @@ static int gtksettings_try(GtkWidget *widget) { session->audio_device_name_out = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); else - fprintf(stderr, + errprintf( "gtksettings_cb_ok: Error getting audio_device_name_out.\n"); /* release checkbutton */ @@ -179,11 +178,11 @@ static int gtksettings_try(GtkWidget *widget) { session->option_release_devices = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); else - fprintf(stderr, + errprintf( "gtksettings_cb_ok: Error getting release_devices state.\n"); if (!session->option_release_devices) { - if (session_audio_init(session)) { + if (session_set_audio_state(session, AUDIO_IDLE) < 0) { successful = 0; free(session->audio_device_name_in); free(session->audio_device_name_out); @@ -196,21 +195,23 @@ static int gtksettings_try(GtkWidget *widget) { session_set_state(session, STATE_READY); /* update everything */ } - /* try to apply msn settings */ - if (isdn_setMSN(session->isdn_fd, session->msn) || - isdn_setMSNs(session->isdn_fd, session->msns)) { - /* got some problem */ - free(session->msn); - free(session->msns); - session->msn = old_msn; - session->msns = old_msns; - isdn_setMSN(session->isdn_fd, session->msn); - isdn_setMSNs(session->isdn_fd, session->msns); - successful = 0; - } else { - /* everything's fine */ - free(old_msn); - free(old_msns); + if (session->state == STATE_READY) { + /* try to apply msn settings */ + if (isdn_setMSN(&session->isdn, session->msn) || + isdn_setMSNs(&session->isdn, session->msns)) { + /* got some problem */ + free(session->msn); + free(session->msns); + session->msn = old_msn; + session->msns = old_msns; + isdn_setMSN(&session->isdn, session->msn); + isdn_setMSNs(&session->isdn, session->msns); + successful = 0; + } else { + /* everything's fine */ + free(old_msn); + free(old_msns); + } } return !!successful - 1; diff --git a/src/isdn.c b/src/isdn.c index 0cc933e..0cec283 100644 --- a/src/isdn.c +++ b/src/isdn.c @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,24 +27,24 @@ /* GNU headers */ #include #ifdef HAVE_STDLIB_H - #include +#include #endif #ifdef HAVE_UNISTD_H - #include -#endif -#ifdef HAVE_TERMIOS_H - #include +#include #endif #ifdef HAVE_FCNTL_H - #include -#endif -#ifdef HAVE_SYS_TIME_H - #include +#include #endif + +#include +#include + #include -#include #include +/* ISDN CAPI header */ +#include + /* own header files */ #include "globals.h" #include "isdn.h" @@ -52,503 +53,1171 @@ static char* calls_filenames[] = { "/var/lib/isdn/calls", "/var/log/isdn/calls", "/var/log/isdn.log" }; char* isdn_calls_filename_from_config = NULL; -/* - * locks (via lock file) and opens a (free) ttyI device - * - * output: - * isdn_device_name, isdn_lockfile_name - * - * returns isdn file descriptor on success, -1 otherwise - */ -int open_isdn_device(char **isdn_device_name, char **isdn_lockfile_name) { - int i = 0; /* try from 0 */ - int found = 0; /* free file name found */ - char buf[64]; - int fd; - int n; - int pid; - - while (i < 64 && !found) { - snprintf(buf, sizeof(buf), "%s/LCK..ttyI%d", LOCK_PATH, i); - *isdn_lockfile_name = strdup(buf); - if ((fd = open(*isdn_lockfile_name, O_RDONLY)) >= 0) { /* exists */ - /* stale? -> remove (for re-use) */ - n = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (n > 0) { - if (n == 4) { - if (sizeof(int) == 4) - pid = *(int*) buf; - else - pid = 0; - } else { - buf[n] = 0; - sscanf(buf, "%d", &pid); - } - if (pid > 0) { - if (kill((pid_t) pid, 0) < 0 && errno == ESRCH) { /* stale */ - if (!unlink(*isdn_lockfile_name)) - found = 1; /* file removed */ - } - } - } - } else { - if (errno == ENOENT) - found = 1; /* file doesn't exist */ - } - if (!found) i++; - } - - if (found && i < 64) { /* got a valid lock file name */ - /* lock device */ - if ((fd = open(*isdn_lockfile_name, O_WRONLY | O_CREAT, 0666)) < 0) { - return -1; - } - snprintf(buf, sizeof(buf), "%10ld\n", (long)getpid()); - write(fd, buf, strlen(buf)); - close(fd); - - /* finally name and open the device itself */ - snprintf(buf, sizeof(buf), "/dev/ttyI%d", i); - *isdn_device_name = strdup(buf); - - /* tty device would possibly block on open -> O_NONBLOCK */ - return open(*isdn_device_name, O_RDWR | O_NONBLOCK); - } else - return -1; -} +/*--------------------------------------------------------------------------*/ /* - * tries to read the given string from the specified tty (e.g. a ttyI) - * (waits for the string if not read immediately) + * initiate listen on ISDN device * - * input: - * fd tty file descriptor - * s (0-terminated) string to compare with - * timeout give up after this number of seconds - * 0: return immediately if no data available - * -1: no timeout - * got if got != NULL, store reference to result there - * - * output: - * got pointer to string containing buffer actually read if called - * with got != NULL - * -> will be NULL on error - * NOTE: caller is responsible to free the referenced buffer - * - * returns 0 on success, -1 otherwise - * - * NOTE: currently uses a fixed size buffer, implying a maximum number - * of bytes read + * isdn ISDN device structure + * controller controller number */ -int tty_read(int fd, char *s, int timeout, char **got) { - int failed = 0; /* 0 or 1*/ - int buf_last = 0; /* index of end of string ('\0') */ - char buf[256] = ""; - struct timeval tv, *tvp; - fd_set fds; - - if (timeout >= 0) { - tv.tv_sec = timeout; - tv.tv_usec = 0; - tvp = &tv; - } else - tvp = NULL; - - while (!strstr(buf, s) && !failed) { - FD_ZERO(&fds); - FD_SET(fd, &fds); +static int isdn_listen(isdn_t *isdn, unsigned int controller) +{ + _cmsg CMSG; /* structure for the message */ + unsigned int info; - if (buf_last == sizeof(buf) - 1) /* buffer full */ - failed = 1; - else { - if (select(FD_SETSIZE, &fds, 0, 0, tvp) == 1) { /* input ready */ - read(fd, &buf[buf_last], 1); /* read 1 more byte */ - buf[++buf_last] = 0; - } else { /* timeout or signal */ - failed = 1; - } - } - } - /* printf("%s\n", buf); */ - if (got != NULL) { - *got = strdup(buf); - } - return -failed; -} + dbgprintf(2, "CAPI 2.0: LISTEN_REQ ApplID %d msg %d ctrl %d infomsk 0x%x CIPmsk 0x%x\n", + isdn->appl_id, isdn->msg_no, controller, + isdn->info_mask, isdn->cip_mask); -/* - * writes specified 0-terminated string to the specified tty - * - * returns 0 on success, -1 otherwise - */ -int tty_write(int fd, char *s) { - if (write(fd, s, strlen(s)) != (int)strlen(s)) { + g_mutex_lock(isdn->data_lock); + info = LISTEN_REQ(&CMSG, isdn->appl_id, isdn->msg_no++, controller, + isdn->info_mask, isdn->cip_mask, 0, NULL, NULL); + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + errprintf("CAPI 2.0: LISTEN_REQ failed, RC=0x%x\n", info); return -1; } return 0; } -/* - * clears input and output queue of specified tty - * - * returns 0 on success, -1 otherwise - */ -int tty_clear(int fd) { - return tcflush(fd, TCIOFLUSH); -} +/*--------------------------------------------------------------------------*/ -/* - * ISDN initialization - * - * returns 0 on success, -1 otherwise +/*! + * @brief Set remote number on ISDN connection object. */ -int init_isdn_device(int isdn_fd, struct termios *backup) { - int failed = 0; - int flags; - char *(init_commands[]) = { - "AT&F\n", "OK\r\n", /* restore factory settings */ - "ATE0\n", "OK\r\n", /* echo off */ - /*"AT&B128\n", "OK\r\n",*/ /* set outgoing packet size */ - "ATI\n", "Linux ISDN\r\nOK\r\n", /* check for real isdn device */ - "AT+FCLASS=8\n", "OK\r\n", /* enable audio mode */ - "AT+VSM=6\n", "OK\r\n", /* set uLaw format */ - "ATS18=1\n", "OK\r\n", /* set audio mode (dial out) */ - "ATS14=4\n", "OK\r\n", /* layer-2 protocol = transparent */ - "ATS13.4=1\n", "OK\r\n", /* CALLER NUMBER after first RING */ - "ATS13.6=1\n", "OK\r\n", /* enable RUNG messages */ - "ATS23=1\n", "OK\r\n" /* Calling Party Number (CPN) extended RING */ - }; - struct termios settings; - unsigned int i; +static void isdn_set_remote_number(isdn_t *isdn, char *number) +{ + char *tmp, *tofree = isdn->remote_number; - /* assume: - * ttyI speed is 64000 by default - */ - - /* switch O_NONBLOCK off (turned on while opening) */ - flags = fcntl(isdn_fd, F_GETFL, 0); - if (flags != -1) { - if (fcntl(isdn_fd, F_SETFL, flags & ~O_NONBLOCK) == -1) { - perror("G_GETFL"); - return -1; + if (number) { + /* NOTE: number format: + * Byte 0: length of structure + * Byte 1: numbering plan + * Byte 2: presentation indicator (0x80 standard, 0xA0 for CLIR) + * Byte 3..n: number digits + */ + int len = number[0] - 2; + if (len <= 0) { + isdn->remote_number = 0; + } else { + tmp = (char*) malloc(len + 1); + memcpy(tmp, number + 3, len); + tmp[len] = 0; + isdn->remote_number = tmp; } } else { - perror("F_GETFL"); - return -1; + isdn->remote_number = 0; } - if (tcgetattr(isdn_fd, &settings)) - return -1; + if (tofree) + free(tofree); +} - memcpy(backup, &settings, sizeof(struct termios)); +/*--------------------------------------------------------------------------*/ - settings.c_lflag &= ~(ICANON | ECHO | ECHONL | ECHOCTL | ISIG); - settings.c_iflag &= ~(IXON | IXOFF | IXANY | IGNCR | ICRNL | INLCR ); - settings.c_iflag |= IGNPAR; - settings.c_cflag |= HUPCL; - settings.c_oflag &= ~ONLCR; +/*! + * @brief Set local number on ISDN connection object. + */ +static void isdn_set_local_number(isdn_t *isdn, char *number) +{ + char *tmp, *tofree = isdn->local_number; - settings.c_cc[VMIN] = 1; - settings.c_cc[VTIME] = 0; - - if (tcsetattr(isdn_fd, TCSANOW, &settings)) - return -1; - - i = 0; - - while (!failed && i < sizeof(init_commands) / sizeof(char*)) { - tty_clear(isdn_fd); - if (tty_write(isdn_fd, init_commands[i++])) - failed = 1; - else - if (tty_read(isdn_fd, init_commands[i++], ISDN_COMMAND_TIMEOUT, NULL)) - failed = 1; + if (number) { + /* NOTE: number format: + * Byte 0: length of structure + * Byte 1: numbering plan + * Byte 2: presentation indicator (0x80 standard, 0xA0 for CLIR) + * Byte 3..n: number digits + */ + int len = number[0] - 2; + if (len <= 0) { + isdn->local_number = 0; + } else { + tmp = (char*) malloc(len + 1); + memcpy(tmp, number + 3, len); + tmp[len] = 0; + isdn->local_number = tmp; + } + } else { + isdn->local_number = 0; } - return -failed; + if (tofree) + free(tofree); } -/* - * ISDN de-initialization, restores termios settings - * - * returns 0 on success, -1 otherwise - */ -int deinit_isdn_device(int isdn_fd, struct termios *backup) { - return tcsetattr(isdn_fd, TCSANOW, backup); -} +/*--------------------------------------------------------------------------*/ -/* - * sets an MSN for the specified ttyI (originating MSN) - * fd is assumed to be in command mode - * - * returns 0 on success, -1 otherwise - */ -int isdn_setMSN(int isdn_fd, char *msn) { - char buf[256]; - - if (snprintf(buf, sizeof(buf), "AT&E%s\n", msn) >= (int)sizeof(buf)) { - fprintf(stderr, "Error: Specified MSN too long.\n"); - return -1; - } - tty_clear(isdn_fd); - if (tty_write(isdn_fd, buf)) - return -1; - else - if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL)) - return -1; - return 0; -} - -/* - * sets MSNs for the specified ttyI (MSNs to listen on) - * fd is assumed to be in command mode - * msns: semicolon-separated list of msns - * - * returns 0 on success, -1 otherwise - */ -int isdn_setMSNs(int isdn_fd, char *msns) { - char buf[256]; - - if (snprintf(buf, sizeof(buf), "AT&L%s\n", msns) >= (int)sizeof(buf)) { - fprintf(stderr, "Error: Specified MSNs string too long.\n"); - return -1; - } - tty_clear(isdn_fd); - if (tty_write(isdn_fd, buf)) - return -1; - else - if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL)) - return -1; - return 0; -} - -/* - * stops audio mode (enter command mode) - * - * input: self_hangup: we want to hang up ourselves - * - * returns 0 on success, -1 otherwise - */ -int isdn_stop_audio(int isdn_fd, int self_hangup) { +static int isdn_trigger_disconnect(isdn_t *isdn) +{ + unsigned int info; int result = 0; - unsigned char abort_sending[] = {DLE, DC4, 0}; - unsigned char end_of_audio[] = {DLE, ETX, 0}; + _cmsg CMSG; /* structure for the message */ - char *got; + switch (isdn->state) { + case ISDN_CONNECT_WAIT: + case ISDN_CONNECT_ACTIVE: + case ISDN_DISCONNECT_B3_REQ: + case ISDN_DISCONNECT_B3_WAIT: + case ISDN_INCOMING_WAIT: + /* no data channel yet or no reply to data disconnect, do physical disconnect */ + { + dbgprintf(1, "CAPI 2.0: DISCONNECT_REQ ApplID %d plci 0x%x\n", + isdn->appl_id, isdn->active_plci); - tty_clear(isdn_fd); + g_mutex_lock(isdn->data_lock); + info = DISCONNECT_REQ(&CMSG, isdn->appl_id, isdn->msg_no++, + isdn->active_plci, /* physical connection ID */ + 0, 0, 0, 0 /* additional info */); + g_mutex_unlock(isdn->data_lock); - if (self_hangup) { /* we want to hang up ourselves */ - if (tty_write(isdn_fd, end_of_audio)) { /* ETX - end of audio */ - fprintf(stderr, "Error sending ETX (End of audio).\n"); - return -1; - } - if (tty_write(isdn_fd, abort_sending)) {/* abort sending (request) (DC4) */ - fprintf(stderr, "Error sending DC4 (abort sending).\n"); - return -1; - } - if (tty_read(isdn_fd, end_of_audio, ISDN_COMMAND_TIMEOUT, NULL)) { - fprintf(stderr, "Error waiting for ETX (End of audio).\n"); - return -1; - } - if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, NULL)) { - fprintf(stderr, "Error getting line break.\n"); - return -1; - } - if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, &got)) { - fprintf(stderr, "Error getting new line.\n"); - } - if (!strstr(got, "VCON")) { - fprintf(stderr, "Error getting status.\n"); + if (info != 0) { + errprintf("CAPI 2.0: DISCONNECT_REQ failed, RC=0x%x\n", info); + isdn->state = ISDN_IDLE; + isdn->callback->info_error(isdn->cb_context, info); + result = -1; + } else { + isdn->state = ISDN_DISCONNECT_ACTIVE; + } + } + break; + + case ISDN_CONNECT_B3_WAIT: + case ISDN_CONNECTED: + /* both data and physical connection active, tear down data channel */ + { + dbgprintf(1, "CAPI 2.0: DISCONNECT_B3_REQ ApplID %d ncci 0x%x\n", + isdn->appl_id, isdn->active_ncci); + + g_mutex_lock(isdn->data_lock); + info = DISCONNECT_B3_REQ(&CMSG, isdn->appl_id, isdn->msg_no++, + isdn->active_ncci, /* logical connection ID */ + NULL /* NCPI */); + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + errprintf("CAPI 2.0: DISCONNECT_B3_REQ failed, RC=0x%x\n", info); + + /* retry with disconnect on whole connection */ + dbgprintf(1, "CAPI 2.0: DISCONNECT_REQ ApplID %d plci 0x%x\n", + isdn->appl_id, isdn->active_plci); + + g_mutex_lock(isdn->data_lock); + info = DISCONNECT_REQ(&CMSG, isdn->appl_id, isdn->msg_no++, + isdn->active_plci, /* physical connection ID */ + 0, 0, 0, 0 /* additional info */); + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + errprintf("CAPI 2.0: DISCONNECT_REQ failed, RC=0x%x\n", info); + isdn->state = ISDN_IDLE; + isdn->callback->info_error(isdn->cb_context, info); + result = -1; + } else { + isdn->state = ISDN_DISCONNECT_ACTIVE; + } + } else { + isdn->state = ISDN_DISCONNECT_B3_REQ; + } + } + break; + + case ISDN_RINGING: + /* reject the call */ + { + dbgprintf(2, "CAPI 2.0: CONNECT_RESP ApplID %d msgno %d plci 0x%x reject %d\n", + isdn->appl_id, isdn->msg_no, isdn->active_plci, 3); + + g_mutex_lock(isdn->data_lock); + info = CONNECT_RESP(&CMSG, isdn->appl_id, isdn->msg_no++, + isdn->active_plci, 3 /* reject */, + 0 /* B1protocol: default */, + 0 /* B2protocol: default */, + 0 /* default B3protocol */, + 0 /* default B1configuration */, + 0 /* default B2configuration */, + 0 /* default B3configuration */, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL /* additional info */); + g_mutex_unlock(isdn->data_lock); + + isdn->state = ISDN_IDLE; + if (info != 0) { + errprintf("CAPI 2.0: CONNECT_RESP failed, RC=0x%x\n", info); + isdn->callback->info_error(isdn->cb_context, info); + } else { + isdn->callback->info_disconnected(isdn->cb_context); + } + } + break; + + default: + errprintf("ISDN in unexpected state %d on disconnect\n", isdn->state); result = -1; - } - free(got); - } else { /* remote side hangup */ - if (tty_write(isdn_fd, end_of_audio)) { /* ETX - end of audio */ - fprintf(stderr, "Error sending ETX (End of audio).\n"); - return -1; - } - /* there doesn't seem to come anything after remote hangup ... (?) */ + break; } - tty_clear(isdn_fd); + return result; } -/* - * hang up isdn device - * - * NOTE: assumes command mode - * - * returns 0 on success, -1 otherwise - */ -int isdn_hangup(int isdn_fd) { - tty_clear(isdn_fd); - if (tty_write(isdn_fd, "ATH\n")) - return -1; - if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL)) - return -1; - tty_clear(isdn_fd); - return 0; +/*--------------------------------------------------------------------------*/ + +static void isdn_handle_confirmation(isdn_t *isdn, _cmsg *msg) +{ + unsigned int info, plci, ncci, controller; + + switch (msg->Command) { + + case CAPI_ALERT: + /* ALERT message */ + { + plci = ALERT_CONF_PLCI(msg); + info = ALERT_CONF_INFO(msg); + + dbgprintf(2, "CAPI 2.0: ALERT_CONF ApplID %d plci 0x%x info 0x%x\n", + isdn->appl_id, plci, info); + + if (info != 0) { + /* connection error */ + isdn->state = ISDN_IDLE; + } else { + /* may ring now */ + isdn->callback->info_ring(isdn->cb_context, isdn->remote_number, isdn->local_number); + } + } + break; + + case CAPI_CONNECT: + /* physical channel connection is being established */ + { + plci = CONNECT_CONF_PLCI(msg); + info = CONNECT_CONF_INFO(msg); + + dbgprintf(2, "CAPI 2.0: CONNECT_CONF ApplID %d plci 0x%x info 0x%x\n", + isdn->appl_id, plci, info); + + if (info != 0) { + /* connection error */ + isdn->state = ISDN_IDLE; + isdn->callback->info_error(isdn->cb_context, info); + } else { + /* CONNECT_ACTIVE_IND comes later, when connection actually established */ + isdn->state = ISDN_CONNECT_WAIT; + isdn->active_plci = plci; + } + } + break; + + case CAPI_CONNECT_B3: + /* logical connection is being established */ + { + ncci = CONNECT_B3_CONF_NCCI(msg); + info = CONNECT_B3_CONF_INFO(msg); + + dbgprintf(2, "CAPI 2.0: CONNECT_B3_CONF ApplID %d ncci 0x%x info 0x%x\n", + isdn->appl_id, ncci, info); + + if (isdn->state == ISDN_CONNECT_ACTIVE) { + if (info != 0) { + /* connection error */ + isdn->callback->info_error(isdn->cb_context, info); + isdn_trigger_disconnect(isdn); + } else { + /* CONNECT_B3_ACTIVE_IND comes later, when connection actually established */ + isdn->active_ncci = ncci; + isdn->state = ISDN_CONNECT_B3_WAIT; + } + } else { + /* wrong connection state for B3 connect, trigger disconnect */ + isdn_trigger_disconnect(isdn); + } + } + break; + + case CAPI_SELECT_B_PROTOCOL: + /* currently unused, response to SELECT_B_PROTOCOL_REQ while connection established */ + break; + + case CAPI_LISTEN: + /* LISTEN confirmation */ + { + info = LISTEN_CONF_INFO(msg); + controller = LISTEN_CONF_CONTROLLER(msg); + + dbgprintf(2, "CAPI 2.0: LISTEN_CONF ApplID %d controller %d info 0x%x\n", + isdn->appl_id, controller, info); + } + break; + + case CAPI_DISCONNECT_B3: + /* data channel disconnect initiated */ + { + ncci = DISCONNECT_B3_CONF_NCCI(msg); + info = DISCONNECT_B3_CONF_INFO(msg); + + dbgprintf(2, "CAPI 2.0: DISCONNECT_B3_CONF ApplID %d ncci 0x%x info 0x%x\n", + isdn->appl_id, ncci, info); + + if (info != 0) { + /* error, most probably NCCI not known */ + isdn->callback->info_error(isdn->cb_context, info); + isdn->state = ISDN_DISCONNECT_B3_WAIT; + isdn_trigger_disconnect(isdn); + } else { + /* DISCONNECT_B3_ACTIVE_IND comes later, when connection actually closed */ + isdn->state = ISDN_DISCONNECT_B3_WAIT; + } + } + break; + + case CAPI_DISCONNECT: + /* physical channel disconnect initiated */ + { + plci = DISCONNECT_CONF_PLCI(msg); + info = DISCONNECT_CONF_INFO(msg); + + dbgprintf(2, "CAPI 2.0: DISCONNECT_CONF ApplID %d plci 0x%x info 0x%x\n", + isdn->appl_id, plci, info); + + if (info != 0) { + /* connection error */ + isdn->state = ISDN_IDLE; + isdn->callback->info_error(isdn->cb_context, info); + } else { + /* DISCONNECT_ACTIVE_IND comes later, when connection actually closed */ + isdn->state = ISDN_DISCONNECT_WAIT; + } + } + break; + + case CAPI_DATA_B3: + /* sent data acknowledged, NOP */ + break; + + case CAPI_FACILITY: + /* TODO */ + break; + } } -/* - * Set isdn device to block mode, assuming in audio mode - * - * in blockmode, the minimum number of bytes possible to read from specified - * ttyI will be set to DEFAULT_ISDNBUF_SIZE and non blocking reads and writes - * will be enabled - * - * input: - * isdn_fd file descriptor - * flag 0 == off, 1 == on - * - * returns 0 on success, -1 otherwise - */ -int isdn_blockmode(int isdn_fd, int flag) { - struct termios settings; - int min, time; - int flags; +/*--------------------------------------------------------------------------*/ - flags = fcntl(isdn_fd, F_GETFL, 0); - if (flags == -1) { - perror("F_GETFL"); - return -1; +static void isdn_handle_indication(isdn_t *isdn, _cmsg *msg) +{ + unsigned int info, plci, ncci, flags, datalen, datahandle, cip, reject; + char *number, *called; + _cstruct ncpi; + void *data; + + switch (msg->Command) { + case CAPI_CONNECT: + /* connect indication when called from remote phone */ + { + plci = CONNECT_IND_PLCI(msg); + cip = CONNECT_IND_CIPVALUE(msg); + + number = (char*) CONNECT_IND_CALLINGPARTYNUMBER(msg); + called = (char*) CONNECT_IND_CALLEDPARTYNUMBER(msg); + + dbgprintf(2, "CAPI 2.0: CONNECT_IND ApplID %d plci 0x%x cip %d\n", + isdn->appl_id, plci, cip); + + reject = 0; + if (cip != 16 && cip != 1 && cip != 4) { + /* not telephony */ + reject = 1; /* ignore */ + } else if (isdn->state != ISDN_IDLE) { + reject = 3; /* user busy */ + } + /* TODO: check called number, if in listening MSN set, set reject=1 (ignore), if not */ + + if (!reject) { + /* may ring now */ + isdn->active_plci = plci; + + isdn_set_remote_number(isdn, number); + isdn_set_local_number(isdn, called); + +#if 0 + isdn->state = ISDN_RINGING; + isdn->callback->info_ring(isdn->cb_context, isdn->remote_number, isdn->local_number); +#else + /* tell the network, we are interested in the call and ring */ + dbgprintf(2, "CAPI 2.0: ALERT_REQ ApplID %d msgno %d plci 0x%x\n", + isdn->appl_id, isdn->msg_no, plci); + + g_mutex_lock(isdn->data_lock); + info = ALERT_REQ(msg, isdn->appl_id, isdn->msg_no++, plci, + NULL, NULL, NULL, NULL, NULL); + g_mutex_unlock(isdn->data_lock); + + if (info == 0) { + isdn->state = ISDN_RINGING; + isdn->active_plci = plci; + } else { + errprintf("CAPI 2.0: ALERT_REQ failed, RC=0x%x, rejecting call\n", info); + reject = 3; + } +#endif + } + + if (reject) { + /* answer the info message immediately */ + dbgprintf(2, "CAPI 2.0: CONNECT_RESP ApplID %d msgno %d plci 0x%x reject %d\n", + isdn->appl_id, isdn->msg_no, plci, reject); + + g_mutex_lock(isdn->data_lock); + CONNECT_RESP(msg, isdn->appl_id, isdn->msg_no++, + plci, reject, + 0 /* B1protocol: default */, + 0 /* B2protocol: default */, + 0 /* default B3protocol */, + 0 /* default B1configuration */, + 0 /* default B2configuration */, + 0 /* default B3configuration */, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL /* additional info */); + g_mutex_unlock(isdn->data_lock); + } + } + break; + + case CAPI_CONNECT_ACTIVE: + /* connection is now active */ + { + plci = CONNECT_ACTIVE_IND_PLCI(msg); + number = (char*) CONNECT_ACTIVE_IND_CONNECTEDNUMBER(msg); + + dbgprintf(2, "CAPI 2.0: CONNECT_ACTIVE_IND ApplID %d plci 0x%x\n", + isdn->appl_id, plci); + + if (plci != isdn->active_plci) { + /* connect on wrong PLCI??? */ + errprintf("CAPI 2.0: CONNECT_ACTIVE_IND wrong plci 0x%x, expected 0x%x\n", + plci, isdn->active_plci); + + g_mutex_lock(isdn->data_lock); + CONNECT_ACTIVE_RESP(msg, isdn->appl_id, isdn->msg_no++, plci); + g_mutex_unlock(isdn->data_lock); + } else { + if (isdn->state != ISDN_INCOMING_WAIT) { + isdn_set_remote_number(isdn, number); + } + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + CONNECT_ACTIVE_RESP(msg, isdn->appl_id, isdn->msg_no++, plci); + g_mutex_unlock(isdn->data_lock); + + if (isdn->state == ISDN_INCOMING_WAIT) { + /* B-channel will be established by remote side */ + isdn->state = ISDN_CONNECT_ACTIVE; + } else { + /* request connection for B-channel */ + dbgprintf(2, "CAPI 2.0: CONNECT_B3_REQ ApplID %d msgno %d plci 0x%x\n", + isdn->appl_id, isdn->msg_no, isdn->active_plci); + + g_mutex_lock(isdn->data_lock); + info = CONNECT_B3_REQ(msg, isdn->appl_id, isdn->msg_no++, isdn->active_plci, NULL); + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + /* connection error */ + errprintf("CAPI 2.0: CONNECT_B3_REQ failed, RC=0x%x\n", info); + isdn->callback->info_error(isdn->cb_context, info); + isdn_set_remote_number(isdn, 0); + /* initiate hangup on PLCI */ + isdn_trigger_disconnect(isdn); + } else { + /* wait for CONNECT_B3, then announce result to application via callback */ + isdn->state = ISDN_CONNECT_ACTIVE; + } + } + } + } + break; + + case CAPI_CONNECT_B3_ACTIVE: + /* B-channel connection is now active, connection complete */ + { + ncci = CONNECT_B3_ACTIVE_IND_NCCI(msg); + + if (ncci != isdn->active_ncci) { + /* connect on wrong NCCI??? */ + errprintf("CAPI 2.0: CONNECT_B3_ACTIVE_IND wrong ncci 0x%x, expected %d\n", + ncci, isdn->active_ncci); + } else { + dbgprintf(2, "CAPI 2.0: CONNECT_B3_ACTIVE_IND ApplID %d msgno %d ncci 0x%x\n", + isdn->appl_id, isdn->msg_no, ncci); + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + CONNECT_B3_ACTIVE_RESP(msg, isdn->appl_id, isdn->msg_no++, ncci); + g_mutex_unlock(isdn->data_lock); + isdn->state = ISDN_CONNECTED; + + /* notify application about successful call establishment */ + isdn_speed_init(&isdn->in_speed); + isdn->callback->info_connected(isdn->cb_context, isdn->remote_number); + } + } + break; + + case CAPI_DISCONNECT: + /* connection completely released */ + { + plci = DISCONNECT_IND_PLCI(msg); + info = DISCONNECT_IND_REASON(msg); + + dbgprintf(2, "CAPI 2.0: DISCONNECT_IND ApplID %d msgno %d plci 0x%x reason 0x%x\n", + isdn->appl_id, isdn->msg_no, plci, info); + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + DISCONNECT_RESP(msg, isdn->appl_id, isdn->msg_no++, plci); + g_mutex_unlock(isdn->data_lock); + + if (plci != isdn->active_plci) { + /* disconnect on wrong PLCI??? */ + errprintf("CAPI 2.0: DISCONNECT_IND wrong PLCI %d, expected %d\n", + plci, isdn->active_plci); + } else { + isdn->state = ISDN_IDLE; + isdn->active_ncci = 0; + isdn->active_plci = 0; + + if ((info & 0xff00) == 0x3400) { + /* network provides reason in lower byte */ + switch (info) { + case 0x3400: + case 0x3480: + case 0x3490: + case 0x349f: + /* normal connection close */ + info = 0; + break; + } + } + + /* notify application */ + if (info != 0) { + isdn->callback->info_error(isdn->cb_context, info); + } else { + isdn->callback->info_disconnected(isdn->cb_context); + } + } + } + break; + + case CAPI_DISCONNECT_B3: + /* B-channel connection is now disconnected, connection terminating */ + { + ncci = DISCONNECT_B3_IND_NCCI(msg); + info = DISCONNECT_B3_IND_REASON_B3(msg); + + dbgprintf(2, "CAPI 2.0: DISCONNECT_B3_IND ApplID %d msgno %d ncci 0x%x reason 0x%x\n", + isdn->appl_id, isdn->msg_no, ncci, info); + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + DISCONNECT_B3_RESP(msg, isdn->appl_id, isdn->msg_no++, ncci); + g_mutex_unlock(isdn->data_lock); + + if (ncci != isdn->active_ncci) { + /* disconnect on wrong NCCI??? */ + errprintf("CAPI 2.0: DISCONNECT_B3_IND wrong ncci 0x%x, expected 0x%x\n", + ncci, isdn->active_ncci); + } else { + isdn->active_ncci = 0; + if (isdn->state == ISDN_CONNECTED || isdn->state == ISDN_CONNECT_B3_WAIT) { + /* passive disconnect, DISCONNECT_IND comes later */ + isdn->state = ISDN_DISCONNECT_ACTIVE; + } else { + /* active disconnect, needs to send DISCONNECT_REQ */ + isdn_trigger_disconnect(isdn); + } + } + } + break; + + case CAPI_DATA_B3: + /* data arrived */ + { + ncci = DATA_B3_IND_NCCI(msg); + data = DATA_B3_IND_DATA(msg); + datalen = DATA_B3_IND_DATALENGTH(msg); + datahandle = DATA_B3_IND_DATAHANDLE(msg); + flags = DATA_B3_IND_FLAGS(msg); + + dbgprintf(flags ? 2 : 3, "CAPI 2.0: DATA_B3_IND ApplID %d msgno %d ncci 0x%x data 0x%lx+%d flags 0x%x\n", + isdn->appl_id, isdn->msg_no, ncci, (long) data, datalen, flags); + + isdn_speed_addsamples(&isdn->in_speed, datalen); + + /* TODO: process flags */ + isdn->callback->info_data(isdn->cb_context, data, datalen); + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + DATA_B3_RESP(msg, isdn->appl_id, isdn->msg_no++, ncci, datahandle); + g_mutex_unlock(isdn->data_lock); + + if (debug > 1) { + isdn_speed_debug(&isdn->in_speed, 2, "CAPI 2.0: in"); + } + } + break; + + case CAPI_CONNECT_B3: + /* connect indication from remote side */ + { + ncci = CONNECT_B3_IND_NCCI(msg); + ncpi = CONNECT_B3_IND_NCPI(msg); + + dbgprintf(3, "CAPI 2.0: CONNECT_B3_IND ApplID %d msgno %d ncci 0x%x\n", + isdn->appl_id, isdn->msg_no, ncci); + + /* answer the info message */ + g_mutex_lock(isdn->data_lock); + CONNECT_B3_RESP(msg, isdn->appl_id, isdn->msg_no++, ncci, 0, ncpi); + g_mutex_unlock(isdn->data_lock); + + if (isdn->state == ISDN_CONNECT_ACTIVE) { + /* CONNECT_B3_ACTIVE_IND comes later, when connection actually established */ + isdn->active_ncci = ncci; + isdn->state = ISDN_CONNECT_B3_WAIT; + } else { + /* wrong connection state for B3 connect, trigger disconnect */ + isdn_trigger_disconnect(isdn); + } + } + break; + + case CAPI_FACILITY: + case CAPI_INFO: + break; } +} - if (flag) { - min = DEFAULT_ISDNBUF_SIZE; - time = 0; /* 10 == 1sec */ - flags |= O_NONBLOCK; /* select consumes much cpu time with this */ - } else { - min = 1; - time = 0; - flags &= ~O_NONBLOCK; - } +/*--------------------------------------------------------------------------*/ - if (fcntl(isdn_fd, F_SETFL, flags) == -1) { - perror("G_GETFL"); - return -1; - } +static gpointer isdn_reply_thread(gpointer param) +{ + isdn_t *isdn = (isdn_t*) param; + _cmsg msg; + unsigned int info; - if (tcgetattr(isdn_fd, &settings)) { - perror("isdn_blockmode, tcgetattr"); - return -1; - } + /* timeout is needed, since CAPI release doesn't release waitformessage as it should */ + struct timeval timeout; - settings.c_cc[VMIN] = min; - settings.c_cc[VTIME] = time; - - if (tcsetattr(isdn_fd, TCSANOW, &settings)) { - perror("isdn_blockmode, tcsetattr"); - return -1; - } + while (!thread_is_stopping(&isdn->reply_thread)) { + /* process CAPI messages and call callbacks */ + timeout.tv_sec = 1; + timeout.tv_usec = 0; + info = capi20_waitformessage(isdn->appl_id, &timeout); - /* verify */ - if (tcgetattr(isdn_fd, &settings)) - return -1; - if (settings.c_cc[VMIN] != min) { - fprintf(stderr,"Error setting block size. New block size: %d.\n", - settings.c_cc[VMIN]); - return -1; + if (info != CapiNoError) { + if (isdn->appl_id == 0) { + /* ISDN inactive, retry later */ + sleep(1); + } + continue; + } + + g_mutex_lock(isdn->data_lock); + + info = CAPI_GET_CMSG(&msg, isdn->appl_id); + + g_mutex_unlock(isdn->data_lock); + + g_mutex_lock(isdn->lock); + + switch (info) { + case CapiNoError: + /* process the message */ + switch (msg.Subcommand) { + case CAPI_CONF: + /* confirmation message */ + isdn_handle_confirmation(isdn, &msg); + break; + + case CAPI_IND: + /* indication message */ + isdn_handle_indication(isdn, &msg); + break; + } + break; + + case CapiReceiveQueueEmpty: + errprintf("CAPI 2.0: Empty queue, even if message pending\n"); + break; + + default: + /* error */ + errprintf("CAPI 2.0: Error while receiving next message, stopping ISDN, RC=0x%x\n", info); + isdn->reply_thread.stop_flag = 1; + break; + } + + g_mutex_unlock(isdn->lock); } return 0; } -/* - * closes ttyI device and removes lock file - * - * returns 0 on success, -1 otherwise - */ -int close_isdn_device(int isdn_fd, char *isdn_device_name, - char *isdn_lockfile_name) { - if (close(isdn_fd)) { +/*--------------------------------------------------------------------------*/ + +int open_isdn_device(isdn_t *isdn, isdn_callback_t *callbacks, void *context) +{ + unsigned int info; + + unsigned char buf[64]; + unsigned int numControllers, i, appl_id; + unsigned int bChannels, dtmf, fax, faxExt, suppServ, transp; + _cdword buf2[4]; + + memset(isdn, 0, sizeof(isdn_t)); + + info = CAPI20_ISINSTALLED(); + if (info != 0) { + errprintf("CAPI 2.0: not installed, RC=0x%x\n", info); return -1; } - if (unlink(isdn_lockfile_name)) { - fprintf(stderr, "Removing isdn device lock file: unlink error.\n"); + + isdn->lock = g_mutex_new(); + if (!isdn->lock) { + errprintf("Cannot allocate ISDN mutex\n"); + return -1; + } + isdn->data_lock = g_mutex_new(); + if (!isdn->data_lock) { + g_mutex_free(isdn->lock); + isdn->lock = 0; + errprintf("Cannot allocate data mutex\n"); return -1; } - free(isdn_lockfile_name); - free(isdn_device_name); - return 0; -} -/* - * dials specified number (voice call) - * (proper ttyI init is assumed) - * - * returns: - * 0 on success (VCON) - * 1 on busy - * -1 otherwise (error / timeout) - */ -int isdn_dial(int isdn_fd, char *number, int timeout) { - char *s; - int result; - char *got; + info = CAPI20_GET_PROFILE (0, buf); + if (info != 0) { + errprintf("CAPI 2.0: error getting profile, RC=0x%x\n", info); + return -1; + } + numControllers = buf[0] + (buf[1] << 8); - if ((s = (char*) malloc(strlen(number) + 5))) { - - snprintf(s, strlen(number) + 5, "ATD%s\n", number); - tty_clear(isdn_fd); - result = tty_write(isdn_fd, s); - free(s); - if (result) { - fprintf(stderr, "Error dialing.\n"); + if (numControllers == 0) { + errprintf("CAPI 2.0: No ISDN controllers installed\n"); + return -1; + } + + if (debug) { + dbgprintf(1, "CAPI 2.0: Controllers found: %d\n", numControllers); + if (capi20_get_manufacturer(0,buf)) { + dbgprintf(1, "CAPI 2.0: Manufacturer: %s\n", buf); + } + if (capi20_get_version(0, (unsigned char *) buf2)) { + dbgprintf(1, "CAPI 2.0: Version: %d.%d/%d.%d\n", + buf2[0], buf2[1], buf2[2], buf2[3]); + } + } + + + for (i = 1; i <= numControllers; ++i) + { + if (debug) { + if (capi20_get_manufacturer(i, buf)) { + dbgprintf(1, "CAPI 2.0: Controller %d: Manufacturer: %s\n", i, buf); + } + if (capi20_get_version(i, (unsigned char *) buf2)) { + dbgprintf(1, "CAPI 2.0: Controller %d: Version: %d.%d/%d.%d\n", + i, buf2[0], buf2[1], buf2[2], buf2[3]); + } + } + + info = CAPI20_GET_PROFILE(i, buf); + if (info != 0) { + errprintf("CAPI 2.0: error getting controller %d profile, RC=0x%x\n", + i, info); + return -1; + } + + bChannels = buf[2] + (buf[3]<<8); + + if (buf[4] & 0x08) + dtmf = 1; + else + dtmf = 0; + + if (buf[4] & 0x10) + suppServ = 1; + else + suppServ = 0; + + if (buf[8] & 0x02 && buf[12] & 0x02 && buf[16] & 0x01) + transp = 1; + else + transp = 0; + + if (buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x10) + fax = 1; + else + fax = 0; + + if (buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x20) + faxExt = 1; + else + faxExt = 0; + + dbgprintf(1, "CAPI 2.0: Bchan %d, DTMF %d, FAX %d/%d, transp %d, suppServ %d\n", + bChannels, dtmf, fax, faxExt, transp, suppServ); + } + + info = capi20_register(2 /*maxLogicalConnection*/, + 7 /*maxBDataBlocks*/, + 2 * ISDN_FRAGMENT_SIZE /*maxBDataLen*/, + &appl_id); + + if (appl_id == 0 || info != 0) { + errprintf("CAPI 2.0: Error registering application, RC=0x%x\n", info); + return -1; + } + dbgprintf(1, "CAPI 2.0: Received application ID %d\n", appl_id); + + isdn->appl_id = appl_id; + isdn->ctrl_count = numControllers; + isdn->callback = callbacks; + isdn->cb_context = context; + isdn->msg_no = 0; + thread_init(&isdn->reply_thread); + + /* INFO and CIP masks as defined in Chapter 5.37 of CAPI 2.0 specs */ + + /* call progression */ + isdn->info_mask = 0x10; + /* speech, 3,1kHz audio, telephony */ + isdn->cip_mask = 0x00010012; + /* telephony only */ + /*isdn->cip_mask = 0x00010000;*/ + /* all services would be: 0x1FFF03FF */ + + /* activate listening on all controllers */ + for (i = 1; i <= numControllers; ++i) { + /* TODO: listen only if the controller has voice capability */ + if (isdn_listen(isdn, i) < 0) { + errprintf("CAPI 2.0: Error listening on controller %d\n", i); return -1; } } - if (tty_read(isdn_fd, "\r\n", timeout, NULL)) /* error / timeout */ - return -1; + thread_start(&isdn->reply_thread, isdn_reply_thread, isdn); - if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, &got)) - return -1; - - if (strstr(got, "BUSY\r\n")) { - free(got); - return 1; - } - - if (strstr(got, "VCON\r\n")) { - free(got); - return 0; - } - - free(got); - return -1; /* failed somehow */ -} - -/* - * sets ttyI to full duplex mode - * (should be called directly after VCON) - * - * returns 0 on success, -1 otherwise - */ -int isdn_set_full_duplex(int isdn_fd) { - tty_clear(isdn_fd); - if (tty_write(isdn_fd, "AT+VRX+VTX\n")) - return -1; return 0; } -/* - * returns the name of the calls file (originally just /var/lib/isdn/calls) - * on error, returns NULL - */ +/*--------------------------------------------------------------------------*/ + +int close_isdn_device(isdn_t *isdn) +{ + unsigned int info; + int result = 0; + + if (isdn->appl_id != 0) { + info = capi20_release(isdn->appl_id); + if (info != 0) { + errprintf("CAPI 2.0: Error releasing ISDN controller, RC=0x%x\n", info); + result = -1; + } + } + + thread_stop(&isdn->reply_thread); + + isdn->appl_id = 0; + if (isdn->own_msn) { + free(isdn->own_msn); + isdn->own_msn = 0; + } + if (isdn->lock) { + g_mutex_free(isdn->lock); + isdn->lock = 0; + } + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int activate_isdn_device(isdn_t *isdn, unsigned int active) +{ + unsigned int info, appl_id, numControllers, i; + unsigned char buf[64]; + int result = 0; + + dbgprintf(1, "CAPI 2.0: activate %d\n", active); + if (active) { + /* activate */ + if (isdn->appl_id == 0) { + info = CAPI20_GET_PROFILE (0, buf); + if (info != 0) { + errprintf("CAPI 2.0: error getting profile, RC=0x%x\n", info); + result = -1; + } else { + numControllers = buf[0] + (buf[1] << 8); + + info = capi20_register(2 /*maxLogicalConnection*/, + 7 /*maxBDataBlocks*/, + 2 * ISDN_FRAGMENT_SIZE /*maxBDataLen*/, + &appl_id); + + if (appl_id == 0 || info != 0) { + errprintf("CAPI 2.0: Error registering application, RC=0x%x\n", info); + return -1; + } + dbgprintf(1, "CAPI 2.0: Received application ID %d\n", appl_id); + isdn->appl_id = appl_id; + + /* activate listening on all controllers */ + for (i = 1; i <= numControllers; ++i) { + /* TODO: listen only if the controller has voice capability */ + if (isdn_listen(isdn, i) < 0) { + errprintf("CAPI 2.0: Error listening on controller %d\n", i); + return -1; + } + } + } + } + } else { + /* deactivate */ + if (isdn->appl_id) { + info = capi20_release(isdn->appl_id); + if (info != 0) { + errprintf("CAPI 2.0: Error releasing ISDN controller, RC=0x%x\n", info); + result = -1; + } + isdn->appl_id = 0; + } + } + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_dial(isdn_t *isdn, unsigned int controller, char *number) +{ + _cmsg CMSG; /* structure for the message */ + unsigned int info, msgno; + int result = 0; + char *called_nr, *calling_nr; + + g_mutex_lock(isdn->lock); + + if (isdn->state != ISDN_IDLE) { + errprintf("ISDN connection or disconnect in progress, cannot dial (state %d)\n", isdn->state); + result = -1; + } else { + msgno = isdn->msg_no++; + + if (controller == 0) { + // TODO: pick proper controller which has voice capability + controller = 1; + } + + dbgprintf(1, "CAPI 2.0: CONNECT_REQ ApplID %d ctrl %d CIP %d Called %s\n", + isdn->appl_id, controller, 16, number); + + called_nr = (char*) malloc(strlen(number) + 3); + if (!called_nr) { + errprintf("Cannot allocate memory for called number\n"); + return -1; + } + called_nr[0] = strlen(number) + 1; + called_nr[1] = 0x80; + strcpy(called_nr + 2, number); + + if (isdn->own_msn) { + calling_nr = (char*) malloc(strlen(isdn->own_msn) + 4); + if (calling_nr) { + calling_nr[0] = strlen(isdn->own_msn) + 2; + calling_nr[1] = 0x00; + calling_nr[2] = 0x80; /* NOTE 0xA0 to disable displaying on remote side */ + strcpy(calling_nr + 3, isdn->own_msn); + } + } else { + calling_nr = 0; + } + + g_mutex_lock(isdn->data_lock); + info = CONNECT_REQ(&CMSG, isdn->appl_id, msgno, controller, + (unsigned short) 16 /* CIP: telephony */, + (unsigned char*) called_nr /* called party number */, + (unsigned char*) calling_nr /* calling party number */, + 0 /* called party subaddress */, + 0 /* calling party subaddress */, + 1 /* B1protocol: DTE (originate) */, + 1 /* B2protocol: transparent */, + 0 /* default B3protocol */, + 0 /* default B1configuration */, + 0 /* default B2configuration */, + 0 /* default B3configuration */, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL /* additional info */); + g_mutex_unlock(isdn->data_lock); + + free(called_nr); + if (calling_nr) + free(calling_nr); + + if (info == 0) { + isdn->state = ISDN_CONNECT_REQ; + } else { + errprintf("CAPI 2.0: CONNECT_REQ failed, RC=0x%x\n", info); + result = -1; + } + } + + g_mutex_unlock(isdn->lock); + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_send_data(isdn_t *isdn, unsigned char *data, unsigned int datalen) +{ + int result = 0; + _cmsg CMSG; /* structure for the message */ + unsigned int info, msgno = isdn->msg_no++; + + if (isdn->state != ISDN_CONNECTED) { + dbgprintf(3, "ISDN data send while not connected (state %d)\n", isdn->state); + return -1; + } + + dbgprintf(3, "CAPI 2.0: DATA_B3_REQ ApplID %d ncci 0x%x data 0x%lx+%d\n", + isdn->appl_id, isdn->active_ncci, (long) data, datalen); + + g_mutex_lock(isdn->data_lock); + info = DATA_B3_REQ(&CMSG, isdn->appl_id, msgno, + isdn->active_ncci, data, datalen, + msgno, /* as data handle */ + 0x0); /* flags */ + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + if (isdn->state == ISDN_CONNECTED) { + dbgprintf(1, "CAPI 2.0: DATA_B3_REQ failed (too fast audio?), RC=0x%x\n", info); + } else { + dbgprintf(3, "CAPI 2.0: DATA_B3_REQ failed (ISDN disconnected, OK), RC=0x%x\n", info); + } + result = -1; + } + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_hangup(isdn_t *isdn) +{ + int result = 0; + + g_mutex_lock(isdn->lock); + + if (isdn->state == ISDN_IDLE) { + errprintf("ISDN hangup called, even if connection idle\n"); + result = -1; + } else { + result = isdn_trigger_disconnect(isdn); + } + + g_mutex_unlock(isdn->lock); + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_pickup(isdn_t *isdn _U_) +{ + _cmsg CMSG; /* structure for the message */ + int result = 0; + unsigned int info; + unsigned char localnum[4]; + + g_mutex_lock(isdn->lock); + + if (isdn->state != ISDN_RINGING) { + errprintf("ISDN pickup called, even if not ringing\n"); + result = -1; + } else { + /* answer the call via CONNECT_RESP */ + dbgprintf(2, "CAPI 2.0: CONNECT_RESP ApplID %d msgno %d plci 0x%x reject %d\n", + isdn->appl_id, isdn->msg_no, isdn->active_plci, 0); + + localnum[0] = 0x00; + localnum[1] = 0x00; + localnum[2] = 0x80; + localnum[3] = 0x00; + + g_mutex_lock(isdn->data_lock); + info = CONNECT_RESP(&CMSG, isdn->appl_id, isdn->msg_no++, + isdn->active_plci, 0, + 1 /* B1protocol: originate */, + 1 /* B2protocol: transparent */, + 0 /* default B3protocol */, + 0 /* default B1configuration */, + 0 /* default B2configuration */, + 0 /* default B3configuration */, + &localnum[0] /* TODO: local number */, + NULL, NULL, NULL, NULL, NULL, NULL, NULL /* additional info */); + g_mutex_unlock(isdn->data_lock); + + if (info != 0) { + errprintf("CAPI 2.0: CONNECT_RESP failed, RC=0x%x\n", info); + isdn->state = ISDN_IDLE; + result = -1; + } else { + /* connection initiated, wait for CONNECT_ACTIVE_IND */ + isdn->state = ISDN_INCOMING_WAIT; + } + } + + g_mutex_unlock(isdn->lock); + + return result; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_setMSN(isdn_t *isdn, char *msn) { + char *to_free = isdn->own_msn; + + if (msn && strcmp(msn, "0") != 0) + isdn->own_msn = strdup(msn); + else + isdn->own_msn = 0; + + if (to_free) + free(to_free); + return 0; +} + +/*--------------------------------------------------------------------------*/ + +int isdn_setMSNs(isdn_t *isdn _U_, char *msns _U_) { + // TODO + return 0; +} + +/*--------------------------------------------------------------------------*/ + char* isdn_get_calls_filename(void) { unsigned int i; int fd; if (isdn_calls_filename_from_config && - (fd = open(isdn_calls_filename_from_config, O_RDONLY)) != -1) + (fd = open(isdn_calls_filename_from_config, O_RDONLY, 0644)) != -1) { close(fd); - if (debug) - fprintf(stderr, - "Using calls file listed in I4L config.\n"); + dbgprintf(1, "Using calls file listed in I4L config.\n"); return isdn_calls_filename_from_config; } for (i = 0; i < sizeof(calls_filenames) / sizeof(char*); i++) { @@ -560,3 +1229,43 @@ char* isdn_get_calls_filename(void) { return NULL; } +/*--------------------------------------------------------------------------*/ + +void isdn_speed_init(isdn_speed_t *speed) +{ + speed->samples = 0; + speed->delta = 0; + speed->start = 0; + speed->debug = 0; +} + +/*--------------------------------------------------------------------------*/ + +void isdn_speed_addsamples(isdn_speed_t *speed, unsigned int samples) +{ + uint64_t time = microsec_time(); + if (speed->start) { + speed->samples += samples; + speed->delta = time - speed->start; + } else { + speed->start = time; + speed->samples = 0; + speed->delta = 0; + speed->debug = 0; + } +} + +/*--------------------------------------------------------------------------*/ + +void isdn_speed_debug(isdn_speed_t *speed, int level, char *prefix) +{ + uint64_t curtime = speed->start + speed->delta; + + if (curtime >= speed->debug + 1000000 && speed->delta) { + speed->debug = curtime; + dbgprintf(level, "%s speed: %.3f samples/sec\n", prefix, + speed->samples * 1000000.0 / speed->delta); + } +} + +/*--------------------------------------------------------------------------*/ diff --git a/src/isdn.h b/src/isdn.h index e305af5..eb24473 100644 --- a/src/isdn.h +++ b/src/isdn.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,47 +22,259 @@ * */ +#ifndef _ANT_ISDN_H +#define _ANT_ISDN_H + #include "config.h" +#include "thread.h" +#include "util.h" -#ifdef HAVE_TERMIOS_H - #include -#endif - -#define LOCK_PATH "/var/lock" - -#define DEFAULT_ISDNBUF_SIZE 255 -#define ISDN_COMMAND_TIMEOUT 1 - -/* 0 (master MSN) or MSN to use as identification on outgoing calls */ +/*! + * @brief 0 (master MSN) or MSN to use as identification on outgoing calls. + */ #define DEFAULT_MSN "0" -/* "*" (wildcard) or comma-separated list of MSNs */ +/*! + * @brief Comma-separated set of MSNs to listen on. + * + * "*" (wildcard) to listen on all MSNs + */ #define DEFAULT_MSNS "*" #define ISDN_SPEED 8000 -#define ETX 0x03 -#define DC4 0x14 -#define DLE 0x10 +/*! + * @brief Fragment size to send to ISDN device. + */ +#define ISDN_FRAGMENT_SIZE 128 #define ISDN_CONFIG_FILENAME "/etc/isdn/isdn.conf" extern char* isdn_calls_filename_from_config; -int open_isdn_device(char **isdn_device_name, char **isdn_lockfile_name); -int init_isdn_device(int isdn_fd, struct termios *backup); -int deinit_isdn_device(int isdn_fd, struct termios *backup); -int isdn_setMSN(int isdn_fd, char *msn); -int isdn_setMSNs(int isdn_fd, char *msns); -int isdn_stop_audio(int isdn_fd, int self_hangup); -int isdn_hangup(int isdn_fd); -int isdn_blockmode(int isdn_fd, int flag); -int close_isdn_device(int isdn_fd, char *isdn_device_name, - char *isdn_lockfile_name); -int isdn_dial(int isdn_fd, char *number, int timeout); -int isdn_set_full_duplex(int isdn_fd); -int tty_read(int fd, char *s, int timeout, char **got); -int tty_write(int fd, char *s); -int tty_clear(int fd); +/*! + * @brief ISDN callbacks to the application. + * + * The callbacks described in this structure run within the context + * of the calling thread. They need to send messages to other threads + * properly synchronized. + */ +typedef struct { + /*! + * @brief Callback when connection established. + * + * @param context context given at initialization time. + * @param number remote party number (may be NULL). + */ + void (*info_connected)(void *context, char *number); + + /*! + * @brief Callback when ISDN data received. + * + * @param context context given at initialization time. + * @param data pointer to received data. + * @param length length of received data (in bytes). + */ + void (*info_data)(void *context, void *data, unsigned int length); + + /*! + * @brief Callback when connection disconnected. + * + * @param context context given at initialization time. + */ + void (*info_disconnected)(void *context); + + /*! + * @brief Callback when connection attempt fails with error. + * + * @param context context given at initialization time. + * @param error CAPI error number. + */ + void (*info_error)(void *context, unsigned int error); + + /*! + * @brief Callback called on RING from other side. + * + * @param context context given at initialization time. + * @param callee remote party number (may be NULL). + * @param called called (our) number (may be NULL). + */ + void (*info_ring)(void *context, char *callee, char *called); + +} isdn_callback_t; + +/*! + * @brief Speed measurmenets. + */ +typedef struct { + unsigned long samples;/*!< total sample count (after first frame) */ + uint64_t delta; /*!< how long did it take to send/receive samples */ + + uint64_t start; /*!< input start time in microseconds (for delta computation) */ + uint64_t debug; /*!< debug timepoint */ +} isdn_speed_t; + +/*! + * @brief ISDN connection state. + */ +typedef enum { + ISDN_IDLE = 0, /*!< no connection active */ + ISDN_CONNECT_REQ, /*!< connection request has been sent */ + ISDN_CONNECT_WAIT, /*!< connection request acknowledged, waiting for connect */ + ISDN_CONNECT_ACTIVE, /*!< connection active, no data channel (but requested) */ + ISDN_CONNECT_B3_WAIT, /*!< data channel request confirmed, waiting for connect */ + ISDN_CONNECTED, /*!< connection is completely established */ + ISDN_DISCONNECT_B3_REQ, /*!< data disconnect has been requested */ + ISDN_DISCONNECT_B3_WAIT, /*!< data disconnect confirmed, waiting for actual disconnect */ + ISDN_DISCONNECT_ACTIVE, /*!< data disconnect done, sent physical channel disconnect req */ + ISDN_DISCONNECT_WAIT, /*!< channel disconnect req confirmed, waiting for actual disconnect */ + ISDN_RINGING, /*!< ringing */ + ISDN_INCOMING_WAIT, /*!< waiting for incoming connect indication */ + ISDN_MAXSTATE +} isdn_state_t; + +/*! + * @brief ISDN handle wrapping CAPI interface. + */ +typedef struct { + /* NOTE: all parts private! Do not access them directly! */ + + unsigned int appl_id; /*!< CAPI application ID */ + unsigned int msg_no; /*!< CAPI message serial number */ + + isdn_state_t state; /*!< current connection state */ + + unsigned int ctrl_count; /*!< controller count */ + char *own_msn; /*!< own MSN (for originating calls) */ + + unsigned int info_mask; /*!< info mask for received info from CAPI */ + unsigned int cip_mask; /*!< CIP mask for listening on services */ + + unsigned int active_plci; /*!< active physical connection PLCI (if off-hook) */ + unsigned int active_ncci; /*!< active logical connection NCCI (if off-hook) */ + char *remote_number; /*!< remote party number */ + char *local_number; /*!< local number (currently only for ring) */ + + GMutex *lock; /*!< lock protecting this structure */ + thread_t reply_thread; /*!< thread for processing ISDN replies */ + + isdn_callback_t *callback;/*!< set of callbacks for ISDN replies */ + void *cb_context; /*!< context to use for callbacks */ + + GMutex *data_lock; /*!< ISDN request/reply lock */ + + isdn_speed_t in_speed; /*!< ISDN data input speed */ +} isdn_t; + +/*! + * @brief Open ISDN device via CAPI interface. + * + * @param isdn handle to fill in. + * @param callbacks ISDN callbacks to call for various ISDN events. + * @param context context for ISDN callbacks. + * @return 0 on success, less than 0 on error. + */ +int open_isdn_device(isdn_t *isdn, isdn_callback_t *callbacks, void *context); + +/*! + * @brief Close ISDN device. + * + * @param isdn handle to close. + * @return 0 on success, less than 0 on error. + */ +int close_isdn_device(isdn_t *isdn); + +/*! + * @brief Activate/deactivate ISDN connection. + * + * @param isdn device handle. + * @param activate if nonzero, activate ISDN, otherwise deactivate. + * @return 0 on success, -1 otherwise (e.g., can't open ISDN device). + */ +int activate_isdn_device(isdn_t *isdn, unsigned int active); + +/*! + * @brief Initiate voice call on ISDN device. + * + * @param isdn device handle. + * @param controller ISDN controller number or 0 to use first available. + * @param number number to call. + * @return 0 on success, less than 0 on error. + */ +int isdn_dial(isdn_t *isdn, unsigned int controller, char *number); + +/*! + * @brief Hang up current call on ISDN device. + * + * @param isdn device handle. + * @return 0 on success, less than 0 on error. + */ +int isdn_hangup(isdn_t *isdn); + +/*! + * @brief Pick up pending call on ISDN device. + * + * @param isdn device handle. + * @return 0 on success, less than 0 on error. + */ +int isdn_pickup(isdn_t *isdn); + +/*! + * @brief Send data over ISDN connection, after it's established. + * + * @param isdn device handle. + * @param data pointer to data. + * @param datalen data length. + */ +int isdn_send_data(isdn_t *isdn, unsigned char *data, unsigned int datalen); + +/*! + * @brief Sets originating MSN for the specified ISDN device. + * + * @param isdn device handle. + * @param msn MSN to set ('0' for default). + * @return 0 on success, -1 otherwise. + */ +int isdn_setMSN(isdn_t *isdn, char *msn); + +/*! + * @brief Sets MSNs to listen on for the specified ISDN device. + * + * @param isdn device handle. + * @param msns comma-separated set of MSNs to listen on ('*' for any). + * @return 0 on success, -1 otherwise. + */ +int isdn_setMSNs(isdn_t *isdn, char *msns); + +/*! + * @brief Get the name of the calls file from isdnlog. + * + * @return file name or NULL on error. + */ char* isdn_get_calls_filename(void); +/*! + * @brief Initialize speed measurement structure. + * + * @param speed structure to initialize. + */ +void isdn_speed_init(isdn_speed_t *speed); + +/*! + * @brief Add bytes sent/received to measurement structure. + * + * @param speed structure to modify. + * @param samples sample count. + */ +void isdn_speed_addsamples(isdn_speed_t *speed, unsigned int samples); + +/*! + * @brief Debug message for speed. + * + * @param speed structure to print out. + * @param level debug level. + * @param prefix prefix for message. + */ +void isdn_speed_debug(isdn_speed_t *speed, int level, char *prefix); + +#endif /* _ANT_ISDN_H */ diff --git a/src/isdnlexer.l b/src/isdnlexer.l index 13c138e..dd20912 100644 --- a/src/isdnlexer.l +++ b/src/isdnlexer.l @@ -107,10 +107,10 @@ INCLUDE([ \t]|"\\\n")*"(" { /* ignoring non-existent included file */ if (debug) { if (loop_detected) - fprintf(stderr, "Loop detected "); + errprintf("Loop detected "); else - fprintf(stderr, "Error "); - fprintf(stderr, + errprintf("Error "); + errprintf( "reading ISDN options file %s " "from %s. Ignoring.\n", isdn_filename, temp->filename); @@ -124,7 +124,7 @@ INCLUDE([ \t]|"\\\n")*"(" { /* include file OK */ isdn_in = temp_in; if (debug) - fprintf(stderr, + errprintf( "Reading options file %s " "from %s ...\n", isdn_filename, temp->filename); @@ -144,7 +144,7 @@ INCLUDE([ \t]|"\\\n")*"(" { isdn_include_t* temp = isdn_include_list; if (debug) - fprintf(stderr, "Returning to %s ...\n", + errprintf("Returning to %s ...\n", temp->filename); yy_delete_buffer(YY_CURRENT_BUFFER); yy_switch_to_buffer(temp->state); @@ -216,7 +216,7 @@ INCLUDE([ \t]|"\\\n")*"(" { . { /* eat up rest */ isdn_locate(); if (debug) { - fprintf(stderr, + errprintf( "isdnlexer: Unrecognized character: %c " "at %d:%d\n", *isdn_text, @@ -261,9 +261,9 @@ void isdn_lexer_init(char* filename) { isdn_lloc.last_column = 1; isdn_include_list = NULL; if (debug) - fprintf(stderr, "Reading options file: %s ...\n", filename); + errprintf("Reading options file: %s ...\n", filename); if (!(isdn_in = fopen(filename, "r"))) { - fprintf(stderr, "Error opening options file %s.\n", filename); + errprintf("Error opening options file %s.\n", filename); } else { isdn_filename = strdup(filename); } @@ -274,7 +274,7 @@ void isdn_lexer_init(char* filename) { */ void isdn_lexer_deinit() { if (fclose(isdn_in) == EOF) { - fprintf(stderr, "Warning: Couldn't close options file.\n"); + errprintf("Warning: Couldn't close options file.\n"); } free(isdn_filename); } diff --git a/src/isdnparser.y b/src/isdnparser.y index 17868d4..a5567e3 100644 --- a/src/isdnparser.y +++ b/src/isdnparser.y @@ -97,7 +97,7 @@ section : '[' ISDN_TOKEN_NAME ']' entries if (!($$ = (isdn_tree_node_t*) malloc(sizeof(isdn_tree_node_t)))) { - fprintf(stderr, "Error: Out of memory.\n"); + errprintf("Error: Out of memory.\n"); exit(1); } $$->type = ISDN_NODE_TYPE_SECTION; @@ -122,7 +122,7 @@ entries : { $$.list = NULL; $$.last = NULL; } } | entries error { - fprintf(stderr, "Error region from %d:%d up to %d:%d.\n", + errprintf("Error region from %d:%d up to %d:%d.\n", @2.first_line, @2.first_column, @2.last_line, @2.last_column); $$ = $1; @@ -134,7 +134,7 @@ entry : ISDN_TOKEN_NAME '=' value if (!($$ = (isdn_tree_node_t*) malloc(sizeof(isdn_tree_node_t)))) { - fprintf(stderr, "Error: Out of memory.\n"); + errprintf("Error: Out of memory.\n"); exit(1); } $$->type = ISDN_NODE_TYPE_ENTRY; @@ -147,7 +147,7 @@ entry : ISDN_TOKEN_NAME '=' value if (!($$ = (isdn_tree_node_t*) malloc(sizeof(isdn_tree_node_t)))) { - fprintf(stderr, "Error: Out of memory.\n"); + errprintf("Error: Out of memory.\n"); exit(1); } $$->type = ISDN_NODE_TYPE_SUBSECTION; @@ -171,7 +171,7 @@ value : ISDN_TOKEN_VALUE */ void isdn_error(const char *message) { if (debug) - fprintf(stderr, + errprintf( "Warning: Parsing isdn options file: %d:%d: %s.\n", isdn_lloc.first_line, isdn_lloc.first_column, message); } diff --git a/src/isdntree.c b/src/isdntree.c index 774918e..2cac790 100644 --- a/src/isdntree.c +++ b/src/isdntree.c @@ -27,6 +27,7 @@ /* own headers */ #include "isdntree.h" +#include "globals.h" isdn_tree_node_t* isdn_tree; @@ -51,7 +52,7 @@ static void isdn_tree_dump_list(isdn_tree_node_t* list, int indent) { isdn_tree_dump_list(list->content.subsection, indent + 2); break; default: - fprintf(stderr, "Unknown ISDN_NODE_TYPE\n"); + errprintf("Unknown ISDN_NODE_TYPE\n"); } list = list->next; @@ -87,7 +88,7 @@ static void isdn_tree_free_list(isdn_tree_node_t* list) { isdn_tree_free_list(list->content.subsection); break; default: - fprintf(stderr, "Unknown ISDN_NODE_TYPE\n"); + errprintf("Unknown ISDN_NODE_TYPE\n"); } temp = list->next; diff --git a/src/llcheck.c b/src/llcheck.c index febd821..e00ecf5 100644 --- a/src/llcheck.c +++ b/src/llcheck.c @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -127,7 +128,9 @@ static void llcheck_quit(GtkObject *window) { GtkWidget *bar = gtk_object_get_data(window, "bar"); /* remove own and restore old handlers */ +#ifdef USE_GTK_LOOP gtk_input_remove(session->gtk_audio_input_tag); +#endif session_io_handlers_start(session); /* stop effect */ @@ -139,95 +142,16 @@ static void llcheck_quit(GtkObject *window) { session_set_state(session, STATE_READY); } -/* - * gdk callback on audio input - * - * input: widget: the llcheck_bar - */ -static void llcheck_handle_audio_input(gpointer widget, gint fd _U_, - GdkInputCondition condition _U_) { - session_t *session = gtk_object_get_data(widget, "session"); - int i, got; - int max = 0; - unsigned char sample; - - got = read(session->audio_fd_in, session->audio_inbuf, - session->fragment_size_in); - - if (got != -1) { - for (i = 0; i < got; - i += session->audio_sample_size_in) { - - if (session->audio_sample_size_in == 1) { - sample = session->audio_LUT_analyze[ - session->audio_LUT_out[(int)(session->audio_inbuf[i])]]; - } else { /* audio_sample_size == 2 */ - /* multiple byte samples are used "little endian" in int - to look up in LUT (see mediation_makeLUT) */ - sample = session->audio_LUT_analyze[ - session->audio_LUT_out[(int)(session->audio_inbuf[i]) | - (int)(session->audio_inbuf[i+1]) << 8]]; - } - - if (abs((int)sample - 128) > max) - max = abs((int)sample - 128); - } - - llcheck_bar_set(widget, (double)max / 128); - - } else { - switch (errno) { - case EAGAIN: - fprintf(stderr, - "llcheck_handle_audio_input: " - "EAGAIN - no data immediately available.\n"); - break; - case EBADF: - fprintf(stderr, - "llcheck_handle_audio_input: " - "EBADF - invalid file descriptor.\n"); - break; - case EINTR: - fprintf(stderr, - "llcheck_handle_audio_input: EINTR - interrupted by signal.\n"); - break; - case EIO: - fprintf(stderr, - "llcheck_handle_audio_input: EIO - hardware error.\n"); - break; - } - } -} - /* * called when sound is requested in level check window * * input: widget: (play) button * data: session pointer */ -static void llcheck_play(GtkWidget *widget, gpointer data) { +static void llcheck_play(GtkWidget *widget _U_, gpointer data) { session_t *session = (session_t *)data; - GtkWidget *bar = gtk_object_get_data(GTK_OBJECT(widget), "bar"); - if (session->effect != EFFECT_NONE) { /* already playing -> stop feeding */ - gtk_input_remove(session->effect_tag); - } - - /* carefully reset audio */ - gtk_input_remove(session->gtk_audio_input_tag); - session_reset_audio(session); - session->gtk_audio_input_tag = gtk_input_add_full(session->audio_fd_in, - GDK_INPUT_READ, - llcheck_handle_audio_input, - NULL, - (gpointer) bar, - NULL); - - /* start recording (again) */ - read(session->audio_fd_in, session->audio_inbuf, - session->fragment_size_in); - - /* finally, start playing */ + /* start playing effect */ session_effect_start(session, EFFECT_TEST); } @@ -313,7 +237,7 @@ void llcheck_bar_set(GtkWidget *bar, double max) { (GtkWidget *)gtk_object_get_data(GTK_OBJECT(bar), "pbar2"); struct history_t *history = (struct history_t *)gtk_object_get_data(GTK_OBJECT(bar), "history"); - double maxmax = history_append(&history, max); + double maxmax = history ? history_append(&history, max) : max; int width = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(bar), "width")); /* saving additional width (bar->allocation.width is valid only with shown widgets) */ @@ -432,17 +356,8 @@ void llcheck(GtkWidget *widget _U_, gpointer data, guint action _U_) { gtk_window_set_modal(GTK_WINDOW(window), TRUE); - /* remove old and set up own audio input handler */ - session_io_handlers_stop(session); - session->gtk_audio_input_tag = gtk_input_add_full(session->audio_fd_in, - GDK_INPUT_READ, - llcheck_handle_audio_input, - NULL, - (gpointer) bar, - NULL); - - read(session->audio_fd_in, session->audio_inbuf, /* start recording */ - session->fragment_size_in); + /* start empty effect */ + session_effect_start(session, EFFECT_EMPTY); /* show everything */ gtk_widget_show(window); diff --git a/src/llcheck.h b/src/llcheck.h index 954280d..adaa962 100644 --- a/src/llcheck.h +++ b/src/llcheck.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/mediation.c b/src/mediation.c index 52832eb..006f3fd 100644 --- a/src/mediation.c +++ b/src/mediation.c @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,9 +22,6 @@ * * * - * NOTE: - * * for performance reasons, separate recording buffers are filled while - * mediating */ #include "config.h" @@ -55,25 +53,41 @@ #include "fxgenerator.h" #include "recording.h" -/* - * allocate memory and build look-up-tables for audio <-> isdn conversion + +/*! + * @brief Invert bits in a byte. * - * input: - * format_in, LUT_in: used audio format and pointer to look-up-table - * for conversion of isdn -> audio - * format_out, LUT_out: the same for audio -> isdn - * LUT_generate: table for conversion of 8 bit unsigned -> isdn - * LUT_analyze: table for conversion of isdn -> 8 bit unsigned + * For the fun of it, ISDN doesn't use plain A-law, but instead uses + * bit-inverse A-law. I.e., instead of bit order 01234567, it sends + * 76543210. This function converts it to bit-inverse. * - * return: 0 on success, -1 otherwise - * - * NOTE: the caller has to free the memory of LUT_* itself + * @param c byte to invert. + * @return inverted byte. */ +static unsigned char bitinverse(unsigned char c); + +/*--------------------------------------------------------------------------*/ + +static unsigned char bitinverse(unsigned char c) +{ + return + ((c >> 7) & 0x1) | + ((c >> 5) & 0x2) | + ((c >> 3) & 0x4) | + ((c >> 1) & 0x8) | + ((c << 1) & 0x10) | + ((c << 3) & 0x20) | + ((c << 5) & 0x40) | + ((c << 7) & 0x80); +} + +/*--------------------------------------------------------------------------*/ + int mediation_makeLUT(int format_in, unsigned char **LUT_in, int format_out, unsigned char **LUT_out, unsigned char **LUT_generate, unsigned char **LUT_analyze, - short **LUT_ulaw2short) { + short **LUT_alaw2short) { int sample_size_in; int sample_size_out; int buf_size_in; @@ -99,368 +113,278 @@ int mediation_makeLUT(int format_in, unsigned char **LUT_in, return -1; if (!(*LUT_analyze = (unsigned char*) malloc (256))) return -1; - if (!(*LUT_ulaw2short = (short*) malloc (256*sizeof(short)))) + if (!(*LUT_alaw2short = (short*) malloc (256*sizeof(short)))) return -1; /* Calculation */ for (i = 0; i < buf_size_in; i += sample_size_in) { /* isdn -> audio */ switch(format_in) { - case AFMT_U8: - (*LUT_in)[i] = (unsigned char)((ulaw2linear((unsigned char)i) / 256 & + case SND_PCM_FORMAT_U8: + (*LUT_in)[i] = (unsigned char)((alaw2linear((unsigned char)i) / 256 & 0xff) ^ 0x80); break; - case AFMT_S8: - (*LUT_in)[i] = (unsigned char)(ulaw2linear((unsigned char)i) / 256 & + case SND_PCM_FORMAT_S8: + (*LUT_in)[i] = (unsigned char)(alaw2linear((unsigned char)i) / 256 & 0xff); break; - case AFMT_MU_LAW: + case SND_PCM_FORMAT_MU_LAW: + (*LUT_in)[i] = linear2ulaw(alaw2linear((unsigned char)i)); + break; + + case SND_PCM_FORMAT_A_LAW: (*LUT_in)[i] = (unsigned char)i; break; - case AFMT_S16_LE: - sample = ulaw2linear((unsigned char)(i / 2)); + case SND_PCM_FORMAT_S16_LE: + sample = alaw2linear((unsigned char)(i / 2)); (*LUT_in)[i] = (unsigned char)(sample & 0xff); (*LUT_in)[i+1] = (unsigned char)(sample >> 8 & 0xff); break; - case AFMT_S16_BE: - sample = ulaw2linear((unsigned char)(i / 2)); + case SND_PCM_FORMAT_S16_BE: + sample = alaw2linear((unsigned char)(i / 2)); (*LUT_in)[i+1] = (unsigned char)(sample & 0xff); (*LUT_in)[i] = (unsigned char)(sample >> 8 & 0xff); break; - case AFMT_U16_LE: - sample = ulaw2linear((unsigned char)(i / 2)); + case SND_PCM_FORMAT_U16_LE: + sample = alaw2linear((unsigned char)(i / 2)); (*LUT_in)[i] = (unsigned char)(sample & 0xff); (*LUT_in)[i+1] = (unsigned char)((sample >> 8 & 0xff) ^ 0x80); break; - case AFMT_U16_BE: - sample = ulaw2linear((unsigned char)(i / 2)); + case SND_PCM_FORMAT_U16_BE: + sample = alaw2linear((unsigned char)(i / 2)); (*LUT_in)[i+1] = (unsigned char)(sample & 0xff); (*LUT_in)[i] = (unsigned char)((sample >> 8 & 0xff) ^ 0x80); break; default: - fprintf(stderr, - "Error: " - "Unsupported format appeared while building input LUT.\n"); + errprintf("MEDIATION: " + "Unsupported in format %d appeared while building input LUT.\n", + format_in); return -1; } } for (i = 0; i < buf_size_out; i++) { /* audio -> isdn */ switch(format_out) { - case AFMT_U8: - (*LUT_out)[i] = linear2ulaw((i - 128) * 256); + case SND_PCM_FORMAT_U8: + (*LUT_out)[i] = linear2alaw((i - 128) * 256); break; - case AFMT_S8: - (*LUT_out)[i] = linear2ulaw(i * 256); + case SND_PCM_FORMAT_S8: + (*LUT_out)[i] = linear2alaw(i * 256); break; - case AFMT_MU_LAW: + case SND_PCM_FORMAT_MU_LAW: + (*LUT_out)[i] = linear2alaw(ulaw2linear((unsigned char)i)); + break; + + case SND_PCM_FORMAT_A_LAW: (*LUT_out)[i] = (unsigned char)i; break; /* next 4 cases: input int i stores first buffer byte in low byte */ - case AFMT_S16_LE: - (*LUT_out)[i] = linear2ulaw((int)(signed char)(i >> 8) << 8 | + case SND_PCM_FORMAT_S16_LE: + (*LUT_out)[i] = linear2alaw((int)(signed char)(i >> 8) << 8 | (int)(i & 0xff)); break; - case AFMT_S16_BE: - (*LUT_out)[i] = linear2ulaw((int)(signed char)(i & 0xff) << 8 | + case SND_PCM_FORMAT_S16_BE: + (*LUT_out)[i] = linear2alaw((int)(signed char)(i & 0xff) << 8 | (int)(i >> 8)); break; - case AFMT_U16_LE: - (*LUT_out)[i] = linear2ulaw(i - 32768); + case SND_PCM_FORMAT_U16_LE: + (*LUT_out)[i] = linear2alaw(i - 32768); break; - case AFMT_U16_BE: - (*LUT_out)[i] = linear2ulaw(((i & 0xff) << 8 | i >> 8) - 32768); + case SND_PCM_FORMAT_U16_BE: + (*LUT_out)[i] = linear2alaw(((i & 0xff) << 8 | i >> 8) - 32768); break; default: - fprintf(stderr, - "Error: " - "Unsupported format appeared while building output LUT.\n"); + errprintf("MEDIATION: " + "Unsupported out format %d appeared while building output LUT.\n", + format_out); return -1; } } for (i = 0; i < 256; i++) { /* 8 bit unsigned -> isdn -> 8 bit unsigned */ - (*LUT_generate)[i] = linear2ulaw((i - 128) * 256); - (*LUT_ulaw2short)[i] = s = ulaw2linear((unsigned char)i); /* ulaw->short */ + (*LUT_generate)[i] = linear2alaw((i - 128) * 256); + (*LUT_alaw2short)[i] = s = alaw2linear((unsigned char)i); /* alaw->short */ (*LUT_analyze)[i] = (unsigned char)((s / 256 & 0xff) ^ 0x80); } return 0; } -/* - * writes buffer carefully out to file (ttyI / audio device) - * - * returns 0 on success, -1 otherwise (write error) - */ -int write_buf(int fd, unsigned char *outbuf, int outbuf_size) { - int towrite = outbuf_size; - int written = 0; - - /* write until everything has been written */ - while (towrite && (written != -1 || errno == EAGAIN)) { - written = write(fd, &outbuf[outbuf_size - towrite], towrite); - if (debug >= 2) - fprintf(stderr, "Wrote %d bytes to device.\n", written); - if (written != -1) - towrite -= written; - else - if (errno == EAGAIN) { - if (debug) - fprintf(stderr, "write_buf: EAGAIN\n"); - ant_sleep(SHORT_INTERVAL); - } - } - if (written == -1) { - perror("write_buf"); - return -1; - } - return 0; -} +/*--------------------------------------------------------------------------*/ /* XXX: smooth samples when converting speeds in next 2 functions */ -/* - * process isdn input from ttyI to sound device - * - * to be called after select found block to read in isdn file descriptor - */ -void process_isdn_source(session_t *session) { - int got, i, j; +void convert_isdn_to_audio(session_t *session, + unsigned char *isdn_buf, + unsigned int isdn_size, + unsigned char *audio_buf, + unsigned int *audio_size, + short *rec_buf, + unsigned int inverse) { + unsigned int i, j; unsigned char inbyte; /* byte read from ttyI */ - int to_process; /* number of samples to process + unsigned int to_process; /* number of samples to process (according to ratio / ratio_support_count) */ + unsigned int outptr; /* output sample pointer */ unsigned char sample; /* 8 bit unsigned sample */ + double llratio; /* line level falloff ratio */ int max = 0; /* for llcheck */ - short s; /* libsndfile sample data */ - - got = read(session->isdn_fd, session->isdn_inbuf, - session->isdn_inbuf_size); + outptr = 0; - if (debug >= 2) - fprintf(stderr, "From isdn: got %d bytes.\n", got); + dbgprintf(3, "MEDIATION: From isdn: got %d bytes.\n", isdn_size); - if (got != -1) { - for (i = 0; i < got; i++) { - inbyte = session->isdn_inbuf[i]; - if (!session->escape == (inbyte != DLE)) { - /* normal mode or last byte was an escape in DLE mode */ - - /* input line level check */ - sample = session->audio_LUT_analyze[inbyte]; - if (abs((int)sample - 128) > max) - max = abs((int)sample - 128); + for (i = 0; i < isdn_size; i++) { + inbyte = isdn_buf[i]; + if (inverse) + inbyte = bitinverse(inbyte); - /* recording */ - if (session->option_record) { - if (session->option_record_remote) - s = session->audio_LUT_ulaw2short[inbyte]; - else - s = 0; - session->rec_buf_remote[session->rec_buf_remote_index++] = s; - if (session->rec_buf_remote_index >= session->rec_buf_remote_size) { - if (recording_write(session->recorder, session->rec_buf_remote, - session->rec_buf_remote_size, RECORDING_REMOTE)) - fprintf(stderr, "Warning: Recording (remote) error.\n"); - session->rec_buf_remote_index = 0; - } - } + /* store sample for recording */ + rec_buf[i] = session->option_record_remote ? + session->audio_LUT_alaw2short[inbyte] : 0; - /* touchtone to audio: after llcheck to monitor other end */ - if (session->touchtone_countdown_audio > 0) { - inbyte = fxgenerate(session, EFFECT_TOUCHTONE, - session->touchtone_index, - (double)session->touchtone_countdown_audio / - ISDN_SPEED); /* playing reverse is ok */ - session->touchtone_countdown_audio--; - } + /* input line level check */ + sample = session->audio_LUT_analyze[inbyte]; + if (abs((int)sample - 128) > max) + max = abs((int)sample - 128); - /* mediation */ - to_process = (int)floor((double)(session->samples_in + 1) * - session->ratio_in) - - (int)floor((double)session->samples_in * - session->ratio_in); - /* printf("isdn -> audio: to_process == %d\n", to_process); */ - for (j = 0; j < to_process; j++) { - if (session->audio_sample_size_out == 1) { - session->audio_outbuf[session->audio_outbuf_index++] = - session->audio_LUT_in[(int)inbyte]; - } else { /* audio_sample_size == 2 */ - session->audio_outbuf[session->audio_outbuf_index++] = - session->audio_LUT_in[(int)inbyte * 2]; - session->audio_outbuf[session->audio_outbuf_index++] = - session->audio_LUT_in[(int)inbyte * 2 + 1]; - } - if (session->audio_outbuf_index >= session->fragment_size_out) { - if (write_buf(session->audio_fd_out, session->audio_outbuf, - session->fragment_size_out)) - session->aborted = 1; - session->audio_outbuf_index = 0; - } - } - - session->samples_in++; - - if (session->escape) { - session->escape = 0; - if (debug) fprintf(stderr, "debug: escape mode off after 2x DLE.\n"); - } - } else if (!session->escape && inbyte == DLE) { /* new escape: DLE */ - session->escape = 1; - if (debug) - fprintf(stderr, "debug: ttyI DLE escape mode on.\n"); - } else /* i.e. if (*escape) */ { - if (inbyte == DC4 || inbyte == ETX) { - session->hangup = 1; - } else {/* else: must be a touchtone: ignored */ - if (debug) { - if ((inbyte >= '0' && inbyte <= '9') || inbyte == '#' || - inbyte =='*' || (inbyte >= 'A' && inbyte <= 'D')) - fprintf(stderr, "Touchtone %c received.\n", inbyte); - else - fprintf(stderr, "Warning: Unknown escape sequence received.\n"); - } - } - - session->escape = 0; - if (debug) fprintf(stderr, - "debug: escape mode off after special char.\n"); - } + /* touchtone to audio: after llcheck to monitor other end */ + if (session->touchtone_countdown_audio > 0) { + inbyte = fxgenerate(session, EFFECT_TOUCHTONE, + session->touchtone_index, + (double)session->touchtone_countdown_audio / + ISDN_SPEED); /* playing reverse is ok */ + session->touchtone_countdown_audio--; } - llcheck_bar_set(session->llcheck_in, (double)max / 128); - - } else { - fprintf(stderr, "process_isdn_source: read error (return -1).\n"); + /* mediation */ + to_process = (int)floor((double)(i + 1) * session->ratio_in) - + (int)floor((double)i * session->ratio_in); + /* printf("isdn -> audio: to_process == %d\n", to_process); */ + for (j = 0; j < to_process; j++) { + if (session->audio_sample_size_out == 1) { + audio_buf[outptr++] = + session->audio_LUT_in[(int)inbyte]; + } else { /* audio_sample_size == 2 */ + audio_buf[outptr++] = + session->audio_LUT_in[(int)inbyte * 2]; + audio_buf[outptr++] = + session->audio_LUT_in[(int)inbyte * 2 + 1]; + } + } } + + if (session->option_record && inverse) { + recording_write(session->recorder, rec_buf, isdn_size, RECORDING_REMOTE); + } + + llratio = isdn_size / 400.0; + if (llratio > 1.0) + llratio = 1.0; + + session->llcheck_in_state = + session->llcheck_in_state * (1.0 - llratio) + + ((double)max / 128) * llratio; + dbgprintf(4, "MEDIATION: Audio out gain: %.3f\n", session->llcheck_in_state); + + *audio_size = outptr; } -/* - * process audio input from sound device to isdn tty - * - * to be called after select found fragment(s) to read from - */ -void process_audio_source(session_t *session) { - int i, j, got, n; - unsigned char sample; /* the ulaw sample */ - short s = 0; /* libsndfile sample data */ - /* the ulaw sample when muted: */ +/*--------------------------------------------------------------------------*/ + +void convert_audio_to_isdn(session_t *session, + unsigned char *audio_buf, + unsigned int audio_size, + unsigned char *isdn_buf, + unsigned int *isdn_size, + short *rec_buf) { + unsigned int i, j; + unsigned int outptr; /* output sample pointer */ + unsigned char sample; /* the alaw sample */ + /* the alaw sample when muted: */ unsigned char zero = session->audio_LUT_generate[128]; - int to_process; /* number of samples to process + unsigned int to_process; /* number of samples to process (according to ratio / ratio_support_count) */ unsigned char sampleu8; /* 8 bit unsigned sample */ + double llratio; /* line level falloff ratio */ int max = 0; /* for llcheck */ - got = read(session->audio_fd_in, session->audio_inbuf, - session->fragment_size_in); - - if (debug >= 2) - fprintf(stderr, "From audio: got %d bytes.\n", got); + outptr = 0; - if (got != -1) { - for (i = 0; i < got; - i += session->audio_sample_size_in, session->samples_out++) { - - to_process = (int)floor((double)(session->samples_out + 1) - * session->ratio_out) - - (int)floor((double)session->samples_out - * session->ratio_out); - /* printf("audio -> isdn: to_process == %d\n", to_process); */ - for (j = 0; j < to_process; j++) { - if (session->audio_sample_size_in == 1) { - sample = session->audio_LUT_out[(int)(session->audio_inbuf[i])]; - } else { /* audio_sample_size == 2 */ - /* multiple byte samples are used "little endian" in int - to look up in LUT (see mediation_makeLUT) */ - sample = session->audio_LUT_out[(int)(session->audio_inbuf[i]) | - (int)(session->audio_inbuf[i+1]) - << 8]; - } - - /* touchtone to isdn: before llcheck to monitor it */ - if (session->touchtone_countdown_isdn > 0) { - sample = fxgenerate(session, EFFECT_TOUCHTONE, - session->touchtone_index, - (double)session->touchtone_countdown_isdn / - ISDN_SPEED /* playing reverse is ok */ ); - session->touchtone_countdown_isdn--; - } + dbgprintf(3, "MEDIATION: From audio: got %d bytes.\n", audio_size); - if (session->option_muted) /* zero if muted */ - sample = zero; + for (i = 0; i < audio_size; + i += session->audio_sample_size_in) { - /* input line level check */ - sampleu8 = session->audio_LUT_analyze[sample]; - if (abs((int)sampleu8 - 128) > max) - max = abs((int)sampleu8 - 128); - - /* recording */ - if (session->option_record) { - if (session->option_record_local) - s = session->audio_LUT_ulaw2short[sample]; - else - s = 0; - session->rec_buf_local[session->rec_buf_local_index++] = s; - if (session->rec_buf_local_index >= session->rec_buf_local_size) { - if (recording_write(session->recorder, session->rec_buf_local, - session->rec_buf_local_size, RECORDING_LOCAL)) - fprintf(stderr, "Warning: Recording (local) error.\n"); - session->rec_buf_local_index = 0; - } - } - - n = (sample == DLE) ? 2 : 1; /* again if DLE escape */ - while (n > 0) { - session->isdn_outbuf[session->isdn_outbuf_index++] = sample; - if (session->isdn_outbuf_index >= session->isdn_outbuf_size) { - /* write outbuf out */ - if (write_buf(session->isdn_fd, session->isdn_outbuf, - session->isdn_outbuf_size)) - session->aborted = 1; - session->isdn_outbuf_index = 0; - } - n--; - } + to_process = (int)floor((double)(outptr + 1) + * session->ratio_out) - + (int)floor((double)outptr + * session->ratio_out); + /* printf("audio -> isdn: to_process == %d\n", to_process); */ + for (j = 0; j < to_process; j++) { + if (session->audio_sample_size_in == 1) { + sample = session->audio_LUT_out[(int)(audio_buf[i])]; + } else { /* audio_sample_size == 2 */ + /* multiple byte samples are used "little endian" in int + to look up in LUT (see mediation_makeLUT) */ + sample = session->audio_LUT_out[(int)(audio_buf[i]) | + ((int)(audio_buf[i+1]) << 8)]; } + + /* touchtone to isdn: before llcheck to monitor it */ + if (session->touchtone_countdown_isdn > 0) { + sample = fxgenerate(session, EFFECT_TOUCHTONE, + session->touchtone_index, + (double)session->touchtone_countdown_isdn / + ISDN_SPEED /* playing reverse is ok */ ); + session->touchtone_countdown_isdn--; + } + + if (session->option_muted) /* zero if muted */ + sample = zero; + + /* input line level check */ + sampleu8 = session->audio_LUT_analyze[sample]; + if (abs((int)sampleu8 - 128) > max) + max = abs((int)sampleu8 - 128); + + /* store sample for recording */ + rec_buf[outptr] = session->option_record_local ? + session->audio_LUT_alaw2short[sample] : 0; + + isdn_buf[outptr++] = bitinverse(sample); } - llcheck_bar_set(session->llcheck_out, (double)max / 128); - - } else { - switch (errno) { - case EAGAIN: - if (debug) - fprintf(stderr, - "process_audio_source: " - "EAGAIN - no data immediately available (that's ok).\n"); - break; - case EBADF: - fprintf(stderr, - "process_audio_source: EBADF - invalid file descriptor.\n"); - break; - case EINTR: - fprintf(stderr, - "process_audio_source: EINTR - interrupted by signal.\n"); - break; - case EIO: - fprintf(stderr, - "process_audio_source: EIO - hardware error.\n"); - break; - } } + + if (session->option_record) { + recording_write(session->recorder, rec_buf, outptr, RECORDING_LOCAL); + } + + llratio = outptr / 400.0; + if (llratio > 1.0) + llratio = 1.0; + + session->llcheck_out_state = + session->llcheck_out_state * (1.0 - llratio) + + ((double)max / 128) * llratio; + dbgprintf(4, "MEDIATION: Audio in gain: %.3f\n", session->llcheck_out_state); + + *isdn_size = outptr; } + +/*--------------------------------------------------------------------------*/ diff --git a/src/mediation.h b/src/mediation.h index da2bcd1..b142f35 100644 --- a/src/mediation.h +++ b/src/mediation.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,17 +24,60 @@ #include "session.h" -/* mediation internal recording buffer (number of shorts): */ -#define MEDIATION_RECBUFSIZE 16384 - -extern int finished; -extern int hangup; - +/*! + * @brief Generate audio sample conversion tables. + * + * + * @note The caller is responsible to free the memory allocated for + * conversion tables! + * + * @param format_in audio input format. + * @param LUT_in conversion table to convert from A-law to audio. + * @param format_out audio output format. + * @param LUT_out conversion table to convert from audio to A-law. + * @param LUT_generate conversion table from signed 8-bit to A-law. + * @param LUT_analyze conversion table from A-law to signed 8-bit. + * @param LUT_alaw2short conversion table from A-law to short. + * @return 0 on success, -1 otherwise. + */ int mediation_makeLUT(int format_in, unsigned char **LUT_in, int format_out, unsigned char **LUT_out, unsigned char **LUT_generate, unsigned char **LUT_analyze, - short **LUT_ulaw2short); -int write_buf(int fd, unsigned char *outbuf, int outbuf_size); -void process_isdn_source(session_t *session); -void process_audio_source(session_t *session); + short **LUT_alaw2short); + +/*! + * @brief Convert ISDN data to audio data. + * + * @param session current session. + * @param isdn_buf ISDN data buffer (A-law or bit-inverse A-law). + * @param isdn_size number of samples in ISDN buffer. + * @param audio_buf destination buffer for audio data. + * @param audio_size filled with size of audio data in bytes. + * @param rec_buf recording buffer as temporary to hold at least isdn_size shorts. + * @param bitinverse if true, ISDN data are bit-inverse A-law, otherwise A-law. + */ +void convert_isdn_to_audio(session_t *session, + unsigned char *isdn_buf, + unsigned int isdn_size, + unsigned char *audio_buf, + unsigned int *audio_size, + short *rec_buf, + unsigned int bitinverse); + +/*! + * @brief Convert audio data to ISDN data. + * + * @param session current session. + * @param audio_buf buffer with audio data. + * @param audio_size size of audio data in bytes. + * @param isdn_buf destination ISDN data buffer (bit-inverse A-law). + * @param isdn_size filled with number of samples written to ISDN buffer. + * @param rec_buf recording buffer as temporary to hold at least isdn_size shorts. + */ +void convert_audio_to_isdn(session_t *session, + unsigned char *audio_buf, + unsigned int audio_size, + unsigned char *isdn_buf, + unsigned int *isdn_size, + short *rec_buf); diff --git a/src/recording.c b/src/recording.c index 6e9f8f8..e24bae1 100644 --- a/src/recording.c +++ b/src/recording.c @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +28,7 @@ #include #include #include +#include /* sndfile audio file reading/writing library */ #include @@ -37,25 +39,19 @@ #include "recording.h" #include "util.h" -/* recorder internal buffer size (number of items, 1 item = 2 shorts): */ -#define RECORDING_BUFSIZE 16384 +/*--------------------------------------------------------------------------*/ + +int recording_init(struct recorder_t *recorder) +{ + memset(recorder, 0, sizeof(struct recorder_t)); + return 0; +} + +/*--------------------------------------------------------------------------*/ -/* - * Carefully opens a file and prepares recorder - * * when the file exists, new data will be appended - * - * File format: - * - * input: - * recorder: struct to be filled with recorder state for recording session - * until recording_close() - * filename: the base file name. It will be expanded - * with full path and extension - * - * returns: 0 on success, -1 otherwise - */ int recording_open(struct recorder_t *recorder, char *filename, - enum recording_format_t format) { + enum recording_format_t format) +{ SF_INFO sfinfo; char *homedir; char *fn; @@ -64,26 +60,24 @@ int recording_open(struct recorder_t *recorder, char *filename, touch_dotdir(); if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("RECORD: Couldn't get home dir.\n"); return -1; } if (asprintf(&fn, "%s/." PACKAGE "/recordings", homedir) < 0) { - fprintf(stderr, "Warning: " + errprintf("RECORD: " "recording_open: Couldn't allocate memory for directory name.\n"); return -1; } if (touch_dir(fn) < 0) { - if (debug) - fprintf(stderr, - "Warning: recording_open: Can't reach directory %s.\n", fn); + errprintf("RECORD: recording_open: Can't reach directory %s.\n", fn); return -1; } free(fn); if (asprintf(&fn, "%s/." PACKAGE "/recordings/%s.%s", homedir, filename, format & RECORDING_FORMAT_WAV ? "wav" : "aiff") < 0) { - fprintf(stderr, "Warning: " - "recording_open: Couldn't allocate memory for directory name.\n"); + errprintf("RECORD: " + "recording_open: Couldn't allocate memory for file name.\n"); return -1; } @@ -94,146 +88,187 @@ int recording_open(struct recorder_t *recorder, char *filename, sfinfo.channels = 2; sfinfo.samplerate = ISDN_SPEED; if (!(recorder->sf = sf_open(fn, SFM_WRITE, &sfinfo))) { - fprintf(stderr, "recording_open: sf_open (file creation) error.\n"); + errprintf("RECORD: recording_open: sf_open (file creation) error.\n"); return -1; } } else { /* file already exists */ sfinfo.format = 0; if (!(recorder->sf = sf_open(fn, SFM_RDWR, &sfinfo))) { - fprintf(stderr, "recording_open: sf_open (reopen) error.\n"); + errprintf("RECORD: recording_open: sf_open (reopen) error.\n"); return -1; } if (sf_seek(recorder->sf, 0, SEEK_END) == -1) { - fprintf(stderr, "recording_open: sf_seek error.\n"); + errprintf("RECORD: recording_open: sf_seek error.\n"); return -1; } } recorder->filename = fn; - if (!(recorder->queue = - (struct recqueue_t *) malloc (sizeof(struct recqueue_t)))) { - fprintf(stderr, "recording_open: recorder->queue malloc error.\n"); - return -1; - } - if (!(recorder->queue->buf = - (short *) malloc (RECORDING_BUFSIZE * sizeof(short) * 2))) { - fprintf(stderr, "recording_open: recorder->queue->buf malloc error.\n"); - return -1; - } - recorder->queue->next = NULL; - recorder->current_local = recorder->current_remote = recorder->queue; - recorder->localindex = 0; - recorder->remoteindex = 0; + + /* initialize streaming buffers */ + recorder->last_write = 0; + memset(&recorder->channel_local, 0, sizeof(rec_channel_t)); + memset(&recorder->channel_remote, 0, sizeof(rec_channel_t)); + + /* NOTE: this has to be the last assignment, as it starts recording */ + recorder->start_time = microsec_time(); return 0; } -/* - * writes specified number of shorts to file - * - * input: - * recorder: struct with sound file state - * buf, size: the buffer of shorts with its size (number of shorts) - * channel: RECORDING_LOCAL or RECORDING_REMOTE - * - * returns: 0 on success, -1 otherwise - */ +/*--------------------------------------------------------------------------*/ + int recording_write(struct recorder_t *recorder, short *buf, int size, - enum recording_channel_t channel) { - int i; - struct recqueue_t **link_this; /* alias for current_local or current_remote */ - int *buf_index_this; /* alias for localindex or remoteindex */ - struct recqueue_t *temp; - - if (channel == RECORDING_LOCAL) { - link_this = &recorder->current_local; - buf_index_this = &recorder->localindex; + enum recording_channel_t channel) +{ + int64_t start = recorder->start_time; + int64_t current, startpos, position; + int bufpos, split, delta; + rec_channel_t *buffer; + + if (start == 0) + return 0; /* not enabled */ + if (size < 1) { + errprintf("RECORD: recording_write: Trying to record with wrong size %d\n", + size); + return -1; + } + + /* determine channel */ + switch (channel) + { + case RECORDING_LOCAL: + buffer = &recorder->channel_local; + break; + case RECORDING_REMOTE: + buffer = &recorder->channel_remote; + break; + default: + errprintf("RECORD: recording_write: Recording to unknown channel %d requested\n", + (int) channel); + return -1; + } + + /* compute position where to start write */ + current = microsec_time() - start; + if (current < 0) + return 0; /* should never happen! */ + int64_t endpos = current * ISDN_SPEED / 1000000LL; + startpos = endpos - size; + position = buffer->position; + if (startpos >= position - RECORDING_JITTER && startpos <= position + RECORDING_JITTER) { + /* position falls within recording jitter, adjust it to prevent cracks in recording */ + startpos = position; + endpos = position + size; + } + if (startpos < position) { + /* should not happen, but to be sure, skip samples at the beginning */ + delta = (int) position - startpos; + startpos = position; + buf += delta; + size -= delta; + if (size <= 0) + return 0; /* skipping too much, no data left */ + } + bufpos = startpos % RECORDING_BUFSIZE; + + dbgprintf(3, "RECORD: recording_write: data 0x%lx+%d to channel %d, pos %lld(%d)\n", + (long) buf, size, (int) channel, (long long) startpos, bufpos); + + /* copy data into buffer */ + if (bufpos + size <= RECORDING_BUFSIZE) { + /* all data can be copied at once */ + memcpy(buffer->buf + bufpos, buf, + size * sizeof(short)); } else { - link_this = &recorder->current_remote; - buf_index_this = &recorder->remoteindex; + /* must split to two copies */ + split = RECORDING_BUFSIZE - bufpos; + memcpy(buffer->buf + bufpos, buf, + split * sizeof(short)); + buf += split; + size -= split; + memcpy(buffer->buf, buf, size * sizeof(short)); } - for (i = 0; i < size; i++) { - (*link_this)->buf[(*buf_index_this)++ * 2 + channel] = buf[i]; - if (*buf_index_this >= RECORDING_BUFSIZE) { /* one buffer full */ - if ((*link_this)->next == NULL) { /* expand list */ - if (!((*link_this)->next = - (struct recqueue_t *) malloc (sizeof(struct recqueue_t)))) { - fprintf(stderr, "recording_write: buffer allocation error.\n"); - return -1; - } - if (!((*link_this)->next->buf = - (short *) malloc (RECORDING_BUFSIZE * sizeof(short) * 2))) { - fprintf(stderr, "recording_write: buffer allocation error.\n"); - return -1; - } - (*link_this)->next->next = NULL; - } - - *link_this = (*link_this)->next; /* go further in list */ - *buf_index_this = 0; - - if (recorder->queue != recorder->current_local && - recorder->queue != recorder->current_remote) { - /* write out buffer */ - sf_writef_short(recorder->sf, recorder->queue->buf, RECORDING_BUFSIZE); - - temp = recorder->queue; - recorder->queue = recorder->queue->next; - free(temp->buf); - free(temp); - } - } - } + /* mark buffer last position */ + buffer->position = endpos; return 0; } -/* - * finishes recording to file, writes remaining data from queue to file - * (eventually filling a dangling channeln with silence) - * - * input: - * recorder: struct with sound file state - * - * returns: 0 on success, -1 otherwise - */ -int recording_close(struct recorder_t *recorder) { - struct recqueue_t **link_this; /* aliases for the links and their buffer - */ - struct recqueue_t **link_other; - int *buf_index_this; /* indices to traverse */ - int *buf_index_other; - enum recording_channel_t channel; - struct recqueue_t *temp; - - /* set last, unavailable samples to zero */ - if (recorder->queue == recorder->current_local) { /* traverse local samples */ - link_this = &recorder->current_local; - link_other = &recorder->current_remote; - buf_index_this = &recorder->localindex; - buf_index_other = &recorder->remoteindex; - channel = RECORDING_LOCAL; - } else { /* traverse remote samples */ - link_this = &recorder->current_remote; - link_other = &recorder->current_local; - buf_index_this = &recorder->remoteindex; - buf_index_other = &recorder->localindex; - channel = RECORDING_REMOTE; +/*--------------------------------------------------------------------------*/ + +int recording_flush(struct recorder_t *recorder, unsigned int last) +{ + /* compute position to write up to */ + int64_t maxposition = recorder->channel_local.position; + int64_t tmp = recorder->channel_remote.position; + int64_t startposition = recorder->last_write; + short recbuf[RECORDING_BUFSIZE * 2]; /* sample buffer */ + int srcptr, dstptr, size; + + if (recorder->start_time == 0) + return 0; /* recording not active */ + + if (tmp > maxposition) + maxposition = tmp; + + if (startposition + (RECORDING_BUFSIZE * 7 / 8) < maxposition) { + /* underflow, skip samples */ + dbgprintf(2, "RECORD: recording_flush: underflow detected, start %lld, current %lld\n", + (long long) startposition, (long long) maxposition); + startposition = maxposition - (RECORDING_BUFSIZE * 7 / 8); } - while (*link_this != NULL) { /* traverse all chain links */ - while ((*link_this != *link_other && *buf_index_this < RECORDING_BUFSIZE) - || *buf_index_this < *buf_index_other) { /* all remaining samples */ - (*link_this)->buf[(*buf_index_this)++ * 2 + channel] = 0; - } - sf_writef_short(recorder->sf, (*link_this)->buf, *buf_index_this); - - *buf_index_this = 0; - temp = *link_this; - *link_this = (*link_this)->next; - free(temp->buf); - free(temp); + if (!last) { + /* skip last 1/8th of the buffer as jitter buffer */ + maxposition -= RECORDING_BUFSIZE / 8; } - - if (!sf_close(recorder->sf)) return -1; - free(recorder->filename); + + size = (int) (maxposition - startposition); + if (maxposition <= 0 || startposition >= maxposition || + (!last && size < RECORDING_BUFSIZE / 8)) + return 0; /* not enough data yet */ + + /* write samples between startposition and maxposition */ + dstptr = 0; + srcptr = startposition % RECORDING_BUFSIZE; + while (--size) { + recbuf[dstptr++] = recorder->channel_local.buf[srcptr]; + recorder->channel_local.buf[srcptr] = 0; + recbuf[dstptr++] = recorder->channel_remote.buf[srcptr]; + recorder->channel_remote.buf[srcptr] = 0; + + if (++srcptr >= RECORDING_BUFSIZE) + srcptr = 0; + } + sf_writef_short(recorder->sf, recbuf, dstptr / 2); + + /* update last sample pointer */ + recorder->last_write = maxposition; return 0; } +/*--------------------------------------------------------------------------*/ + +int recording_close(struct recorder_t *recorder) +{ + int result = 0; + + if (recorder->start_time) { + /* flush outstanding data and disable recording */ + if (recording_flush(recorder, 1) < 0) + result = -1; + recorder->start_time = 0; + + if (recorder->filename) { + free(recorder->filename); + recorder->filename = 0; + } + + /* close the recorder */ + if (sf_close(recorder->sf) != 0) + result = -1; + } + + return result; +} + +/*--------------------------------------------------------------------------*/ diff --git a/src/recording.h b/src/recording.h index ec495ec..60760d4 100644 --- a/src/recording.h +++ b/src/recording.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +33,19 @@ /* sndfile audio file reading/writing library */ #include +/*! + * @brief Recorder internal buffer size (number of items, 1 item = 2 shorts). + */ +#define RECORDING_BUFSIZE 32768 + +/*! + * @brief Recording jitter to compensate, about 25ms. + */ +#define RECORDING_JITTER 200 + +/*! + * @brief Recording formats. + */ enum recording_format_t { RECORDING_FORMAT_WAV = 0x10, RECORDING_FORMAT_AIFF = 0x20, @@ -43,30 +57,111 @@ enum recording_format_t { RECORDING_FORMAT_MINOR = 0x0F }; +/*! + * @brief Recording channels. + */ enum recording_channel_t { - RECORDING_LOCAL = 0, - RECORDING_REMOTE = 1 + RECORDING_LOCAL = 0, /*!< local channel */ + RECORDING_REMOTE = 1 /*!< remote channel */ }; -struct recqueue_t { - struct recqueue_t *next; - short *buf; -}; +/*! + * @brief Recording channel structure (cyclic buffer). + * + * Each recording data buffer computes its start and end positions based on + * sample count, current microtime, start microtime and ISDN speed. Then, + * it will write itself at appropriate position in recording buffer and update + * the position. + * + * Consumer of recording buffer will take last written position and minimum of + * the positions in buffers and write out the samples in between. The written + * samples will be zeroed out, to prevent repeating data. The consumer will + * be repeatedly called by main thread (via GTK timeout). + * + * Opening of recording device will set a flag to start writing to recording + * buffers. Closing of recording device will turn off recording flag and flush + * all not yet written data. + * + * In this way, it's possible to more or less guarantee real-time properties + * of ISDN and audio threads (as they will not wait for any disk writes) and + * also be reasonably sure the samples do make it to the disk. If they don't, + * because of CPU load, there will be skipping of samples on disk. So what... + */ +typedef struct { + int64_t position; /*!< one past last sample (absolute position) */ + short buf[RECORDING_BUFSIZE]; /*!< buffer with samples to record */ +} rec_channel_t; +/*! + * @brief Recorder state. + */ struct recorder_t { - SNDFILE *sf; /* sndfile state */ - char *filename; /* audio file name */ - struct recqueue_t *queue; /* buffer queue for communication with libsndfile */ - struct recqueue_t *current_local; /* current queue element for local input */ - struct recqueue_t *current_remote;/* current queue element for remote input */ - int localindex; /* indices into current block in queue */ - int remoteindex; + SNDFILE *sf; /*!< sndfile state */ + char *filename; /*!< audio file name */ + + int64_t start_time; /*!< recording start time (0=disabled) */ + rec_channel_t channel_local; /*!< recoding data channel for local data */ + rec_channel_t channel_remote; /*!< recoding data channel for remote data */ + int64_t last_write; /*!< position of last known write */ }; +/*! + * @brief Initialize recorder structure. + * + * @param recorder struct to be filled with recorder state for recording + * session until recording_close(). + * @return 0 on success, -1 otherwise. + */ +int recording_init(struct recorder_t *recorder); + +/*! + * @brief Opens a file and prepares recorder. + * + * If the file already exists, new data will be appended at the end. + * + * @param recorder struct to be filled with recorder state for recording + * session until recording_close(). + * @param filename the base file name. It will be expanded with full path + * and extension. + * @return 0 on success, -1 otherwise. + */ int recording_open(struct recorder_t *recorder, char *filename, enum recording_format_t format); + +/*! + * @brief Writes specified number of shorts to recording channel. + * + * @param recorder struct with sound file state. + * @param buf buffer to write. + * @param size buffer size. + * @param channel channel to write to (RECORDING_LOCAL or RECORDING_REMOTE). + * @return 0 on success, -1 otherwise. + */ int recording_write(struct recorder_t *recorder, short *buf, int size, - enum recording_channel_t channel); + enum recording_channel_t channel); + +/*! + * @brief Flushes current record buffer to the file. + * + * This function is to be called periodically by main thread to write + * outstanding sound samples to the sound file and make space in cyclic + * buffer for data pro + * + * @param recorder struct with sound file state. + * @param last if nonzero, flush all samples, otherwise account for jitter. + * @return 0 on success, -1 otherwise. + */ +int recording_flush(struct recorder_t *recorder, unsigned int last); + +/*! + * @brief Finishes recording to file. + * + * This function disables recording and writes remaining data from queue + * to file (eventually filling the lagging channel with silence). + * + * @param recorder struct with sound file state. + * @return 0 on success, -1 otherwise. + */ int recording_close(struct recorder_t *recorder); #endif /* recording.h */ diff --git a/src/server.c b/src/server.c index 1e808eb..4b6268a 100644 --- a/src/server.c +++ b/src/server.c @@ -47,7 +47,7 @@ char *server_local_socket_name(void) { if (asprintf(&filename, "%s/." PACKAGE "/%s", get_homedir(), SERVER_LOCAL_SOCKET_NAME) < 0) { - fprintf(stderr, + errprintf( "Warning: Couldn't allocate memory for history filename.\n"); return NULL; } @@ -83,7 +83,7 @@ int server_init(session_t *session) { /* bind (file) name to socket */ if (bind(local_sock, (struct sockaddr *) &local_name, size)) { perror("local bind"); - fprintf(stderr, + errprintf( "This program was possibly killed by accident on last run.\n" "In this case, try running it with option -r.\n"); return -1; @@ -118,15 +118,25 @@ void server_handle_local_input(gpointer data, gint fd _U_, bytes = read(sock, buffer, SERVER_INBUF_SIZE); if (bytes > 0) { /* got message */ - if (buffer[0] == LOCAL_MSG_CALL) { - if (debug) fprintf(stderr, "Request (%d bytes): call %s.\n", - bytes, &buffer[1]); - session_make_call(session, &buffer[1]); + switch (buffer[0]) { + case LOCAL_MSG_CALL: + dbgprintf(1, "Request (%d bytes): call %s.\n", + bytes, &buffer[1]); + session_make_call(session, &buffer[1]); + break; + case LOCAL_MSG_SUSPEND: + dbgprintf(1, "Request (%d bytes): sleep ISDN.\n", bytes); + session_activate_isdn(session, 0); + break; + case LOCAL_MSG_WAKEUP: + dbgprintf(1, "Request (%d bytes): wake up ISDN.\n", bytes); + session_activate_isdn(session, 1); + break; } } else if (bytes < 0) { /* error */ perror("local read"); } else { /* EOF */ - fprintf(stderr, "local read: EOF"); + errprintf("local read: EOF"); } close(sock); diff --git a/src/server.h b/src/server.h index 04e2b36..0888a8e 100644 --- a/src/server.h +++ b/src/server.h @@ -29,8 +29,13 @@ #define SERVER_CONNECTIONS_MAX_PENDING 5 #define SERVER_INBUF_SIZE 1024 +/*! + * @brief Messages passed from client to server. + */ enum local_msg_t { - LOCAL_MSG_CALL + LOCAL_MSG_CALL, + LOCAL_MSG_SUSPEND, + LOCAL_MSG_WAKEUP }; char *server_local_socket_name(void); diff --git a/src/session.c b/src/session.c index 460b536..cb41c8a 100644 --- a/src/session.c +++ b/src/session.c @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,12 +56,16 @@ #include "settings.h" #include "fxgenerator.h" #include "server.h" +#include "g711.h" -/* - * This is our session. Currently just one globally. +/*! + * @brief This is our session. Currently just one globally. */ session_t session; +/*! + * @brief State and button names for various session states. + */ struct state_data_t state_data[STATE_NUMBER] = { {N_("Ready"), N_("Dial"), 1,N_("Hang up"),0},/* STATE_READY */ {N_("RING"), N_("Answer"), 1,N_("Reject"), 1},/* STATE_RINGING */ @@ -71,20 +76,197 @@ struct state_data_t state_data[STATE_NUMBER] = { {N_("Playback"), N_("Pick up"),0,N_("Stop") ,1} /* STATE_PLAYBACK */ }; -/* - * Simply opens audio devices for specified session +/*! + * @brief Callback executed in session thread after hang up. * - * returns 0 on success, -1 on error + * @param context session object. + * @param error ISDN error code cast to pointer, 0 for no error. */ -int session_audio_open(session_t *session) { - if (debug) - fprintf(stderr, "session_audio_open: Opening audio device(s).\n"); +static void isdn_hangup_callback(void *context, void *error); + +/*! + * @brief Callback executed in session thread after ISDN connected. + * + * @param context session object. + * @param number remote number which has been connected. + */ +static void isdn_connect_callback(void *context, void *number); + +/*! + * @brief Thread main routine for audio input thread. + * + * @param data session. + */ +static gpointer handler_audio_input(gpointer data); + +/*! + * @brief Stop conversation threads. + * + * @param session session. + * @param self_hangup if nonzero, hung up by our side, otherwise by remote side. + */ +static void session_deinit_conversation(session_t *session, int self_hangup); + +/*! + * @brief Opens audio devices for specified session. + * + * @param session session. + * @return 0 on success, -1 on error. + */ +static int session_audio_open(session_t *session); + +/*! + * @brief Closes audio devices for specified session. + * + * @param session session. + * @return 0 on success, -1 on error. + */ +static int session_audio_close(session_t *session); + +/*! + * @brief Recover from audio error. + * + * @param session session. + * @param audio PCM handle. + * @param err ALSA error code. + * @return 0 on success, ALSA error code on error. + */ +static int session_snd_pcm_recover(session_t *session _U_, snd_pcm_t *audio, int err); + +/*! + * @brief Callback when connection established (in ISDN thread). + * + * @param context session. + * @param number remote party number (may be NULL). + */ +static void session_isdn_connected(void *context, char *number); + +/*! + * @brief Callback when ISDN data received (in ISDN thread). + * + * @param context session. + * @param data pointer to received data. + * @param length length of received data (in bytes). + */ +static void session_isdn_data(void *context, void *data, unsigned int length); + +/*! + * @brief Callback when connection disconnected (in ISDN thread). + * + * @param context session. + */ +static void session_isdn_disconnected(void *context); + +/*! + * @brief Callback when connection attempt fails with an error (in ISDN thread). + * + * @param context session. + * @param error CAPI error number. + */ +static void session_isdn_error(void *context, unsigned int error); + +/*! + * @brief Callback called on RING from other side (in session thread). + * + * @param context session. + * @param data structure with callee and called numbers. + */ +static void isdn_ring_callback(void *context, void *data); + +/*! + * @brief Callback called on RING from other side (in ISDN thread). + * + * @param context session. + * @param callee remote party number (may be NULL). + * @param called called (our) number (may be NULL). + */ +static void session_isdn_ring(void *context, char *callee, char *called); + +/*! + * @brief Init ISDN device for session. + * + * @param session session. + * @return 0 on success, -1 otherwise. + */ +static int session_isdn_init(session_t *session); + +/*! + * @brief Init recording related things in session. + * + * @param session session. + * @return 0 on success, -1 otherwise. + */ +static int session_recording_init(session_t *session); + +/*! + * @brief Clean up recording related things in session. + * + * @param session session. + * @return 0 on success, -1 otherwise. + */ +static int session_recording_deinit(session_t *session); + +/*! + * @brief Close isdn device and clean up (deallocate buffers). + * + * @param session session. + * @return 0 on success, -1 otherwise. + */ +static int session_isdn_deinit(session_t *session); + +/*! + * @brief Called to initialize conversation, just after connection established. + * + * Includes state transition. + * + * @param session session to use. + */ +static void session_start_conversation(session_t *session); + +/*! + * @brief Repeatedly called function to update various stuff. + * + * @param data session. + * @return TRUE to be called again. + */ +static gboolean session_timer_func(gpointer data); + +/*! + * @brief Effect thread main function. + * + * @param data session. + */ +static gpointer handler_effect(gpointer data); + +/*! + * @brief Sets status bar for audio state (e.g. "AUDIO OFF"). + * + * @param session session. + * @param note text to show (hide status bar if note is ""). + */ +static void session_audio_notify(session_t *session, char *note); + +/*! + * @brief Cut session->dial_number_history to specified size. + * + * Uses session->dial_number_history_maxlen for maximum size. Then redisplay + * in session->dial_number_box. + * + * @param session session to use. + */ +static void session_history_normalize(session_t *session); + +/*--------------------------------------------------------------------------*/ + +static int session_audio_open(session_t *session) +{ + dbgprintf(1, "SESSION: Opening audio device(s).\n"); if (open_audio_devices(session->audio_device_name_in, session->audio_device_name_out, 1, session->format_priorities, - &session->audio_fd_in, - &session->audio_fd_out, + &session->audio_in, + &session->audio_out, &session->fragment_size_in, &session->fragment_size_out, &session->audio_format_in, @@ -96,254 +278,403 @@ int session_audio_open(session_t *session) { return 0; } -/* - * Closes audio devices for specified session - * - * returns 0 on success, -1 on error - */ -int session_audio_close(session_t *session) { - if (debug) - printf("session_audio_close: Closing audio device(s).\n"); - if (close_audio_devices(session->audio_fd_in, session->audio_fd_out)) { +/*--------------------------------------------------------------------------*/ + +static int session_audio_close(session_t *session) +{ + dbgprintf(1, "SESSION: Closing audio device(s)\n"); + thread_stop(&session->thread_audio_input); + if (close_audio_devices(session->audio_in, session->audio_out)) { return -1; } return 0; } -/* - * init audio devices in session - * - * input: session->audio_device_name_in, session->audio_device_name_out - * - * returns 0 on success, -1 otherwise - * - * output: on success, session->audio_LUT* and session->audio_*buf are - * allocated (and set, if appropriate) - */ -int session_audio_init(session_t *session) { - /* message: open audio device(s) */ - if (debug) { - if (strcmp(session->audio_device_name_in, session->audio_device_name_out)) - { - /* different devices */ - fprintf(stderr, "Initializing %s and %s...\n", - session->audio_device_name_in, session->audio_device_name_out); - } else { - fprintf(stderr, "Initializing %s ...\n", - session->audio_device_name_in); +/*--------------------------------------------------------------------------*/ + +static int session_snd_pcm_recover(session_t *session _U_, snd_pcm_t *audio, int err) +{ + int err2; + if (err == -EBADFD) { + dbgprintf(1, "AUDIO: Preparing audio for I/O\n"); + return snd_pcm_prepare(audio); + } else { + err2 = snd_pcm_recover(audio, err, 1); + if (err2 != 0) + return err2; + dbgprintf(2, "AUDIO: snd_pcm_recover from error %s on %s\n", + snd_strerror(err), + (audio == session->audio_out) ? "output" : "input"); + return 0; + } +} + +/*--------------------------------------------------------------------------*/ + +int session_set_audio_state(session_t *session, enum audio_t state) +{ + enum audio_t oldstate = session->audio_state; + + if (state == oldstate) { + return 0; + } + + if (oldstate == AUDIO_EFFECT) { + // stop effect first + thread_stop(&session->thread_effect); + } + + if (session->audio_state == AUDIO_DISCONNECTED) { + /* message: open audio device(s) */ + if (debug) { + if (strcmp(session->audio_device_name_in, session->audio_device_name_out)) + { + /* different devices */ + dbgprintf(1, "AUDIO: Initializing %s and %s...\n", + session->audio_device_name_in, session->audio_device_name_out); + } else { + dbgprintf(1, "AUDIO: Initializing %s ...\n", + session->audio_device_name_in); + } + } + + /* other options */ + session->audio_speed_in = ISDN_SPEED; /* default audio speed */ + session->audio_speed_out = ISDN_SPEED; + + /* audio device buffer fragment sizes */ + session->fragment_size_in = DEFAULT_FRAGMENT_SIZE; + session->fragment_size_out = DEFAULT_FRAGMENT_SIZE; + + session->format_priorities = default_audio_priorities; + + if (session_audio_open(session)) { + errprintf("AUDIO: Error initializing audio device(s).\n"); + return -1; + } + + session->ratio_in = (double)session->audio_speed_out / ISDN_SPEED; + session->ratio_out = (double)ISDN_SPEED / session->audio_speed_in; + + if (mediation_makeLUT(session->audio_format_out, &session->audio_LUT_in, + session->audio_format_in, &session->audio_LUT_out, + &session->audio_LUT_generate, + &session->audio_LUT_analyze, + &session->audio_LUT_alaw2short)) { + errprintf("AUDIO: Error building conversion look-up-table.\n"); + return -1; + } + + session->audio_sample_size_in = + sample_size_from_format(session->audio_format_in); + session->audio_sample_size_out = + sample_size_from_format(session->audio_format_out); + } else if (state == AUDIO_DISCONNECTED) { + /* close devices */ + + /* free allocated buffers */ + free(session->audio_LUT_in); + free(session->audio_LUT_out); + free(session->audio_LUT_generate); + free(session->audio_LUT_analyze); + free(session->audio_LUT_alaw2short); + + /* close audio device(s) */ + if (session_audio_close(session)) { + errprintf("AUDIO: Error closing sound device(s).\n"); + return -1; + } + } + + /* set new state on session */ + session->audio_state = state; + + if (state == AUDIO_CONVERSATION) { + /* start thread handling audio input during conversation */ + if (!thread_is_running(&session->thread_audio_input)) { + if (thread_start(&session->thread_audio_input, handler_audio_input, session) < 0) { + errprintf("AUDIO: Cannot start audio input thread\n"); + session->audio_state = AUDIO_IDLE; + return -1; } - } - - /* other options */ - session->audio_speed_in = ISDN_SPEED; /* default audio speed */ - session->audio_speed_out = ISDN_SPEED; - - /* audio device buffer fragment sizes */ - session->fragment_size_in = DEFAULT_FRAGMENT_SIZE; - session->fragment_size_out = DEFAULT_FRAGMENT_SIZE; - - session->format_priorities = default_audio_priorities; - - if (session_audio_open(session)) { - fprintf(stderr, "Error initializing audio device(s).\n"); - return -1; - } - - session->ratio_in = (double)session->audio_speed_out / ISDN_SPEED; - session->ratio_out = (double)ISDN_SPEED / session->audio_speed_in; - - if (mediation_makeLUT(session->audio_format_out, &session->audio_LUT_in, - session->audio_format_in, &session->audio_LUT_out, - &session->audio_LUT_generate, - &session->audio_LUT_analyze, - &session->audio_LUT_ulaw2short)) { - fprintf(stderr, "Error building conversion look-up-table.\n"); - return -1; - } - - session->audio_sample_size_in = - sample_size_from_format(session->audio_format_in); - session->audio_sample_size_out = - sample_size_from_format(session->audio_format_out); - - /* allocate buffers */ - if (!(session->audio_inbuf = - (unsigned char *)malloc(session->fragment_size_in))) { - return -1; - } - if (!(session->audio_outbuf = - (unsigned char *)malloc(session->fragment_size_out))) { - return -1; + } + } else { + /* no conversation, shut down input, if any */ + thread_stop(&session->thread_audio_input); } return 0; } -/* - * init isdn in session - * - * input: session->msn, session->msns - * - * returns 0 on success, -1 otherwise - */ -int session_isdn_init(session_t *session) { +/*--------------------------------------------------------------------------*/ + +static void session_isdn_connected(void *context, char *number) +{ + session_t *session = (session_t*) context; + + remote_call_invoke(&session->rem_port, isdn_connect_callback, session, number); +} + +/*--------------------------------------------------------------------------*/ + +static void session_isdn_data(void *context, void *data, unsigned int length) +{ + session_t *session = (session_t*) context; + unsigned int ptr, outsize, framesize, size; + unsigned char outbuffer[16384]; + short recbuffer[8192]; + int err; +#if 0 + double factor; +#endif + + convert_isdn_to_audio(session, + data, length, + outbuffer, &outsize, + recbuffer, + 1); + + framesize = sample_size_from_format(session->audio_format_out); + outsize /= framesize; + + /* dump the ISDN data to audio and/or recording */ + ptr = 0; + while (ptr < outsize) { + size = outsize - ptr; + err = snd_pcm_writei(session->audio_out, + outbuffer + ptr * framesize, + size); + if (err < 0) { + if (err != -EAGAIN) { + err = session_snd_pcm_recover(session, session->audio_out, err); + if (err >= 0) { + /* write one frame doubled to catch up */ + snd_pcm_writei(session->audio_out, + outbuffer + ptr * framesize, + size); + continue; /* retry */ + } + /* TODO: handle error better and/or stop audio */ + errprintf("AUDIO: Error writing to audio: %s\n", snd_strerror(err)); + break; + } else { + /* non-blocking write failed */ + dbgprintf(2, "AUDIO: Clock unsynchronized, skipping audio buffer\n"); + isdn_speed_addsamples(&session->audio_out_speed, size); + break; + } + } else { + /* some data written */ + ptr += err; + isdn_speed_addsamples(&session->audio_out_speed, err); + + if (debug > 1) { + isdn_speed_debug(&session->audio_out_speed, 2, "AUDIO: out"); + } + +#if 0 + /* NOTE: even though ISDN and audio should both run at 8000Hz, they don't. + At least not exactly. This code experiment tries to adjust audio speed + to match ISDN speed to prevent underruns and frame drops. Someone + might try to take it from here... */ + snd_pcm_sframes_t delay; + if (snd_pcm_delay(session->audio_out, &delay) == 0) { + /* reevaluate the audio speed */ + + /* now compute factor to speed up/down the audio to meet optimum delay */ + factor = (4*session->fragment_size_out - delay) / 3000.0 + 1.0; + + /* normalize to ISDN speed */ + factor *= ISDN_SPEED / ((double)session->audio_speed_out); + + if (factor < 0.95) + factor = 0.95; + else if (factor > 1.05) + factor = 1.05; + + factor *= ((double)session->audio_speed_out / ISDN_SPEED); + + session->ratio_in = (session->ratio_in + factor) / 2.0; + + dbgprintf(3, "AUDIO: out delay: %d, fragment %d, ratio %.3f\n", + (int) delay, (int) session->fragment_size_out, + session->ratio_in); + } +#endif + } + } + +} + +/*--------------------------------------------------------------------------*/ + +static void session_isdn_disconnected(void *context) +{ + session_t *session = (session_t*) context; + + dbgprintf(1, "SESSION: Disconnected callback\n"); + + remote_call_invoke(&session->rem_port, isdn_hangup_callback, session, NULL); +} + +/*--------------------------------------------------------------------------*/ + +static void session_isdn_error(void *context, unsigned int error) +{ + session_t *session = (session_t*) context; + + dbgprintf(1, "SESSION: Error callback, 0x%x\n", error); + + remote_call_invoke(&session->rem_port, isdn_hangup_callback, session, (void*) (long) error); +} + +/*--------------------------------------------------------------------------*/ + +static void isdn_ring_callback(void *context, void *data) +{ + session_t *session = (session_t*) context; + struct msg { + char *callee; + char *called; + } *numbers = (struct msg*) data; + char *callee = numbers->callee; + char *called = numbers->called; + + /* caller id update */ + session->ring_time = time(NULL); + + /* save callee's number */ + free(session->from); + free(session->to); + session->from = strdup(callee ? (char*) callee : "(no caller ID)"); + session->to = strdup(called ? (char*) called : "(no caller ID)"); + + cid_add_line(session, CALL_IN, session->from, session->to); + + if (session_set_state(session, STATE_RINGING)) + session_set_state(session, STATE_RINGING_QUIET); +} + +/*--------------------------------------------------------------------------*/ + +static void session_isdn_ring(void *context, char *callee, char *called) +{ + session_t *session = (session_t*) context; + struct { + char *callee; + char *called; + } msg; + msg.callee = callee; + msg.called = called; + + dbgprintf(1, "SESSION: Ring callback from '%s' to '%s'\n", + callee ? callee : "(no number)", + called ? called : "(no number)"); + + remote_call_invoke(&session->rem_port, isdn_ring_callback, session, &msg); +} + +/*--------------------------------------------------------------------------*/ + +static int session_isdn_init(session_t *session) +{ + static isdn_callback_t callbacks = { + session_isdn_connected, + session_isdn_data, + session_isdn_disconnected, + session_isdn_error, + session_isdn_ring + }; + /* open and init isdn device */ - if (debug) - fprintf(stderr, "Initializing ISDN device...\n"); - if ((session->isdn_fd = - open_isdn_device(&session->isdn_device_name, - &session->isdn_lockfile_name)) < 0) { - fprintf(stderr, "Error opening isdn device.\n"); - return -1; - } - if (init_isdn_device(session->isdn_fd, &session->isdn_backup)) { - fprintf(stderr, "Error initializing isdn device.\n"); + dbgprintf(1, "SESSION: Initializing ISDN device...\n"); + + if (open_isdn_device(&session->isdn, &callbacks, session) < 0) { + errprintf("SESSION: Error opening isdn device.\n"); return -1; } - session->isdn_inbuf_size = session->isdn_outbuf_size = DEFAULT_ISDNBUF_SIZE; - - /* allocate buffers */ - if (!(session->isdn_inbuf = - (unsigned char *)malloc(session->isdn_inbuf_size))) { - return -1; - } - if (!(session->isdn_outbuf = - (unsigned char *)malloc(session->isdn_outbuf_size))) { - return -1; - } - - if (isdn_setMSN(session->isdn_fd, session->msn) || - isdn_setMSNs(session->isdn_fd, session->msns)) { - fprintf(stderr, "Error setting MSN properties.\n"); + if (isdn_setMSN(&session->isdn, session->msn) || + isdn_setMSNs(&session->isdn, session->msns)) { + errprintf("SESSION: Error setting MSN properties.\n"); + close_isdn_device(&session->isdn); return -1; } - session->isdn_inbuf_len = 0; - session->isdn_inbuf[0] = 1; - + session->isdn_active = 1; return 0; } -/* - * init recording related things in session - */ -int session_recording_init(session_t *session) { +/*--------------------------------------------------------------------------*/ + +int session_activate_isdn(session_t *session, unsigned int activate) +{ + int result = 0; + + if (activate) { + if (!session->isdn_active) { + result = activate_isdn_device(&session->isdn, 1); + if (result == 0) + session->isdn_active = 1; + } + } else { + if (session->isdn_active) { + /* make sure the connection is closed */ + session->isdn_active = 0; + if (session->isdn.state == ISDN_IDLE) { + result = activate_isdn_device(&session->isdn, 0); + } else { + session->hangup_reason = _("(ABORTED)"); + isdn_hangup(&session->isdn); + } + } + } + + return result; +} + +/*--------------------------------------------------------------------------*/ + +static int session_recording_init(session_t *session) +{ /* mediation recording stuff */ - session->rec_buf_local_size = MEDIATION_RECBUFSIZE; - session->rec_buf_remote_size = MEDIATION_RECBUFSIZE; - session->rec_buf_local_index = 0; - session->rec_buf_remote_index = 0; - if (!(session->rec_buf_local = - (short *) malloc(session->rec_buf_local_size * sizeof(short)))) { - return -1; - } - if (!(session->rec_buf_remote = - (short *) malloc(session->rec_buf_remote_size * sizeof(short)))) { - return -1; - } if (!(session->recorder = (struct recorder_t *)malloc(sizeof(struct recorder_t)))) { return -1; } - - return 0; + return recording_init(session->recorder); } -/* - * deinit recording related things in session - */ -int session_recording_deinit(session_t *session) { - free(session->rec_buf_local); - free(session->rec_buf_remote); +/*--------------------------------------------------------------------------*/ + +static int session_recording_deinit(session_t *session) +{ free(session->recorder); + session->recorder = 0; return 0; } -/* - * de-allocates memory used by audio buffers of session - * -> prepares de-initialization - * -> is part of session_audio_free() - */ -void session_audio_free(session_t *session) { - free(session->audio_inbuf); - free(session->audio_outbuf); +/*--------------------------------------------------------------------------*/ - free(session->audio_LUT_in); - free(session->audio_LUT_out); - free(session->audio_LUT_generate); - free(session->audio_LUT_analyze); - free(session->audio_LUT_ulaw2short); -} - -/* - * close audio device(s) and clean up (deallocate buffers) - * - * returns 0 on success, -1 otherwise - */ -int session_audio_deinit(session_t *session) { - /* free allocated buffers */ - session_audio_free(session); - - /* close audio device(s) */ - if (debug) - fprintf(stderr, "Closing sound device(s)...\n"); - if (session_audio_close(session)) { - fprintf(stderr, "Error closing sound device(s).\n"); - return -1; +static int session_isdn_deinit(session_t *session) +{ + dbgprintf(1, "SESSION: Closing ISDN device...\n"); + if (close_isdn_device(&session->isdn) < 0) { + errprintf("SESSION: Error closing ISDN device.\n"); } - + session->isdn_active = 0; return 0; } -/* - * close isdn device and clean up (deallocate buffers) - * - * returns 0 on success, -1 otherwise - */ -int session_isdn_deinit(session_t *session) { - /* free allocated buffers */ - free(session->isdn_inbuf); - free(session->isdn_outbuf); - - if (debug) - fprintf(stderr, "Closing ISDN device...\n"); - /* de-init / restore isdn device */ - if (deinit_isdn_device(session->isdn_fd, &session->isdn_backup)) { - fprintf(stderr, "Error restoring ttyI state.\n"); - } - /* close isdn device */ - if (close_isdn_device(session->isdn_fd, - session->isdn_device_name, - session->isdn_lockfile_name)) { - fprintf(stderr, "Error closing isdn device.\n"); - } - - return 0; -} +/*--------------------------------------------------------------------------*/ -/* - * initialize a session (isdn device, audio device) and read options file - * - * input: - * session: empty struct waiting for work - * audio_device_name_in, audio_device_name_out: name(s) of audio device(s) - * msn: msn to use - * - * NOTE: The latter 4 parameters are only the requested ones. When they are - * overridden by the options file, the resulting values will be different - * - * output: session: initialized session struct - * - * returns 0 on success, -1 otherwise - */ int session_init(session_t *session, char *audio_device_name_in, char *audio_device_name_out, char *msn, char *msns) { int i; - + /* * first: set all defaults possibly overridden by options file */ @@ -416,56 +747,84 @@ int session_init(session_t *session, session->ring_time = 0; session->unanswered = 0; + session->gtk_local_input_tag = 0; + session->gtk_updater_timer_tag = 0; + + /* create communication pipe for communicating events from thread to main */ + if (remote_call_init(&session->rem_port) < 0) { + errprintf("SESSION: Cannot create remote call port\n"); + return -1; + } + /* setup audio and isdn */ - if (!session->option_release_devices) - if (session_audio_init(session) < 0) return -1; - if (session_isdn_init(session) < 0) return -1; - if (session_recording_init(session) < 0) return -1; + session->audio_state = AUDIO_DISCONNECTED; + thread_init(&session->thread_audio_input); session->state = STATE_READY; /* initial state */ - + thread_init(&session->thread_effect); session->effect = EFFECT_NONE; + if (!session->option_release_devices) + session_set_audio_state(session, AUDIO_IDLE); + if (session_isdn_init(session) < 0) + return -1; + if (session_recording_init(session) < 0) + return -1; + /* init server functionality */ - if (server_init(session) < 0) return -1; - + if (server_init(session) < 0) + return -1; + + /* register remote call with GTK */ + if (remote_call_register(&session->rem_port) < 0) { + errprintf("SESSION: Cannot register remote call port with GTK\n"); + return -1; + } + return 0; } -/* - * helper function to free single element data from g_list - * (used by session_deinit) +/*--------------------------------------------------------------------------*/ + +/*! + * @brief Helper function to free single element data from g_list. + * + * Used by session_deinit. */ -void free_g_list_element(gpointer data, gpointer user_data _U_) { +static void free_g_list_element(gpointer data, gpointer user_data _U_) { free(data); } -/* - * de-initialize a session (isdn device, audio device) - * - * input: session: session to de-initialize - * - * returns 0 on success, -1 otherwise - */ +/*--------------------------------------------------------------------------*/ + int session_deinit(session_t *session) { int i; + /* stop GTK handlers */ + session_io_handlers_stop(session); + /* deinit server functionality */ - if (server_deinit(session) < 0) return -1; - - if (session->option_save_options) settings_options_write(session); + if (server_deinit(session) < 0) + return -1; + + if (session->option_save_options) + settings_options_write(session); + + /* stop communication request handler */ + remote_call_close(&session->rem_port); /* free dial_number_history */ g_list_foreach(session->dial_number_history, free_g_list_element, NULL); g_list_free(session->dial_number_history); /* close devices and clean up (buffers) */ - if (session_isdn_deinit(session) < 0) return -1; - if (!session->option_release_devices) - if (session_audio_deinit(session) < 0) return -1; + if (session_isdn_deinit(session) < 0) + return -1; + if (session_set_audio_state(session, AUDIO_DISCONNECTED) < 0) + return -1; if (session_recording_deinit(session) < 0) return -1; - + free(session->exec_on_incoming); /* clean up pre-set options */ @@ -485,26 +844,308 @@ int session_deinit(session_t *session) { return 0; } -/* - * set up handlers for audio/ISDN input - */ -void session_io_handlers_start(session_t *session) { - if (!(session->option_release_devices && - (session->state == STATE_READY || - session->state == STATE_RINGING_QUIET))) { - session->gtk_audio_input_tag = gtk_input_add_full(session->audio_fd_in, - GDK_INPUT_READ, - gtk_handle_audio_input, - NULL, - (gpointer) session, - NULL); +/*--------------------------------------------------------------------------*/ + +static gpointer handler_audio_input(gpointer data) { + session_t *session = (session_t*) data; + + unsigned char inbuffer[16384]; /* audio input buffer */ + unsigned char outbuffer[16384]; /* ISDN output buffer */ + short recbuffer[16384]; + int err, bytes_per_frame; + + dbgprintf(1, "AUDIO: Starting audio input thread\n"); + + /* set blocking mode for audio input */ + snd_pcm_nonblock(session->audio_in, 0); + + isdn_speed_init(&session->audio_out_speed); + isdn_speed_init(&session->audio_in_speed); + + bytes_per_frame = sample_size_from_format(session->audio_format_in); + + while (!thread_is_stopping(&session->thread_audio_input)) { + for (;;) { + err = snd_pcm_readi(session->audio_in, inbuffer, session->fragment_size_in/*sizeof(inbuffer)*/); + if (err < 0) { + err = session_snd_pcm_recover(session, session->audio_in, err); + if (err >= 0) + continue; + switch (err) { + case -EAGAIN: + break; + default: + errprintf("AUDIO: Unrecoverable PCM read error %s, terminating\n", + snd_strerror(err)); + // TODO: trigger hangup + return (gpointer) 5; + } + } else { + err *= bytes_per_frame; + isdn_speed_addsamples(&session->audio_in_speed, err); + + /* process the data */ + unsigned int outsize; + convert_audio_to_isdn(session, + inbuffer, err, + outbuffer, &outsize, + recbuffer); + + /* dump the audio to ISDN */ + isdn_send_data(&session->isdn, outbuffer, outsize); + + if (debug > 1) { + isdn_speed_debug(&session->audio_in_speed, 1, "AUDIO: in"); + } + } + break; + } } - session->gtk_isdn_input_tag = gtk_input_add_full(session->isdn_fd, - GDK_INPUT_READ, - gtk_handle_isdn_input, - NULL, - (gpointer) session, - NULL); + + dbgprintf(1, "AUDIO: Stopping audio input thread\n"); + + return 0; +} + +/*--------------------------------------------------------------------------*/ + +int session_start_recording(session_t *session) +{ + char *digits = NULL; + int result = 0; + + if ((digits = util_digitstime(&session->vcon_time))) { + if (recording_open(session->recorder, digits, + session->option_recording_format)) + { + errprintf("SESSION: Error opening audio file for recording.\n"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + session->record_checkbutton), FALSE); + result = -1; + } else { + cid_row_mark_record(session, session->cid_num - 1); + } + free(digits); + } else { + errprintf("SESSION: Error generating audio filename for recording.\n"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + session->record_checkbutton), FALSE); + result = -1; + } + return result; +} + +static void session_start_conversation(session_t *session) +{ + session->vcon_time = time(NULL); /* for caller id monitor */ + cid_set_date(session, session->vcon_time); + session_effect_stop(session); + session_set_state(session, STATE_CONVERSATION); + if (session->option_record) { + session_start_recording(session); + } + + session_io_handlers_stop(session); + if (session_set_audio_state(session, AUDIO_CONVERSATION) < 0) { + /* TODO: stop conversation, as no audio possible */ + return; + } + + /* set non-blocking mode for audio output */ + snd_pcm_nonblock(session->audio_out, 1); + + session_io_handlers_start(session); +} + +/*--------------------------------------------------------------------------*/ + +static void isdn_hangup_callback(void *context, void *error) +{ + session_t *session = (session_t*) context; + + // NOTE: error contains no pointer, but error code from CAPI + + char *reason = session->hangup_reason; + if (error) { + unsigned long e = (unsigned long) error; + reason = _("ERROR"); + switch (e) { + case 0x3301: + case 0x3302: + case 0x3303: + + case 0x34EF: /* Protocol error, unspecified */ + reason = _("PROTOCOL ERROR"); + break; + + case 0x3481: /* Unallocated (unassigned) number */ + case 0x349C: /* Invalid number format */ + reason = _("WRONG NUMBER"); + break; + + case 0x3482: /* No route to specified transit network */ + case 0x3483: /* No route to destination */ + reason = _("NO ROUTE"); + break; + + case 0x3486: /* Channel unacceptable */ + case 0x3487: /* Call awarded and being delivered in an established channel */ + case 0x34A2: /* No circuit / channel available */ + case 0x34A9: /* Temporary failure */ + case 0x34AA: /* Switching equipment congestion */ + case 0x34AC: /* Requested circuit / channel not available */ + case 0x34AF: /* Resources unavailable, unspecified */ + reason = _("CHANNEL UNAVAILABLE"); + break; + + case 0x3491: /* User busy */ + reason = _("BUSY"); + break; + + case 0x3492: /* No user responding (wrong MSN?) */ + case 0x3493: /* No answer from user (user alerted) */ + reason = _("NO ANSWER"); + break; + + case 0x3495: /* Call rejected */ + reason = _("REJECTED"); + break; + + case 0x3496: /* Number changed */ + reason = _("NUMBER CHANGED"); + break; + + case 0x349A: /* Non-selected user clearing */ + reason = _("DISCONNECT"); + break; + + case 0x349B: /* Destination out of order */ + reason = _("REMOTE FAILURE"); + break; + + case 0x34A6: /* Network out of order */ + case 0x34DB: /* Invalid transit network selection */ + reason = _("NETWORK ERROR"); + break; + + case 0x34B1: /* Quality of service unavailable */ + case 0x34BA: /* Bearer capability not presently available */ + case 0x34BF: /* Service or option not available, unspecified */ + reason = _("SERVICE UNAVAILABLE"); + break; + + case 0x34B2: /* Requested facility not subscribed */ + case 0x34B9: /* Bearer capability not authorized */ + reason = _("NOT SUBSCRIBED"); + break; + + case 0x34C1: /* Bearer capability not implemented */ + case 0x34C2: /* Channel type not implemented */ + case 0x34C5: /* Requested facility not implemented */ + case 0x34C6: /* Only restricted digital information bearer capability is available */ + case 0x34CF: /* Service or option not implemented, unspecified */ + reason = _("NOT IMPLEMENTED"); + break; + + case 0x34D8: /* Incompatible destination */ + reason = _("NOT COMPATIBLE"); + break; + + /* Other CAPI network error codes: + 0x349D Facility rejected + 0x349E Response to STATUS ENQUIRY + 0x34AB Access information discarded + 0x34D1 Invalid call reference value + 0x34D2 Identified channel does not exist + 0x34D3 A suspended call exists, but this call identity does not + 0x34D4 Call identity in use + 0x34D5 No call suspended + 0x34D6 Call having the requested call identity has been cleared + 0x34DF Invalid message, unspecified + 0x34E0 Mandatory information element is missing + 0x34E1 Message type non-existent or not implemented + 0x34E2 Message not compatible with call state or message type non-existent or not implemented + 0x34E3 Information element non-existent or not implemented + 0x34E4 Invalid information element contents + 0x34E5 Message not compatible with call state + 0x34E6 Recovery on timer expiry + 0x34FF Interworking, unspecified */ + } + } + + if (session->state == STATE_CONVERSATION) { + session_deinit_conversation(session, error ? 0 : 1); + } else if (session->state == STATE_RINGING || + session->state == STATE_RINGING_QUIET) { + reason = _("(MISSED)"); + cid_mark_row(session, session->cid_num - 1, 1); + } + session_set_state(session, STATE_READY); + cid_set_duration(session, reason); + + if (!session->isdn_active) { + /* we were asked to deactivate ISDN */ + activate_isdn_device(&session->isdn, 0); + } +} + +/*--------------------------------------------------------------------------*/ + +static void isdn_connect_callback(void *context, void *number) +{ + session_t *session = (session_t*) context; + + if (session->state == STATE_RINGING || session->state == STATE_RINGING_QUIET) { + char *old = session->from; + session->from = strdup((char*) number); + free(old); + } + + session_start_conversation(session); /* including state transition */ +} + +/*--------------------------------------------------------------------------*/ + +static gboolean session_timer_func(gpointer data) +{ + session_t *session = (session_t *) data; + + switch (session->state) { + case STATE_CONVERSATION: + /* flush any recording buffers */ + recording_flush(session->recorder, 0); + /* fall through */ + + case STATE_RINGING: + case STATE_PLAYBACK: + case STATE_DIALING: + case STATE_SERVICE: + /* update line level bars */ + llcheck_bar_set(session->llcheck_in, log10(1.0 + 9.0 * session->llcheck_in_state)); + llcheck_bar_set(session->llcheck_out, log10(1.0 + 9.0 * session->llcheck_out_state)); + + if (session->state == STATE_PLAYBACK && session->effect == EFFECT_NONE) { + /* playback finished, set ready state */ + session_set_state(session, STATE_READY); + } + break; + + default: + /* line level is at 0 when no audio */ + llcheck_bar_set(session->llcheck_in, 0.0); + llcheck_bar_set(session->llcheck_out, 0.0); + break; + } + + return TRUE; +} + +/*--------------------------------------------------------------------------*/ + +void session_io_handlers_start(session_t *session) +{ + /* stop old handlers first, if any */ + session_io_handlers_stop(session); /* server functionality */ session->gtk_local_input_tag = gtk_input_add_full(session->local_sock, @@ -513,388 +1154,252 @@ void session_io_handlers_start(session_t *session) { NULL, (gpointer) session, NULL); + session->gtk_updater_timer_tag = gtk_timeout_add(100, + session_timer_func, + (gpointer) session); } -/* - * remove handlers for audio/ISDN input - */ -void session_io_handlers_stop(session_t *session) { - if (!(session->option_release_devices && - (session->state == STATE_READY || - session->state == STATE_RINGING_QUIET))) { - gtk_input_remove(session->gtk_audio_input_tag); +/*--------------------------------------------------------------------------*/ + +void session_io_handlers_stop(session_t *session) +{ + if (session->gtk_local_input_tag) { + gtk_input_remove(session->gtk_local_input_tag); + session->gtk_local_input_tag = 0; + } + if (session->gtk_updater_timer_tag) { + gtk_timeout_remove(session->gtk_updater_timer_tag); + session->gtk_updater_timer_tag = 0; + llcheck_bar_reset(session->llcheck_in); + llcheck_bar_reset(session->llcheck_out); } - gtk_input_remove(session->gtk_isdn_input_tag); - gtk_input_remove(session->gtk_local_input_tag); } -/* - * Resets audio devices by closing and reopening - * - * assumes audio in open, initialized (possibly used) state - * - * WARNING: * Stop I/O handlers first! - * * Only resets currently used device(s). To use another device, - * use session_audio_deinit() and session_audio_init() - * - * returns 0 on success, -1 on error - */ -int session_reset_audio(session_t *session) { +/*--------------------------------------------------------------------------*/ + +int session_reset_audio(session_t *session) +{ int result = 0; if (!(session->option_release_devices && (session->state == STATE_READY || session->state == STATE_RINGING_QUIET))) { if (session_audio_close(session)) { - fprintf(stderr, "Error closing audio device(s) while resetting.\n"); + errprintf("Error closing audio device(s) while resetting.\n"); result = -1; } if (session_audio_open(session)) { - fprintf(stderr, "Error reopening audio device(s) while resetting.\n"); + errprintf("Error reopening audio device(s) while resetting.\n"); result = -1; } } return result; } -/* - * Callback for new unhandled modem string - * - * This function will be called, whenever we got a new unhandled modem string. - * That means that it's called unless modem answers were stolen by response - * handlers in functions like isdn_setMSN or isdn_hangup where "OK\r\n" will - * be checked for - */ -void session_new_modem_string_callback(session_t *session) { - /* modem string is in session->isdn_inbuf */ - if (debug) - /* new line is in isdn_inbuf */ - fprintf(stderr, "|%s", session->isdn_inbuf); -} +/*--------------------------------------------------------------------------*/ -/* - * Tries to read isdn device in command mode, returns immediately - * (non blocking). If we have a partially filled input buffer, continue - * with that - * - * input: session struct with open isdn_fd of ttyI in command mode - * - * output: command (or partial command) read in - * session->isdn_inbuf, session->isdn_inbuf_len - * - * returns with 1 if we got a new line (else 0) - * - * NOTE: * completed lines are 0-terminated at session->isdn_inbuf_len, - * non-compleded lines are 1-terminated there - * * completed lines are actually terminated by "\r\n\0" - */ -static int isdn_try_read_line(session_t *session) { - int total = 0; /* number of bytes read in this call */ - struct timeval tv; - fd_set fds; - int num; /* number of file descriptors with data (0/1) */ +static void session_deinit_conversation(session_t *session, int self_hangup _U_) +{ + /* stop audio thread */ + session_set_audio_state(session, AUDIO_IDLE); + /* stop recording, if used */ + recording_close(session->recorder); - tv.tv_sec = 0; /* return immediately */ - tv.tv_usec = 0; - - do { - FD_ZERO(&fds); - FD_SET(session->isdn_fd, &fds); - - num = select(FD_SETSIZE, &fds, 0, 0, &tv); - if (num > 0) { /* got another char: append to buffer */ - - if (session->isdn_inbuf[session->isdn_inbuf_len] == 0 || - session->isdn_inbuf_len == session->isdn_inbuf_size - 1) { - /* we have to start new line or buffer is full -> reset buffer */ - session->isdn_inbuf_len = 0; - session->isdn_inbuf[0] = 1; - } - - read(session->isdn_fd, &session->isdn_inbuf[session->isdn_inbuf_len], 1); - total ++; - session->isdn_inbuf[++session->isdn_inbuf_len] = 1; - - if (session->isdn_inbuf_len >= 2 && - session->isdn_inbuf[session->isdn_inbuf_len - 2] == '\r' && - session->isdn_inbuf[session->isdn_inbuf_len - 1] == '\n') { - /* end of line */ - session->isdn_inbuf[session->isdn_inbuf_len] = 0; - } - } - } while (num > 0 && session->isdn_inbuf[session->isdn_inbuf_len] != 0); - if (session->isdn_inbuf[session->isdn_inbuf_len] == 0 && total > 0) { - session_new_modem_string_callback(session); - return 1; - } else - return 0; -} - -/* do some initialization of full duplex conversation mode */ -void session_init_conversation(session_t *session) { - session->samples_in = 0; - session->samples_out = 0; - - session->audio_outbuf_index = 0; - session->isdn_outbuf_index = 0; - - session->escape = 0; - - session->hangup = 0; - session->aborted = 0; - - session->no_input = 0; -} - -/* - * do some deinitialization of full duplex conversation mode - * - * return: self_hangup: 1 == we hangup, 0 == other side hung up - */ -void session_deinit_conversation(session_t *session, int self_hangup) { - if (session->option_record) { - recording_write(session->recorder, session->rec_buf_local, - session->rec_buf_local_index, RECORDING_LOCAL); - recording_write(session->recorder, session->rec_buf_remote, - session->rec_buf_remote_index, RECORDING_REMOTE); - recording_close(session->recorder); - } - session->rec_buf_local_index = 0; - session->rec_buf_remote_index = 0; - session_io_handlers_stop(session); session_reset_audio(session); session_io_handlers_start(session); - - if (isdn_blockmode(session->isdn_fd, 0)) - fprintf(stderr, "Warning: " - "Switching back to normal isdn tty mode not successful.\n"); - - /* go back to command mode */ - if (isdn_stop_audio(session->isdn_fd, self_hangup)) { - fprintf(stderr, "Error switching back to command mode.\n"); - } - - /* isdn hangup */ - if (isdn_hangup(session->isdn_fd)) { - fprintf(stderr, "Error hanging up.\n"); - } - - session->isdn_inbuf_len = 0; - session->isdn_inbuf[0] = 1; } -/* - * will be called directly after getting VCON from ttyI - * includes state transition - */ -void session_start_conversation(session_t *session) { - char *digits; /* time in digits form */ - - if (isdn_set_full_duplex(session->isdn_fd)) { - /* ISDN full duplex (REC start command) error */ - fprintf(stderr, "Error setting full duplex audio mode.\n"); - isdn_hangup(session->isdn_fd); - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(HW ERROR)")); - } else { /* full duplex ok: audio mode */ - session->vcon_time = time(NULL); /* for caller id monitor */ - cid_set_date(session, session->vcon_time); - session_set_state(session, STATE_CONVERSATION); - if (session->option_record) { - if ((digits = util_digitstime(&session->vcon_time))) { - if (recording_open(session->recorder, digits, - session->option_recording_format)) { - fprintf(stderr, "Error opening audio file.\n"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( - session->record_checkbutton), FALSE); - } - free(digits); - cid_row_mark_record(session, session->cid_num - 1); - } else { - fprintf(stderr, "Error generating audio filename.\n"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( - session->record_checkbutton), FALSE); - } - } - if (isdn_blockmode(session->isdn_fd, 1)) /* blockmode error */ - fprintf(stderr, - "Warning: Switching to ISDN blockmode not successful.\n"); - session_init_conversation(session); /* init some conversation variables */ - - session_io_handlers_stop(session); - session_reset_audio(session); - session_io_handlers_start(session); +/*--------------------------------------------------------------------------*/ - /* start with first block to start recording */ - process_audio_source(session); - } -} - -/* - * to be called by a timeout and everyone who wants to stop a running effect - * to go back to STATE_READY - */ -gint session_effect_stop_cb(gpointer data) { - session_t *session = (session_t *) data; - - session_set_state(session, STATE_READY); - - return FALSE; /* don't call me regularly */ -} - -/* - * To be called when we can write another block to sound device - */ -void session_effect_callback(gpointer data, - gint source _U_, GdkInputCondition condition _U_) +static gpointer handler_effect(gpointer data) { session_t *session = (session_t *) data; - int pos = 0; /* position in raw byte output buffer */ - int i; - int index_out = 0; /* logical position (sample number) in output buffer */ - unsigned char x; /* generated ulaw sample */ - int just_read = 0; - int sndfile_buffer_index = 0; - double speed_factor = (double)session->effect_sndfile_buffer_frames / - (session->fragment_size_out / session->audio_sample_size_out); - /* factor from audio device to sndfile */ + unsigned int i; + int err; + unsigned long effectpos = 0; /* position within effect in frames */ + short buffer[2048]; /* buffer for sound file samples */ + int just_read; /* read count from sndfile */ + int sample; /* linear sample to convert to A-law */ + unsigned char alawbuffer[4096]; /* buffer for alaw samples */ + unsigned int alawcount; /* count of samples to play */ + unsigned char sndbuffer[12*4096]; /* sound data buffer */ + short recbuffer[4096]; /* temporary */ + unsigned int sndcount; /* count of bytes in sound buffer */ + unsigned int ptr; /* playback pointer */ + unsigned int size; /* playback frame size */ + unsigned int framesize; /* frame/sample size (1 or 2B) */ + int term_retry; /* retry count on termination */ + + dbgprintf(1, "EFFECT: Starting effect thread\n"); + + /* set blocking mode */ + snd_pcm_nonblock(session->audio_out, 0); + /* set non-blocking mode */ + snd_pcm_nonblock(session->audio_in, 1); + + framesize = sample_size_from_format(session->audio_format_out); + + while (!thread_is_stopping(&session->thread_effect)) { + switch (session->effect) { + case EFFECT_SOUNDFILE: + just_read = sf_readf_short(session->effect_sndfile, + buffer, + sizeof(buffer) / 4); + if (just_read > 0) { + /* convert samples to alaw */ + for (i = 0; i < (unsigned int) just_read; ++i) { + /* convert to ALAW */ + sample = ((int) buffer[2*i]) + ((int) buffer[2*i+1]); + if (sample < -32768) + sample = -32768; + else if (sample > 32767) + sample = 32767; + alawbuffer[i] = linear2alaw(sample); + } + alawcount = just_read; + } else { + alawcount = 0; + } + break; + + case EFFECT_RING: /* somebody's calling */ + case EFFECT_RINGING: /* waiting for the other end to pick up the phone */ + case EFFECT_TEST: /* play test sound (e.g. line level check) */ + case EFFECT_TOUCHTONE:/* play a touchtone */ + case EFFECT_EMPTY: /* silence for llcheck */ + for (i = 0; i < sizeof(alawbuffer) / 4; ++i) + alawbuffer[i] = fxgenerate(session, + session->effect, + session->touchtone_index, + effectpos++ / 8000.0); + alawcount = sizeof(alawbuffer) / 4; + break; + + default: + errprintf("EFFECT: Unknown effect %d to play, exiting thread\n", + session->effect); + return (gpointer) 1; + } + + if (alawcount == 0) { + /* end of file */ + dbgprintf(1 ,"EFFECT: End-of-file reached, stopping playback\n"); + + /* set non-blocking mode */ + snd_pcm_nonblock(session->audio_out, 1); + /* drain output buffer in order not to cut last seconds of playback */ + snd_pcm_drain(session->audio_out); + term_retry = 15; + while (!thread_is_stopping(&session->thread_effect) && term_retry--) { + usleep(20); + } + /* drop the rest, if any, and quit effect thread */ + snd_pcm_drop(session->audio_out); + break; + } + + /* convert A-law to audio */ + convert_isdn_to_audio(session, + alawbuffer, alawcount, + sndbuffer, &sndcount, + recbuffer, 0); + sndcount /= framesize; + + /* play it! */ + ptr = 0; + err = 0; + while (ptr < sndcount && !thread_is_stopping(&session->thread_effect)) { + size = sndcount - ptr; + if (size > 512) /* limit to 512B/syscall to allow timely stopping */ + size = 512; + if ((err = snd_pcm_writei(session->audio_out, + sndbuffer + ptr * framesize, + size)) < 0) { + err = session_snd_pcm_recover(session, session->audio_out, err); + if (err >= 0) + continue; /* retry */ + errprintf("EFFECT: Error writing effect to audio device: %s\n", + snd_strerror(err)); + break; + } else { + ptr += err; + + /* try to read some audio to adjust line level for input */ + err = snd_pcm_readi(session->audio_in, sndbuffer, size); + if (err < 0) { + /* restart audio input (it slipped) */ + err = session_snd_pcm_recover(session, session->audio_in, err); + } else if (err > 0) { + /* convert read data, this updates llcheck */ + convert_audio_to_isdn(session, sndbuffer, err, alawbuffer, &alawcount, recbuffer); + } + err = 0; + } + } + if (err < 0) + break; + } if (session->effect == EFFECT_SOUNDFILE) { - just_read = sf_readf_short(session->effect_sndfile, - session->effect_sndfile_buffer, session->effect_sndfile_buffer_frames); + /* signalise we have stopped playback */ + sf_close(session->effect_sndfile); + session->effect_sndfile = 0; } - for (i = 0; i < session->effect_sfinfo.channels; i++) - session->effect_sndfile_buffer[session->effect_sndfile_buffer_frames * - session->effect_sfinfo.channels + i] = - session->effect_sndfile_buffer[(session->effect_sndfile_buffer_frames-1) * - session->effect_sfinfo.channels + i]; - - /* fill buffer */ - while (pos < session->fragment_size_out) { - if (session->effect == EFFECT_SOUNDFILE) { - double sum = 0.0; - double leftbound = speed_factor * index_out; /* sndfile bounds */ - double rightbound = speed_factor * (index_out + 1); - double dummy; - double frac = modf(leftbound, &dummy); - - while (leftbound < rightbound) { - double frame_sum = 0; - double weight; - if (rightbound - leftbound < 1.0) - weight = rightbound - leftbound; - else - weight = 1.0; - - sndfile_buffer_index = (int)leftbound; - if (sndfile_buffer_index < just_read) { - int t_count; - for (t_count=0; t_count < session->effect_sfinfo.channels; t_count++){ - frame_sum += (1.0 - frac) * session->effect_sndfile_buffer[ - sndfile_buffer_index * session->effect_sfinfo.channels + t_count] - + frac * session->effect_sndfile_buffer[ - (sndfile_buffer_index+1)*session->effect_sfinfo.channels+t_count]; - } - frame_sum /= session->effect_sfinfo.channels; - } else { - frame_sum = 0; - } - sum += weight * frame_sum; - leftbound += 1.0; - } - sum /= speed_factor; - x = session->audio_LUT_generate[(int)sum / 256 + 128]; - } else { - double seconds = (double)session->effect_pos / session->audio_speed_out; - x = fxgenerate(session, session->effect, 0, seconds); - } - if (session->audio_sample_size_out == 1) { - session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x]; - } else { /* audio_sample_size == 2 */ - session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x * 2]; - session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x * 2 + 1]; - } - index_out ++; - session->effect_pos++; - } - - /* play it! */ - write_buf(session->audio_fd_out, - session->audio_outbuf, - session->fragment_size_out); + session->effect = EFFECT_NONE; - if (session->effect == EFFECT_SOUNDFILE && - just_read < session->effect_sndfile_buffer_frames && - session->effect_filename) - { - gtk_timeout_add( - 1000 * session->fragment_size_out / session->audio_sample_size_out * - (audio_get_write_fragments_total(session->audio_fd_out) - - audio_get_write_fragments_number(session->audio_fd_out)) / - session->audio_speed_out, - session_effect_stop_cb, session); - /* session->effect_filename provides a flag if we already want to stop */ - free(session->effect_filename); - session->effect_filename = NULL; - } + /* set non-blocking mode */ + snd_pcm_nonblock(session->audio_out, 1); + + /* playback stopped, reset audio (devices closed elsewhere) */ + audio_stop(session->audio_in, session->audio_out); + + dbgprintf(1, "EFFECT: Stopping effect thread\n"); + + return (gpointer) 0; } -/* - * Start an effect (effect_t) playing on the sound device - * - * on kind == EFFECT_SOUNDFILE, session->effect_filename should be - * initialized so that session_effect_stop can free() it afterwards - */ -void session_effect_start(session_t *session, enum effect_t kind) { +/*--------------------------------------------------------------------------*/ + +void session_effect_start(session_t *session, enum effect_t kind) +{ + session_effect_stop(session); + + if (session_set_audio_state(session, AUDIO_EFFECT) < 0) { + /* cannot open audio */ + return; + } + if (kind == EFFECT_SOUNDFILE) { session->effect_sfinfo.format = 0; if (!(session->effect_sndfile = sf_open(session->effect_filename, SFM_READ, &session->effect_sfinfo))) { - fprintf(stderr, "Error on opening sound file.\n"); + errprintf("EFFECT: Error opening sound file '%s' for playback.\n", + session->effect_filename); + return; } - session->effect_sndfile_buffer_frames = - session->fragment_size_out / session->audio_sample_size_out * - session->effect_sfinfo.samplerate / session->audio_speed_out; - session->effect_sndfile_buffer = (short*) malloc (sizeof(short) * - (session->effect_sndfile_buffer_frames + 1) * /* doubled dummy at end */ - session->effect_sfinfo.channels); - } - session->effect_tag = gtk_input_add_full(session->audio_fd_out, - GDK_INPUT_WRITE, - session_effect_callback, - NULL, - (gpointer) session, - NULL); session->effect = kind; session->effect_pos = 0; + thread_start(&session->thread_effect, handler_effect, session); } -/* - * Reset sound device and unset callback - */ -void session_effect_stop(session_t *session) { +/*--------------------------------------------------------------------------*/ + +void session_effect_stop(session_t *session) +{ + thread_stop(&session->thread_effect); if (session->effect != EFFECT_NONE) { /* stop only if already playing */ - if (session->effect == EFFECT_SOUNDFILE) { - sf_close(session->effect_sndfile); - free(session->effect_sndfile_buffer); - } - gtk_input_remove(session->effect_tag); session->effect = EFFECT_NONE; } - session_io_handlers_stop(session); - session_reset_audio(session); - session_io_handlers_start(session); } -/* - * Sets status bar audio state (e.g. "AUDIO OFF") - * hide if note is "" - */ -void session_audio_notify(session_t *session, char *note) { +/*--------------------------------------------------------------------------*/ + +static void session_audio_notify(session_t *session, char *note) +{ GtkWidget *dummy_label; /* needed to adjust size of sub-statusbar */ GtkRequisition requisition; @@ -915,28 +1420,21 @@ void session_audio_notify(session_t *session, char *note) { } } -/* - * Sets new state in session and GUI (also handles audio state) - * - * returns 0 on success, -1 otherwise (can't open audio device) - */ -int session_set_state(session_t *session, enum state_t state) { +/*--------------------------------------------------------------------------*/ + +int session_set_state(session_t *session, enum state_t state) +{ int result = 0; /* open / close audio when needed, set state */ session_io_handlers_stop(session); - if (session->option_release_devices && state != session->state) { - if (state == STATE_READY && session->state != STATE_RINGING_QUIET) { - /* release */ - session_audio_deinit(session); - } else if ((session->state == STATE_READY || - session->state == STATE_RINGING_QUIET) && - state != STATE_READY && state != STATE_RINGING_QUIET) { - /* (try to) resume */ - if (session_audio_init(session)) { - state = session->state; - result = -1; - } + if (state == STATE_READY && state != session->state && session->state != STATE_RINGING_QUIET) { + /* release audio if going to idle state */ + session_effect_stop(session); + if (session->option_release_devices) { + session_set_audio_state(session, AUDIO_DISCONNECTED); + } else { + session_set_audio_state(session, AUDIO_IDLE); } } session->state = state; @@ -951,7 +1449,7 @@ int session_set_state(session_t *session, enum state_t state) { case STATE_DIALING: if (session->effect == EFFECT_NONE) session_effect_start(session, EFFECT_RINGING); - if (debug) fprintf(stderr, "New state: STATE_DIALING\n"); + dbgprintf(1, "SESSION: New state: STATE_DIALING\n"); break; case STATE_RINGING: if (session->option_popup) { @@ -959,38 +1457,36 @@ int session_set_state(session_t *session, enum state_t state) { } if (session->effect == EFFECT_NONE) session_effect_start(session, EFFECT_RING); - if (debug) fprintf(stderr, "New state: STATE_RINGING\n"); + dbgprintf(1, "SESSION: New state: STATE_RINGING\n"); break; case STATE_RINGING_QUIET: if (session->option_popup) { gtk_window_present(GTK_WINDOW(session->main_window)); } - if (debug) fprintf(stderr, "New state: STATE_RINGING_QUIET\n"); + dbgprintf(1, "SESSION: New state: STATE_RINGING_QUIET\n"); break; case STATE_READY: gtk_widget_grab_focus(GTK_WIDGET(GTK_COMBO(session->dial_number_box) ->entry)); - if (session->effect != EFFECT_NONE) - session_effect_stop(session); - if (debug) fprintf(stderr, "New state: STATE_READY\n"); + dbgprintf(1, "SESSION: New state: STATE_READY\n"); break; case STATE_CONVERSATION: - if (session->effect != EFFECT_NONE) - session_effect_stop(session); session->touchtone_countdown_isdn = 0; session->touchtone_countdown_audio = 0; - if (debug) fprintf(stderr, "New state: STATE_CONVERSATION\n"); + dbgprintf(1, "SESSION: New state: STATE_CONVERSATION\n"); break; case STATE_SERVICE: - if (debug) fprintf(stderr, "New state: STATE_SERVICE\n"); + dbgprintf(1, "SESSION: New state: STATE_SERVICE\n"); break; case STATE_PLAYBACK: if (session->effect == EFFECT_NONE) session_effect_start(session, EFFECT_SOUNDFILE); - if (debug) fprintf(stderr, "New state: STATE_PLAYBACK\n"); + dbgprintf(1, "SESSION: New state: STATE_PLAYBACK\n"); break; default: - fprintf(stderr, "Warning: session_set_state: Unhandled state.\n"); + errprintf("SESSION: session_set_state: Unknown state %d.\n", state); + result = -1; + break; } /* audio on / off notify */ @@ -1027,292 +1523,45 @@ int session_set_state(session_t *session, enum state_t state) { return result; } -/* - * Callback: reinitialize isdn input watchdog - */ -static gboolean gtk_isdn_input_defer_timeout(session_t* session) { - session->gtk_isdn_input_tag = gtk_input_add_full(session->isdn_fd, - GDK_INPUT_READ, - gtk_handle_isdn_input, - NULL, - (gpointer) session, - NULL); - return FALSE; /* don't call me regularly */ -} +/*--------------------------------------------------------------------------*/ -/* defer 300 milliseconds if appropriate */ -#define DEFER_INTERVAL 300 - -/* - * put a graceful delay into GTK main loop to prevent permanent isdn input - * callback - */ -static void gtk_isdn_input_defer(session_t* session) { - gtk_input_remove(session->gtk_isdn_input_tag); - session->gtk_isdn_input_tag = - gtk_timeout_add(DEFER_INTERVAL, - (GtkFunction) gtk_isdn_input_defer_timeout, - session); -} - -/* - * callback for gtk on isdn input - * - * input: data: session (session_t *) - * fd: file descriptor where we got the input from - * condition: will be GDK_INPUT_READ in this case - */ -void gtk_handle_isdn_input(gpointer data, gint fd _U_, - GdkInputCondition condition _U_) { - session_t *session = (session_t *) data; - char *temp; - - switch (session->state) { - case STATE_READY: /* we are in command mode */ - if (isdn_try_read_line(session)){ /* got new line: something happened */ - if (!strncmp(session->isdn_inbuf, "RING/", 5)) { /* -> RINGING state */ - session->isdn_inbuf[session->isdn_inbuf_len - 2] = 0; - /* caller id update */ - session->ring_time = time(NULL); - - /* save callee's number */ - free(session->to); - session->to = strdup(&session->isdn_inbuf[5]); - - cid_add_line(session, CALL_IN, NULL, session->to); - - if (session_set_state(session, STATE_RINGING)) - session_set_state(session, STATE_RINGING_QUIET); - - } else { /* something else */ - if (debug) - fprintf(stderr, "Unknown message from ISDN device.\n"); - } - } - break; - case STATE_DIALING: - if (isdn_try_read_line(session)){ /* a response to our dial request */ - if (strstr(session->isdn_inbuf, "BUSY\r\n")) { /* get back to READY */ - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(BUSY)")); - } else if (strstr(session->isdn_inbuf, "VCON\r\n")) { /* let's go! */ - session_start_conversation(session); /* including state transition */ - } else if (strstr(session->isdn_inbuf, "NO CARRIER\r\n")) { - /* timeout? */ - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(TIMEOUT)")); - } else { /* got some other modem answer string while dialing out */ - if (debug) - fprintf(stderr, "Unknown message from ISDN device.\n"); - } - } - break; - case STATE_RINGING: - case STATE_RINGING_QUIET: - if (isdn_try_read_line(session)){ /* got new line: something happened */ - if (strstr(session->isdn_inbuf, "VCON\r\n")) { /* let's go! */ - /* will only come in STATE_RINGING */ - session_start_conversation(session); /* including state transition */ - } else if (strstr(session->isdn_inbuf, "CALLER NUMBER: ")) { - /* got Caller ID */ - session->isdn_inbuf[session->isdn_inbuf_len - 2] = 0; - - /* save caller's number */ - free(session->from); - session->from = strdup(&session->isdn_inbuf[15]); - - /* complete from field */ - cid_set_from(session, session->from); - - /* execute command if given */ - if (session->exec_on_incoming) { - temp = strdup(session->exec_on_incoming); - substitute(&temp, "%n", session->to); - substitute(&temp, "%s", session->from); - execute(temp); - free(temp); - } - - } else if (strstr(session->isdn_inbuf, "RUNG\r\n")) { - /* caller giving up */ - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(RUNG)")); - cid_mark_row(session, session->cid_num - 1, TRUE); - } else { /* got some other modem answer string while it rings */ - if (debug) - fprintf(stderr, "Unknown message from ISDN device.\n"); - } - } - break; - case STATE_CONVERSATION: - process_isdn_source(session); - if (session->samples_in >= ISDN_SPEED) - session->samples_in %= ISDN_SPEED; - session->no_input = 0; - - if (session->aborted || session->hangup) { /* That's it! */ - - if (session->hangup) - session_deinit_conversation(session, 0); /* 0 == other side hung up */ - else - session_deinit_conversation(session, 1); /* 1 == let's hang up (I/O error) */ - - session_set_state(session, STATE_READY); - cid_set_duration(session, NULL); - } - break; - case STATE_SERVICE: - if (debug) - fprintf(stderr, "Note: Got ISDN input in service mode.\n"); - gtk_isdn_input_defer(session); - break; - case STATE_PLAYBACK: - if (debug) - fprintf(stderr, "Note: Got ISDN input in playback mode.\n"); - gtk_isdn_input_defer(session); - break; - default: - fprintf(stderr, - "Warning: gtk_handle_isdn_input: Unknown session state.\n"); - gtk_isdn_input_defer(session); +void session_make_call(session_t *session, char *number) +{ + if (session->state == STATE_READY || session->state == STATE_PLAYBACK) { + gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box)->entry), + number); + gtk_button_clicked(GTK_BUTTON(session->pick_up_button)); } } -/* - * callback for gtk on audio isdn input - * - * input: data: session (session_t *) - * fd: file descriptor where we got the input from - * condition: will be GDK_INPUT_READ in this case - */ -void gtk_handle_audio_input(gpointer data, gint fd _U_, - GdkInputCondition condition _U_) { +/*--------------------------------------------------------------------------*/ + +void gtk_handle_pick_up_button(GtkWidget *widget _U_, gpointer data) +{ session_t *session = (session_t *) data; - - switch (session->state) { - case STATE_READY: /* we are in command mode */ - if (debug > 1) - fprintf(stderr, "Warning: Got audio input in ready mode (ALSA?).\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - case STATE_DIALING: - if (debug > 1) - fprintf(stderr, "Warning: Got audio input in dialing mode (ALSA?).\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - case STATE_RINGING: - if (debug > 1) - fprintf(stderr, "Warning: Got audio input in ringing mode (ALSA?).\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - case STATE_RINGING_QUIET: - if (debug > 1) - fprintf(stderr, - "Warning: Got audio input in QUIET ringing mode.\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - case STATE_CONVERSATION: - process_audio_source(session); - if (session->samples_out >= session->audio_speed_in) - session->samples_out %= session->audio_speed_in; - session->no_input++; - - /* if no more input from isdn came, assume abort and switch back */ - if (session->no_input >= 10) { - /* XXX: reasonable number? */ - if (isdn_blockmode(session->isdn_fd, 0)) - fprintf(stderr, "Error: Could not switching off isdn blockmode.\n"); - session->no_input = 0; - } - if (session->aborted) { /* That's it! */ - - session_deinit_conversation(session, 1); - /* 1 == let's hang up (I/O error) */ - - session_set_state(session, STATE_READY); - cid_set_duration(session, NULL); - } - break; - case STATE_SERVICE: - if (debug > 1) - fprintf(stderr, "Warning: Got audio input in service mode (ALSA?).\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - case STATE_PLAYBACK: - if (debug > 1) - fprintf(stderr, "Warning: Got audio input in playback mode (ALSA?).\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - break; - default: - fprintf(stderr, - "Warning: gtk_handle_audio_input: Unknown session state.\n"); - /* flush audio input */ - read(session->audio_fd_in, - session->audio_inbuf, session->fragment_size_in); - } -} - -/* - * initiates dialing to specified number - * -> changes contents of dial entry and simulates pick up button - */ -void session_make_call(session_t *session, char *number) { - gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box)->entry), - number); - gtk_button_clicked(GTK_BUTTON(session->pick_up_button)); -} - -/* - * callback for GTK on pick up button clicked - * - * input: widget: the button - * data: will be a (session_t *) - */ -void gtk_handle_pick_up_button(GtkWidget *widget _U_, gpointer data) { - session_t *session = (session_t *) data; - char *s; /* the modem dial string */ const char *number; /* the number to dial "inside" gtk (entry) */ char *clear_number; /* number after un_vanity() */ - int result; switch (session->state) { case STATE_READY: /* we are in command mode and want to dial */ + session_activate_isdn(session, 1); number = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box) ->entry)); /* replace letters with numbers ("Vanity" Numbers) */ clear_number = un_vanity(strdup(number)); - if ((s = (char*) malloc(strlen(clear_number) + 5)) && - strcmp(clear_number, "")) { + if (strcmp(clear_number, "") != 0 && session->isdn_active) { if (!session_set_state(session, STATE_DIALING)) { /* dial only if audio is on etc. */ - snprintf(s, strlen(clear_number) + 5, "ATD%s\n", clear_number); - - tty_clear(session->isdn_fd); - result = tty_write(session->isdn_fd, s); - free(s); - if (result) { - fprintf(stderr, "Error dialing.\n"); + if (isdn_dial(&session->isdn, 0, clear_number) < 0) { + errprintf("SESSION: Error dialing number '%s'.\n", clear_number); } else { - /* update dial combo box */ session_history_add(session, number); - + /* caller id update */ session->ring_time = time(NULL); cid_add_line(session, CALL_OUT, session->msn, clear_number); - + /* save caller's and callee's number */ free(session->from); session->from = strdup(session->msn); @@ -1330,89 +1579,77 @@ void gtk_handle_pick_up_button(GtkWidget *widget _U_, gpointer data) { case STATE_DIALING: /* already dialing! */ break; case STATE_RINGING: /* we want to pick up the phone while it rings */ - tty_clear(session->isdn_fd); - if (tty_write(session->isdn_fd, "ATA\n")) - fprintf(stderr, "Error answering call.\n"); + if (isdn_pickup(&session->isdn) < 0) { + errprintf("SESSION: Error answering call.\n"); + session_set_state(session, STATE_READY); + } break; case STATE_RINGING_QUIET: if (!session_set_state(session, STATE_RINGING)) { - tty_clear(session->isdn_fd); - if (tty_write(session->isdn_fd, "ATA\n")) - fprintf(stderr, "Error answering call.\n"); + if (isdn_pickup(&session->isdn) < 0) { + errprintf("SESSION: Error answering call.\n"); + session_set_state(session, STATE_READY); + } } else { + if (isdn_pickup(&session->isdn) < 0) { + errprintf("SESSION: Error rejecting call due to audio problems.\n"); + session_set_state(session, STATE_READY); + } show_audio_error_dialog(); } break; case STATE_CONVERSATION: /* channel already working */ - fprintf(stderr, - "Non-sense warning: Pick up button pressed in conversation mode\n"); + errprintf("SESSION: Non-sense warning: Pick up button pressed in conversation mode\n"); break; case STATE_SERVICE: - fprintf(stderr, - "Non-sense warning: Pick up button pressed in service mode\n"); + errprintf("SESSION: Non-sense warning: Pick up button pressed in service mode\n"); break; case STATE_PLAYBACK: - fprintf(stderr, - "Non-sense warning: Pick up button pressed in playback mode\n"); + errprintf("SESSION: Non-sense warning: Pick up button pressed in playback mode\n"); break; default: - fprintf(stderr, - "Warning: gtk_handle_pick_up_button: Unknown session state.\n"); + errprintf("SESSION: Warning: gtk_handle_pick_up_button: Unknown session state.\n"); } } -/* - * callback for GTK on hang up button clicked, !!! also called on exit !!! - * - * input: widget: the button, NULL when called directly (on exit) - * data: will be a (session_t *) - */ -void gtk_handle_hang_up_button(GtkWidget *widget _U_, gpointer data) { +/*--------------------------------------------------------------------------*/ + +void gtk_handle_hang_up_button(GtkWidget *widget _U_, gpointer data) +{ session_t *session = (session_t *) data; switch (session->state) { case STATE_READY: /* we are already in command mode */ break; case STATE_DIALING:/* abort dialing */ - tty_clear(session->isdn_fd); - if (tty_write(session->isdn_fd, "ATH\n")) - fprintf(stderr, "Error answering call.\n"); - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(ABORTED)")); + session->hangup_reason = _("(ABORTED)"); + isdn_hangup(&session->isdn); break; case STATE_RINGING: /* reject call */ case STATE_RINGING_QUIET: /* reject call */ - tty_clear(session->isdn_fd); - if (tty_write(session->isdn_fd, "ATH\n")) - fprintf(stderr, "Error answering call.\n"); - session_set_state(session, STATE_READY); - cid_set_duration(session, _("(REJECTED)")); + session->hangup_reason = _("(REJECTED)"); + isdn_hangup(&session->isdn); break; case STATE_CONVERSATION: /* hang up (while b-channel is open) */ - session_deinit_conversation(session, 1); /* 1 == we hang up ourselves ;) */ - - session_set_state(session, STATE_READY); - cid_set_duration(session, NULL); + session->hangup_reason = NULL; + isdn_hangup(&session->isdn); break; case STATE_SERVICE: - fprintf(stderr, - "Non-sense warning: Hang up button pressed in service mode\n"); + errprintf("SESSION: Non-sense warning: Hang up button pressed in service mode\n"); break; case STATE_PLAYBACK: session_set_state(session, STATE_READY); break; default: - fprintf(stderr, - "Warning: gtk_handle_hang_up_button: Unknown session state.\n"); + errprintf("SESSION: Warning: gtk_handle_hang_up_button: Unknown session state.\n"); + break; } } -/* - * cut session->dial_number_history to specified size - * (session->dial_number_history_maxlen) - * -> and redisplay in session->dial_number_box - */ -static void session_history_normalize(session_t *session) { +/*--------------------------------------------------------------------------*/ + +static void session_history_normalize(session_t *session) +{ /* cut size if needed */ while (g_list_length(session->dial_number_history) > session->dial_number_history_maxlen + 1) { @@ -1426,12 +1663,8 @@ static void session_history_normalize(session_t *session) { session->dial_number_history); } -/* - * Add line to history of dial number combo box at start (first row) - * and care about maximum size of history - * - * number will be copied, so caller has to care about it's associated memory - */ +/*--------------------------------------------------------------------------*/ + void session_history_add(session_t *session, const char *number) { char *temp = strdup(number); @@ -1440,9 +1673,8 @@ void session_history_add(session_t *session, const char *number) { session_history_normalize(session); } -/* - * like session_history_add but _appending_ number to list - */ +/*--------------------------------------------------------------------------*/ + void session_history_append(session_t *session, char *number) { char *temp = strdup(number); @@ -1451,3 +1683,4 @@ void session_history_append(session_t *session, char *number) { session_history_normalize(session); } +/*--------------------------------------------------------------------------*/ diff --git a/src/session.h b/src/session.h index 6b5fdc2..32db0f2 100644 --- a/src/session.h +++ b/src/session.h @@ -4,6 +4,7 @@ * This file is part of ANT (Ant is Not a Telephone) * * Copyright 2002, 2003 Roland Stigge + * Copyright 2007 Ivan Schreter * * ANT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,149 +36,160 @@ /* GTK */ #include +#include + /* own header files */ #include "recording.h" +#include "isdn.h" +#include "thread.h" #define SESSION_PRESET_SIZE 4 + +/*! + * @brief Session states. + */ enum state_t { - STATE_READY, /* completely idle */ - STATE_RINGING, /* somebody's calling */ - STATE_RINGING_QUIET, /* same as above, audio off (device blocked) */ - STATE_DIALING, /* we are dialing out */ - STATE_CONVERSATION, /* we are talking */ - STATE_SERVICE, /* special mode (llcheck) */ - STATE_PLAYBACK, /* sound playback, usually recorded conversation */ + STATE_READY, /*!< completely idle */ + STATE_RINGING, /*!< somebody's calling */ + STATE_RINGING_QUIET, /*!< same as above, audio off (device blocked) */ + STATE_DIALING, /*!< we are dialing out */ + STATE_CONVERSATION, /*!< we are talking */ + STATE_SERVICE, /*!< special mode (llcheck) */ + STATE_PLAYBACK, /*!< sound playback, usually recorded conversation */ - STATE_NUMBER /* dummy to calculate size */ + STATE_NUMBER /*!< dummy to calculate size */ }; +/*! + * @brief Known audio effects. + */ enum effect_t { - EFFECT_NONE, /* nothing is played currently */ - EFFECT_RING, /* somebody's calling */ - EFFECT_RINGING, /* waiting for the other end to pick up the phone */ - EFFECT_TEST, /* play test sound (e.g. line level check) */ - EFFECT_TOUCHTONE,/* play a touchtone */ - EFFECT_SOUNDFILE /* play sound from file */ + EFFECT_NONE, /*!< nothing is played currently */ + EFFECT_RING, /*!< somebody's calling */ + EFFECT_RINGING, /*!< waiting for the other end to pick up the phone */ + EFFECT_TEST, /*!< play test sound (e.g. line level check) */ + EFFECT_TOUCHTONE,/*!< play a touchtone */ + EFFECT_EMPTY, /*!< don't play anything */ + EFFECT_SOUNDFILE /*!< play sound from file */ }; -/* - * Data needed for setting the session state (the state is the index) +/*! + * @brief Audio states. + */ +enum audio_t { + AUDIO_DISCONNECTED, /*!< audio is disconnected */ + AUDIO_IDLE, /*!< audio is connected, but idle */ + AUDIO_EFFECT, /*!< audio is playing an effect */ + AUDIO_CONVERSATION /*!< audio is used for conversation */ +}; + +/*! + * @brief Data needed for setting the session state (the state is the index). */ struct state_data_t { - char *status_bar; - char *pick_up_label; - int pick_up_state; - char *hang_up_label; - int hang_up_state; + char *status_bar; /*!< what to display in status bar */ + char *pick_up_label; /*!< label for pick-up button */ + int pick_up_state; /*!< state for pick-up button */ + char *hang_up_label; /*!< label for hang-up button */ + int hang_up_state; /*!< state for hang-up button */ }; -struct state_data_t state_data[STATE_NUMBER]; +/*! + * @brief GUI state data for various session states. + */ +extern struct state_data_t state_data[STATE_NUMBER]; -/* - * session data +/*! + * @brief Session data. */ typedef struct { /* audio device data */ - char *audio_device_name_in; /* allocated memory! */ - char *audio_device_name_out; /* allocated memory! */ - int audio_fd_in; /* audio device file descriptors */ - int audio_fd_out; - unsigned int audio_speed_in; /* sound device recording speed */ - unsigned int audio_speed_out; /* sound device playback speed */ - int fragment_size_in; /* sound device buffer fragment sizes in bytes */ - int fragment_size_out; - int *format_priorities; /* 0-terminated sorted list of preferenced formats */ - int audio_format_in; /* the actual formats */ - int audio_format_out; - int audio_sample_size_in; /* number of bytes of a sample */ - int audio_sample_size_out; - unsigned char *audio_inbuf; /* buffer: should have size fragment_size_in */ - unsigned char *audio_outbuf; /* buffer: should have size fragment_size_out */ - int audio_outbuf_index; /* needed for conversation mode output memory */ + char *audio_device_name_in; /*!< name of input audio device */ + char *audio_device_name_out; /*!< name of output audio device */ + snd_pcm_t *audio_in; /*!< input audio device PCM handle */ + snd_pcm_t *audio_out; /*!< output audio device PCM handle */ + enum audio_t audio_state; /*!< current audio state */ + unsigned int audio_speed_in; /*!< audio device recording speed */ + unsigned int audio_speed_out; /*!< audio device playback speed */ + int fragment_size_in; /*!< audio input fragment sizes in bytes */ + int fragment_size_out; /*!< audio output fragment sizes in bytes */ + int *format_priorities; /*!< 0-terminated sorted list of preferred audio formats (ALSA constants) */ + int audio_format_in; /*!< used audio in format */ + int audio_format_out; /*!< used audio out format */ + int audio_sample_size_in; /*!< number of bytes of an input audio sample */ + int audio_sample_size_out; /*!< number of bytes of an output audio sample */ + isdn_speed_t audio_out_speed; /*!< actual audio out speed */ + isdn_speed_t audio_in_speed; /*!< actual audio in speed */ + thread_t thread_audio_input; /*!< audio data input thread */ - /* isdn data */ - char* isdn_device_name; /* "/dev/ttyIxx", xx == 0 .. 63 */ - int isdn_fd; - char* isdn_lockfile_name; - int isdn_inbuf_size; - int isdn_outbuf_size; - unsigned char *isdn_inbuf; /* 8 bit ulaw */ - unsigned char *isdn_outbuf; - int isdn_inbuf_len; /* index of '\0' in audio_inbuf in command mode, - if char at this index != 0 -> reading line */ - int isdn_outbuf_index; /* needed for conversation mode output memory */ - int escape; /* escape mode/state RECEIVING isdn data */ - int no_input; /* after this many select calls without isdn input - get back from blockmode */ - struct termios isdn_backup; /* saved state to restore on exit */ + /* ISDN data */ + isdn_t isdn; /*!< ISDN handle */ + unsigned int isdn_active; /*!< flag for active CAPI connection */ - char *from; /* caller's number */ - char *to; /* callee's number */ + char *from; /*!< caller's number */ + char *to; /*!< callee's number */ + char *hangup_reason; /*!< reason for hangup */ /* mediation data */ /* Look-up-tables for audio <-> isdn conversion: */ - unsigned char *audio_LUT_in; /* isdn -> audio */ - unsigned char *audio_LUT_out; /* audio -> isdn */ - unsigned char *audio_LUT_generate; /* 8 bit unsigned -> isdn */ - unsigned char *audio_LUT_analyze; /* isdn -> 8 bit unsigned */ - short *audio_LUT_ulaw2short; /* unsigned char (ulaw) -> short */ - double ratio_in; /* ratio: audio output rate / isdn input rate */ - double ratio_out; /* ratio: isdn output rate / audio input rate */ - unsigned int samples_in; /* ring counter of samples: from isdn */ - unsigned int samples_out; /* ... ... : from sound device */ + unsigned char *audio_LUT_in; /*!< lookup table ISDN -> audio */ + unsigned char *audio_LUT_out; /*!< lookup table audio -> ISDN */ + unsigned char *audio_LUT_generate; /*!< lookup table 8 bit unsigned -> ISDN */ + unsigned char *audio_LUT_analyze; /*!< lookup table ISDN -> 8 bit unsigned */ + short *audio_LUT_alaw2short; /*!< lookup table unsigned char (alaw) -> short */ + double ratio_in; /*!< ratio: audio output rate / ISDN input rate */ + double ratio_out; /*!< ratio: ISDN output rate / audio input rate */ /* recording data */ - short *rec_buf_local; /* pointers to recording buffers */ - short *rec_buf_remote; - int rec_buf_local_index; /* number of shorts in local recording buffer */ - int rec_buf_remote_index; /* number of shorts in local recording buffer */ - int rec_buf_local_size; /* recording buffer sizes */ - int rec_buf_remote_size; - struct recorder_t *recorder; /* recorder internal data */ + struct recorder_t *recorder; /*!< recorder internal data */ + + /* level check data */ + double llcheck_in_state; /*!< current input value for level check */ + double llcheck_out_state; /*!< current output value for level check */ + guint gtk_updater_timer_tag; /*!< GTK timer tag for updating levels */ + + remote_call_port_t rem_port; /*!< remote call port to call functions in session thread */ /* GUI elements in this session (GTK specific) */ - GtkWidget *main_window; /* the main window (with style ...) */ - GtkWidget *pick_up_button; /* the pick up button to enable / disable */ - GtkWidget *pick_up_label; /* the label on the pick up button */ - GtkWidget *hang_up_button; /* the hang up button to enable / disable */ - GtkWidget *hang_up_label; /* the label on the hang up button */ - GtkWidget *dial_number_box; /* the dial number combo box */ - GList *dial_number_history; /* the last called numbers */ - unsigned int dial_number_history_maxlen; /* how many numbers to remember */ - unsigned int dial_number_history_pointer; /* which one to use next if req */ - GtkWidget *status_bar; /* the status bar */ - gint phone_context_id; /* a context for the status bar */ - GtkWidget *audio_warning; /* inside status bar */ - gint audio_context_id; /* a context for audio_warning */ + GtkWidget *main_window; /*!< the main window (with style ...) */ + GtkWidget *pick_up_button; /*!< the pick up button to enable / disable */ + GtkWidget *pick_up_label; /*!< the label on the pick up button */ + GtkWidget *hang_up_button; /*!< the hang up button to enable / disable */ + GtkWidget *hang_up_label; /*!< the label on the hang up button */ + GtkWidget *dial_number_box; /*!< the dial number combo box */ + GList *dial_number_history; /*!< the last called numbers */ + unsigned int dial_number_history_maxlen; /*!< how many numbers to remember */ + unsigned int dial_number_history_pointer; /*!< which one to use next if req */ + GtkWidget *status_bar; /*!< the status bar */ + gint phone_context_id; /*!< a context for the status bar */ + GtkWidget *audio_warning; /*!< inside status bar */ + gint audio_context_id; /*!< a context for audio_warning */ - GtkWidget *llcheck; /* line level check widget inside status bar */ - GtkWidget *llcheck_in; /* input level meter */ - GtkWidget *llcheck_out; /* output level meter */ - GtkWidget *llcheck_check_menu_item; /* state of line levels (status bar) */ + GtkWidget *llcheck; /*!< line level check widget inside status bar */ + GtkWidget *llcheck_in; /*!< input level meter */ + GtkWidget *llcheck_out; /*!< output level meter */ + GtkWidget *llcheck_check_menu_item; /*!< state of line levels (status bar) */ - guint gtk_isdn_input_tag; /* these tags are saved to later remove handlers */ - guint gtk_audio_input_tag; - - GtkWidget *controlpad; /* key pad etc. */ - GtkWidget *controlpad_check_menu_item; /* display state of control pad */ - GtkWidget *mute_button; /* toggle button */ - GtkWidget *muted_warning; /* show in status bar if muted */ - gint muted_context_id; /* a context for the status bar */ + GtkWidget *controlpad; /*!< key pad etc. */ + GtkWidget *controlpad_check_menu_item; /*!< display state of control pad */ + GtkWidget *mute_button; /*!< mute toggle button */ + GtkWidget *muted_warning; /*!< show in status bar if muted */ + gint muted_context_id; /*!< a context for mute in the status bar */ - GtkWidget *record_checkbutton; /* recording checkbutton */ - GtkWidget *record_checkbutton_local; /* local recording checkbutton */ - GtkWidget *record_checkbutton_remote; /* remote recording checkbutton */ + GtkWidget *record_checkbutton; /*!< recording checkbutton */ + GtkWidget *record_checkbutton_local; /*!< local recording checkbutton */ + GtkWidget *record_checkbutton_remote; /*!< remote recording checkbutton */ /* caller id related */ - GtkWidget *cid; /* the caller id widget itself (to show/hide) */ - GtkWidget *cid_check_menu_item; /* to handle state of cid monitor (show?) */ - GtkWidget *cid_list; /* the list to hold the individual call data */ - GtkWidget *cid_scrolled_window; /* the home of the clist with adjustments */ - gint cid_num; /* number of rows in list */ - gint cid_num_max; /* maximum number of rows in list */ - time_t vcon_time; /* the start of conversation mode (for duration calc.) */ - time_t ring_time; /* the first sign of the conversation (dial/ring) */ + GtkWidget *cid; /*!< the caller id widget itself (to show/hide) */ + GtkWidget *cid_check_menu_item; /*!< to handle state of cid monitor (show?) */ + GtkWidget *cid_list; /*!< the list to hold the individual call data */ + GtkWidget *cid_scrolled_window; /*!< the home of the clist with adjustments */ + gint cid_num; /*!< number of rows in list */ + gint cid_num_max; /*!< maximum number of rows in list */ + time_t vcon_time; /*!< the start of conversation mode (for duration calc.) */ + time_t ring_time; /*!< the first sign of the conversation (dial/ring) */ /* the symbols for the CList */ GdkPixmap *symbol_in_pixmap; GdkBitmap *symbol_in_bitmap; @@ -186,87 +198,228 @@ typedef struct { GdkPixmap *symbol_record_pixmap; GdkBitmap *symbol_record_bitmap; - GtkWidget *menuitem_settings; /* Menu items to select / deselect */ + GtkWidget *menuitem_settings; /*!< Menu items to select / deselect */ GtkWidget *menuitem_line_check; /* ringing etc. */ - guint effect_tag; /* remove this callback after e.g. ringing */ - enum effect_t effect; /* which effect is currently been played? */ - unsigned int effect_pos; /* sample position in effect */ - char* effect_filename; /* the file to playback */ - SNDFILE* effect_sndfile; /* the handle */ - SF_INFO effect_sfinfo; /* info struct about effect_sndfile */ - short* effect_sndfile_buffer;/*temporary buffer to construct playback output*/ - int effect_sndfile_buffer_frames;/*number of frames of effect_sndfile_buffer*/ - time_t effect_playback_start_time; /* start time of playback */ - int touchtone_countdown_isdn; /* number of samples yet to play */ - int touchtone_countdown_audio; - int touchtone_index; /* which touchtone */ + thread_t thread_effect; /*!< effect thread, e.g., for ringing */ + enum effect_t effect; /*!< which effect is currently been played? */ + unsigned int effect_pos; /*!< sample position in effect */ + char* effect_filename; /*!< the file to play back */ + SNDFILE* effect_sndfile; /*!< sound file handle */ + SF_INFO effect_sfinfo; /*!< info struct about effect_sndfile */ + time_t effect_playback_start_time; /*!< start time of playback */ + int touchtone_countdown_isdn; /*!< number of samples yet to play */ + int touchtone_countdown_audio; /*!< number of samples yet to play */ + int touchtone_index; /*!< which touchtone */ /* phone specific */ - enum state_t state; /* which state we are currently in */ - int hangup; /* remote hangup */ - int aborted; /* i/o error (isdn or audio) */ + enum state_t state; /*!< which state we are currently in */ - char* msn; /* originating msn, allocated memory! */ - char* msns; /* comma-separated list of msns to listen on, allocated memory!*/ + char* msn; /*!< originating msn, allocated memory! */ + char* msns; /*!< comma-separated list of msns to listen on, allocated memory!*/ - int unanswered; /* unanswered calls for this session */ + int unanswered; /*!< unanswered calls for this session */ /* some options (useful for options file handling) */ - int option_save_options; /* save options on exit */ - int option_release_devices; /* close sound devices while not needed */ - int option_show_llcheck; /* show line level checks in main window */ - int option_show_callerid; /* show callerid part in main window */ - int option_show_controlpad; /* show control pad (key pad etc.) */ - int option_muted; /* mute microphone (other party gets zeros) */ - int option_record; /* record to file */ - int option_record_local; /* record local channel */ - int option_record_remote; /* record remote channel */ - enum recording_format_t option_recording_format; /* recording file format */ + int option_save_options; /*!< save options on exit */ + int option_release_devices; /*!< close sound devices while not needed */ + int option_show_llcheck; /*!< show line level checks in main window */ + int option_show_callerid; /*!< show callerid part in main window */ + int option_show_controlpad; /*!< show control pad (key pad etc.) */ + int option_muted; /*!< mute microphone (other party gets zeros) */ + int option_record; /*!< record to file */ + int option_record_local; /*!< record local channel */ + int option_record_remote; /*!< record remote channel */ + enum recording_format_t option_recording_format; /*!< recording file format */ - int option_calls_merge; /* merge isdnlog */ + int option_calls_merge; /*!< merge isdnlog */ int option_calls_merge_max_days; - char *exec_on_incoming; /* string with command to execute on incoming call */ - int option_popup; /* push main window to foreground on incoming call */ + char *exec_on_incoming; /*!< string with command to execute on incoming call */ + int option_popup; /*!< push main window to foreground on incoming call */ - char* preset_names[SESSION_PRESET_SIZE]; /* names and numbers for */ - char* preset_numbers[SESSION_PRESET_SIZE]; /* preset buttons */ + char* preset_names[SESSION_PRESET_SIZE]; /*!< names for preset buttons */ + char* preset_numbers[SESSION_PRESET_SIZE]; /*!< numbers for preset buttons */ + + int local_sock; /*!< unix domain socket for local server functionality */ + guint gtk_local_input_tag; /*!< GTK tag for GTK main loop select */ - int local_sock; /* unix domain socket for local server functionality */ - guint gtk_local_input_tag; /* gtk tag for gtk main loop select */ } session_t; + + +/*! + * @brief Default session. + */ extern session_t session; -int session_set_state(session_t *session, enum state_t state); -void session_io_handlers_start(session_t *session); -void session_io_handlers_stop(session_t *session); -int session_reset_audio(session_t *session); -int session_audio_open(session_t *session); -int session_audio_close(session_t *session); -int session_audio_init(session_t *session); -int session_audio_deinit(session_t *session); + +/*! + * @brief Sets new state in session and GUI (also handles audio state). + * + * @param session session to use. + * @param state new session state (@see state_t). + * @return 0 on success, -1 otherwise (e.g., can't open audio device). + */ +int session_set_state(session_t *session, enum state_t state); + +/*! + * @brief Activate/deactivate ISDN connection. + * + * @param session session to use. + * @param activate if nonzero, activate ISDN, otherwise deactivate. + * @return 0 on success, -1 otherwise (e.g., can't open ISDN device). + */ +int session_activate_isdn(session_t *session, unsigned int activate); + + + +/*! + * @brief Set up GTK I/O handlers. + * + * @param session session to use. + */ +void session_io_handlers_start(session_t *session); + +/*! + * @brief Remove GTK handlers. + * + * @param session session to use. + */ +void session_io_handlers_stop(session_t *session); + + + +/*! + * @brief Set audio device state in session. + * + * @param session session. + * @param state requested audio state (@see audio_t). + * @return 0 on success, -1 otherwise. + */ +int session_set_audio_state(session_t *session, enum audio_t state); + +/*! + * @brief Resets audio devices by closing and reopening. + * + * @param session session. + * @return 0 on success, -1 otherwise. + * + * @note Stop I/O handlers first! + * Only resets currently used device(s). To use another device, + * use session_set_audio_state() with AUDIO_DISCONNECTED to + * disconnect the old device first. + */ +int session_reset_audio(session_t *session); + +/*! + * @brief Initialize a session (ISDN and audio devices) and read options file. + * + * @param session session, empty, to be filled. + * @param audio_device_name_in default name of input audio device. + * @param audio_device_name_out default name of output audio device. + * @param msn default MSN to use. + * @param msns default set of MSNs to listen on. + * @return 0 on success, -1 otherwise. + * + * @note The latter 4 parameters are only the defaults. They are normally + * overridden by the options file. + */ int session_init(session_t *session, char *audio_device_name_in, char *audio_device_name_out, char *msn, char *msns); -void session_audio_free(session_t *session); + +/*! + * @brief Clean up a session (ISDN and audio devices). + * + * @param session session to clean up. + * @return 0 on success, -1 otherwise. + */ int session_deinit(session_t *session); + + +/*! + * @brief Start an effect (effect_t) playing on the sound device. + * + * If kind == EFFECT_SOUNDFILE, session->effect_filename should be + * initialized. session_effect_stop() will free() it afterwards. + * + * @param session session. + * @param kind effect kind. + */ void session_effect_start(session_t *session, enum effect_t kind); + +/*! + * @brief Stop playing an effect. + * + * @param session session. + */ void session_effect_stop(session_t *session); -void gtk_handle_isdn_input(gpointer data, gint fd, - GdkInputCondition condition); -void gtk_handle_audio_input(gpointer data, gint fd, - GdkInputCondition condition); + + +/*! + * @brief Start recording on a session. + * + * @param session session on which to record. + * @return 0 on success, -1 otherwise. + */ +int session_start_recording(session_t *session); + + + +/*! + * @brief Initiates dialing to specified number. + * + * Changes contents of dial entry and simulates pick up button. + * + * @param session session. + * @param number number to dial. + */ void session_make_call(session_t *session, char *number); + +/*! + * @brief Callback from GTK on pick up button clicked. + * + * @param widget the button. + * @param data session. + */ void gtk_handle_pick_up_button(GtkWidget *widget, gpointer data); + +/*! + * @brief Callback from GTK on hang up button clicked. + * + * @note Also called on exit. + * + * @param widget the button, NULL when called directly (on exit). + * @param data session. + */ void gtk_handle_hang_up_button(GtkWidget *widget, gpointer data); + + +/*! + * @brief Add line to history of dial number combo box as first row. + * + * Also check maximum size of history. + * + * @param session session. + * @param number number to add (will be copied). + */ void session_history_add(session_t *session, const char *number); + +/*! + * @brief Add line to history of dial number combo box as last row. + * + * Also check maximum size of history. + * + * @param session session. + * @param number number to add (will be copied). + */ void session_history_append(session_t *session, char *number); + #endif /* session.h */ diff --git a/src/settings.c b/src/settings.c index 93010d5..5774b06 100644 --- a/src/settings.c +++ b/src/settings.c @@ -202,7 +202,7 @@ void settings_options_read(session_t *session) { /* read isdn4linux config */ isdn_lexer_init(ISDN_CONFIG_FILENAME); if (!isdn_in) { - fprintf(stderr, "Warning: Couldn't read ISDN config file.\n"); + errprintf("Warning: Couldn't read ISDN config file.\n"); } else { isdn_tree_node_t* node; @@ -238,7 +238,7 @@ void settings_options_read(session_t *session) { } if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } @@ -246,14 +246,14 @@ void settings_options_read(session_t *session) { if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_OPTIONS_FILENAME) < 0) { - fprintf(stderr, + errprintf( "Warning: Couldn't allocate memory for options filename.\n"); return; } isdn_lexer_init(filename); if (!isdn_in) { - fprintf(stderr, "Warning: No options file available.\n"); + errprintf("Warning: No options file available.\n"); } else { isdn_tree_node_t* node; @@ -268,20 +268,18 @@ void settings_options_read(session_t *session) { while (node != NULL) { switch (node->type) { case ISDN_NODE_TYPE_ENTRY: - if (debug) - printf("Setting \"%s\" to \"%s\"...\n", + dbgprintf(1, "Setting \"%s\" to \"%s\"...\n", node->name, node->content.value); settings_option_set(session, node->name, node->content.value); break; case ISDN_NODE_TYPE_SECTION: - fprintf(stderr, "Warning: Unexpected section \"%s\".\n", node->name); + errprintf("Warning: Unexpected section \"%s\".\n", node->name); break; case ISDN_NODE_TYPE_SUBSECTION: - fprintf(stderr, - "Warning: Unexpected subsection \"%s\".\n", node->name); + errprintf("Warning: Unexpected subsection \"%s\".\n", node->name); break; default: - fprintf(stderr, "Unknown ISDN_NODE_TYPE\n"); + errprintf("Unknown ISDN_NODE_TYPE\n"); } node = node->next; @@ -302,7 +300,7 @@ void settings_options_write(session_t *session) { FILE *f; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } @@ -311,7 +309,7 @@ void settings_options_write(session_t *session) { if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_OPTIONS_FILENAME) < 0) { - fprintf(stderr, + errprintf( "Warning: Couldn't allocate memory for options filename.\n"); return; } @@ -405,10 +403,10 @@ void settings_options_write(session_t *session) { session->option_calls_merge_max_days); if (fclose(f) == EOF) { - fprintf(stderr, "Warning: Couldn't close options file.\n"); + errprintf("Warning: Couldn't close options file.\n"); } - } else if (debug) { - fprintf(stderr, "Warning: Can't write to options file.\n"); + } else { + dbgprintf(1, "Warning: Can't write to options file.\n"); } free(filename); } @@ -423,19 +421,18 @@ void settings_history_read(session_t *session) { ssize_t got; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_HISTORY_FILENAME) < 0) { - fprintf(stderr, + errprintf( "Warning: Couldn't allocate memory for history filename.\n"); return; } - if (debug) - fprintf(stdout, "Info: History Filename: %s.\n", filename); + dbgprintf(1, "Info: History Filename: %s.\n", filename); if ((f = fopen(filename, "r"))) { do { got = getline(&lineptr, &linesize, f); @@ -444,16 +441,15 @@ void settings_history_read(session_t *session) { } if (got > 0 && strlen(lineptr) > 0) { session_history_append(session, lineptr); - if (debug) - fprintf(stdout, "Info: History Number: %s.\n", lineptr); + dbgprintf(1, "Info: History Number: %s.\n", lineptr); } } while (got > 0); if (fclose(f) == EOF) { - fprintf(stderr, "Warning: Couldn't close history file.\n"); + errprintf("Warning: Couldn't close history file.\n"); } - } else if (debug) { - fprintf(stderr, "Warning: No history file available.\n"); + } else { + dbgprintf(1, "Warning: No history file available.\n"); } free(filename); @@ -480,7 +476,7 @@ void settings_history_write(session_t *session) { FILE *f; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } @@ -489,7 +485,7 @@ void settings_history_write(session_t *session) { if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_HISTORY_FILENAME) < 0) { - fprintf(stderr, + errprintf( "Warning: Couldn't allocate memory for history filename.\n"); return; } @@ -499,10 +495,10 @@ void settings_history_write(session_t *session) { settings_history_write_line, f); if (fclose(f) == EOF) { - fprintf(stderr, "Warning: Couldn't close history file.\n"); + errprintf("Warning: Couldn't close history file.\n"); } - } else if (debug) { - fprintf(stderr, "Warning: Can't write to history file.\n"); + } else { + dbgprintf(1, "Warning: Can't write to history file.\n"); } free(filename); @@ -514,13 +510,13 @@ void settings_callerid_read(session_t *session) { char *filename; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_CALLERID_HISTORY_FILENAME) < 0) { - fprintf(stderr, "Warning: " + errprintf("Warning: " "Couldn't allocate memory for caller id history filename.\n"); return; } @@ -528,10 +524,10 @@ void settings_callerid_read(session_t *session) { if ((callerid_in = fopen(filename, "r"))) { callerid_parse(session); if (fclose(callerid_in) == EOF) { - fprintf(stderr, "Warning: Couldn't close callerid history file.\n"); + errprintf("Warning: Couldn't close callerid history file.\n"); } - } else if (debug) { - fprintf(stderr, "Warning: No caller id history file available.\n"); + } else { + dbgprintf(1, "Warning: No caller id history file available.\n"); } free(filename); @@ -552,7 +548,7 @@ void settings_callerid_write(session_t *session) { gchar *duration; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return; } @@ -561,7 +557,7 @@ void settings_callerid_write(session_t *session) { if (asprintf(&filename, "%s/." PACKAGE "/%s", homedir, SETTINGS_CALLERID_HISTORY_FILENAME) < 0) { - fprintf(stderr, "Warning: " + errprintf("Warning: " "Couldn't allocate memory for callerid history filename.\n"); return; } @@ -583,16 +579,16 @@ void settings_callerid_write(session_t *session) { fprintf (f, "%-19s|%-3s|%-20s|%-20s|%-10s\n", date, type, from, to, duration); if (debug > 1) - fprintf(stderr, "%-19s|%-3s|%-20s|%-20s|%-10s\n", + errprintf("%-19s|%-3s|%-20s|%-20s|%-10s\n", date, type, from, to, duration); } if (fclose(f) == EOF) { - fprintf(stderr, "Warning: Couldn't close callerid history file.\n"); + errprintf("Warning: Couldn't close callerid history file.\n"); } - } else if (debug) { - fprintf(stderr, "Warning: Can't write to callerid history file.\n"); + } else { + dbgprintf(1, "Warning: Can't write to callerid history file.\n"); } free(filename); diff --git a/src/sound.c b/src/sound.c index 67b6818..3992221 100644 --- a/src/sound.c +++ b/src/sound.c @@ -46,363 +46,300 @@ #include "globals.h" #include "sound.h" -int default_audio_priorities[] = {AFMT_S16_LE,/* try formats in this order */ \ - AFMT_S16_BE,\ - AFMT_U16_LE,\ - AFMT_U16_BE,\ - AFMT_U8,\ - AFMT_S8,\ - /* ulaw at last because no native soundcard support assumed */ \ - AFMT_MU_LAW,\ - 0}; /* end of list */ -/* - * common initialization for a specific audio device +/* try formats in this order */ +int default_audio_priorities[] = {SND_PCM_FORMAT_S16_LE, + SND_PCM_FORMAT_S16_BE, + SND_PCM_FORMAT_U16_LE, + SND_PCM_FORMAT_U16_BE, + SND_PCM_FORMAT_U8, + SND_PCM_FORMAT_S8, + /* alaw/ulaw at last because no native soundcard support assumed */ + SND_PCM_FORMAT_A_LAW, + SND_PCM_FORMAT_MU_LAW, + 0}; /* end of list */ + +/*--------------------------------------------------------------------------*/ + +/*! + * @brief Common initialization for a specific audio device * - * NOTE: Assumes opened device, but leaves it in a state not able for further - * use when not successful. The device has to be closed and opened again. + * @param audio PCM descriptor of an audio device. + * @param format PCM format. + * @param channels number of PCM channels. + * @param speed requested/actual sampling rate (in/out). + * @param fragment_size requested/actual fragment size (in/out). * - * input: audio_fd: valid file descriptor of an audio device, - * format, number of channels, requested sampling rate - * requested fragment sizes - * - * returns: 0 if successful, non-zero otherwise - * - * output: speed: actually sampling rate - * fragment_size: actually fragment size for this device - * + * @return 0 if successful, non-zero otherwise. */ -int init_audio_device(int audio_fd, int format, int channels, int *speed, - int *fragment_size) { - int temp_format; - int temp_channels; - int temp_speed; - int formats_mask; - int fragment_arg; +static int init_audio_device(snd_pcm_t *audio, + int format, + int channels, + unsigned int *speed, + int *fragment_size) +{ + int err; + unsigned int rspeed; + int dir; + unsigned int buffer_time, period_time; + snd_pcm_uframes_t period_size; - /* set fragment size */ - fragment_arg = 0x7FFF0000 + logb(*fragment_size); - - if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &fragment_arg)) { - perror("SNDCTL_DSP_SETFRAGMENT"); - return(-1); + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + + if ((err = snd_pcm_hw_params_any(audio, hwparams)) < 0) { + errprintf("AUDIO: AUDIO: No audio configurations available: %s\n", + snd_strerror(err)); + return err; } - /* query supported audio formats */ - if (ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &formats_mask) == -1) { - perror("SNDCTL_DSP_GETFMTS"); - return(-1); - } - if (!(formats_mask & format)) { /* requested format not supported */ - return(-1); /* caller should try another one */ + if ((err = snd_pcm_hw_params_set_format(audio, hwparams, format)) < 0) { + errprintf("AUDIO: Audio sample format (%d) not available: %s\n", + format, snd_strerror(err)); + return err; } - /* audio format */ - temp_format = format; - if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &temp_format) == -1) { - perror("SNDCTL_DSP_SETFMT"); - return(-1); + if ((err = snd_pcm_hw_params_set_channels(audio, hwparams, channels)) < 0) { + errprintf("AUDIO: Audio channels count (%d) not available: %s\n", + channels, snd_strerror(err)); + return err; } - /* number of channels */ - temp_channels = channels; - if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &temp_channels) == -1) { - perror("SNDCTL_DSP_CHANNELS"); - return(-1); + rspeed = *speed; + if ((err = snd_pcm_hw_params_set_rate_near(audio, hwparams, &rspeed, 0)) < 0) { + errprintf("AUDIO: Audio speed %dHz not available: %s\n", + *speed, snd_strerror(err)); + return err; } - if (temp_channels != channels) { - fprintf(stderr, "Error: %d not supported as number of channels", channels); - return(-1); + if (rspeed != *speed) { + errprintf("AUDIO: Audio speed doesn't match (requested %dHz, got %dHz)\n", + *speed, rspeed); + return -EINVAL; } - /* sampling rate */ - temp_speed = *speed; - if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &temp_speed) == -1) { - perror("SNDCTL_DSP_SPEED"); - /* rarely returned because of oss' liberal use of other possible speeds */ - return(-1); + buffer_time = 150000; + if ((err = snd_pcm_hw_params_set_buffer_time_near(audio, hwparams, &buffer_time, &dir)) < 0) { + errprintf("AUDIO: Unable to set audio buffer time %d: %s\n", + buffer_time, snd_strerror(err)); + return err; } - *speed = temp_speed; /* actually used speed */ - /* "verify" fragment size: - * let the driver actually calculate the fragment size - */ - if (ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &fragment_arg)) { - perror("SNDCTL_DSP_GETBLKSIZE"); - return(-1); + period_time = 25000; + if ((err = snd_pcm_hw_params_set_period_time_near(audio, hwparams, &period_time, &dir)) < 0) { + errprintf("AUDIO: Unable to set audio period time %d: %s\n", + period_time, snd_strerror(err)); + return err; } - if (fragment_arg != *fragment_size) - fprintf(stderr, "Note: Using non-default fragment size %d.\n", - fragment_arg); - *fragment_size = fragment_arg; + + if ((err = snd_pcm_hw_params_get_period_size_min(hwparams, &period_size, &dir)) < 0) { + errprintf("AUDIO: Unable to get audio period time: %s\n", + snd_strerror(err)); + return err; + } + *fragment_size = period_size; + + if ((err = snd_pcm_hw_params_set_access(audio, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + errprintf("AUDIO: Unable to set audio access mode: %s\n", + snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_hw_params(audio, hwparams)) < 0) { + errprintf("AUDIO: Unable to set hw params for audio: %s\n", + snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_current(audio, swparams)) < 0) { + errprintf("AUDIO: Unable to determine current swparams for audio: %s\n", + snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_start_threshold(audio, swparams, period_size)) < 0) { + errprintf("AUDIO: Unable to set start threshold: %s\n", + snd_strerror(err)); + return err; + } + + /* + if ((err = snd_pcm_sw_params_set_sleep_min(audio, swparams, 0)) < 0) { + errprintf("AUDIO: Unable to set minimum sleep time: %s\n", + snd_strerror(err)); + return err; + } + */ + + if ((err = snd_pcm_sw_params_set_xfer_align(audio, swparams, 1)) < 0) { + errprintf("AUDIO: Unable to set transfer alignment: %s\n", + snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_silence_size(audio, swparams, period_size * 2)) < 0) { + errprintf("AUDIO: Unable to set silence threshold: %s\n", + snd_strerror(err)); + return err; + } + + /* + if ((err = snd_pcm_sw_params_set_stop_threshold(audio, swparams, 0)) < 0) { + errprintf("AUDIO: Unable to set stop threshold: %s\n", + snd_strerror(err)); + return err; + } + */ + + //snd_pcm_sw_params_set_avail_min(Handle,swparams,Frames); + + + if ((err = snd_pcm_sw_params(audio, swparams)) < 0) { + printf("Unable to set sw params for audio: %s\n", snd_strerror(err)); + return err; + } + +#if 0 + if ((err = snd_pcm_prepare(audio)) < 0) { + errprintf("AUDIO: Cannot prepare audio: %s\n", snd_strerror(err)); + return -1; + } +#endif return 0; } -/* - * opens the audio device(s). if in_audio_device_name and out_audio_device_name - * are equal, full duplex mode is assumed - * - * input: in_audio_device_name, out_audio_device_name: strings - * channels: requestes number of channels (1 / 2) - * format_priorities: list of sorted integers with valid sound formats - * (e.g. AFMT_U8). the first working one will be used - * speed_in, speed_out: requested speeds (equal for full duplex) - * - * returns: 0 if successful - * -1 if not successful - * - * output: - * audio_fd_in, audio_fd_out: file descriptors - * fragment_size_in, fragment_size_out: device buffer fragment sizes - * format_in, format_out: actually used formats - * speed_in, speed_out: actually used speeds - */ +/*--------------------------------------------------------------------------*/ + int open_audio_devices(char *in_audio_device_name, char *out_audio_device_name, int channels, int *format_priorities, - int *audio_fd_in, int *audio_fd_out, + snd_pcm_t **audio_in, snd_pcm_t **audio_out, int *fragment_size_in, int *fragment_size_out, int *format_in, int *format_out, - int *speed_in, int *speed_out) { - int audio_fd; + unsigned int *speed_in, unsigned int *speed_out) +{ + int err; int initresult; - int capabilities; int *priority; /* try to open the sound device */ - if (strcmp(in_audio_device_name, out_audio_device_name) == 0) { - /* same device for input and output: full duplex */ - - for (initresult = -1, priority = format_priorities; - priority && initresult; priority++) { /* try different formats */ - if ((audio_fd = open(in_audio_device_name, O_RDWR | O_NONBLOCK, 0)) - == -1) { - perror(in_audio_device_name); - return(-1); - } - /* device is in non-blocking mode now */ - - *audio_fd_in = audio_fd; - *audio_fd_out = audio_fd; - - /* set device to full duplex mode */ - if (ioctl(audio_fd, SNDCTL_DSP_SETDUPLEX, 0)) { - perror("SNDCTL_DSP_SETDUPLEX"); - return(-1); - } - if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &capabilities)) { - perror("SNDCTL_DSP_GETCAPS"); - return(-1); - } - if (!(capabilities & DSP_CAP_DUPLEX)) { - fprintf(stderr, "Error: device doesn't support full duplex mode\n"); - return(-1); - } - - /* *speed_in assumed to be equal to *speed_out */ - *format_in = *priority; /* get new format to try */ - initresult = init_audio_device(audio_fd, *format_in, channels, speed_in, - fragment_size_in); - if (initresult) /* let's retry (and re-open) */ - close_audio_devices(*audio_fd_in, *audio_fd_out); - } - *format_out = *format_in; - *speed_out = *speed_in; - *fragment_size_out = *fragment_size_in; - return initresult; - } else { - /* different devices for input and output */ - - for (initresult = -1, priority = format_priorities; - initresult && priority; priority++) { - if ((audio_fd = open(in_audio_device_name, O_RDONLY | O_NONBLOCK, 0)) == - -1) { - perror(in_audio_device_name); - return(-1); - } - /* device is in non-blocking mode now */ - - *audio_fd_in = audio_fd; - *format_in = *priority; - initresult = init_audio_device(audio_fd, *format_in, channels, speed_in, - fragment_size_in); - if (initresult) /* let's retry (and re-open) */ - if (close(audio_fd)) { - perror("close audio in device"); - return -1; - } - } - - if (initresult) return initresult; /* no chance */ - - for (initresult = -1, priority = format_priorities; - initresult && priority; priority++) { - if ((audio_fd = open(out_audio_device_name, O_WRONLY | O_NONBLOCK, 0)) - == -1) { - perror(out_audio_device_name); - if (close(*audio_fd_in)) { /* error -> close audio_in device */ - perror("close audio in device"); - } - return(-1); - } - /* device is in non-blocking mode now */ - - *audio_fd_out = audio_fd; - *format_out = *priority; - - initresult = init_audio_device(audio_fd, *format_out, channels, - speed_out, fragment_size_out); - if (initresult) /* let's retry (and re-open) */ - if (close(audio_fd)) { - perror("close audio out device"); - return -1; - } - } - return initresult; - } -} - -/* - * stops audio playback and recording on specified file descriptors - */ -int audio_stop(int audio_fd_in, int audio_fd_out) { - /* Instead of ioctl SNDCTL_DSP_RESET, OSS Programmer's Guide recommends - * closing and reopening the devices (-> close/open_audio_devices). - * Here, we flush the input buffer and use SNDCTL_DSP_RESET - * to clean up for further recording, but on restart, old input comes again. - */ - int result; - int frag_size; - char *buf; /* temporary allocated buffer */ - - struct timeval tv; - fd_set fds; - int num; /* number of file descriptors with data (0/1) */ - - /* flush input buffer */ - if (ioctl(audio_fd_in, SNDCTL_DSP_GETBLKSIZE, &frag_size)) - fprintf(stderr, "Error obtaining audio input fragment size " - "with SNDCTL_DSP_GETBLKSIZE in audio_stop().\n"); - else { - buf = (char *) malloc(frag_size); - - tv.tv_sec = 0; /* return immediately */ - tv.tv_usec = 0; - - do { - FD_ZERO(&fds); - FD_SET(audio_fd_in, &fds); - - num = select(FD_SETSIZE, &fds, 0, 0, &tv); /* return immediately */ - if (num > 0) - read(audio_fd_in, buf, frag_size); - } while (num > 0); - if (num == -1) - perror("select at audio_stop"); - free(buf); - } - - /* stop device */ - if (audio_fd_in == audio_fd_out) - result = ioctl(audio_fd_in, SNDCTL_DSP_RESET, 0); - else - result = ioctl(audio_fd_in, SNDCTL_DSP_RESET, 0) | - ioctl(audio_fd_out, SNDCTL_DSP_RESET, 0); - - return result; -} - -/* - * Closes specified input/output file descriptors - * returns 0 on success, -1 on error - */ -int close_audio_devices(int audio_fd_in, int audio_fd_out) { - audio_stop(audio_fd_in, audio_fd_out); - - if (close(audio_fd_in) == -1) { - perror("close audio device"); - return -1; - } - if (audio_fd_in != audio_fd_out) - if (close(audio_fd_out) == -1) { - perror("close audio device"); + if ((err = snd_pcm_open(audio_in, in_audio_device_name, SND_PCM_STREAM_CAPTURE, 0/*SND_PCM_NONBLOCK*/)) < 0) { + errprintf("AUDIO: Audio recording device '%s' open error: %s, trying default\n", + in_audio_device_name, snd_strerror(err)); + if ((err = snd_pcm_open(audio_in, "default", SND_PCM_STREAM_CAPTURE, 0/*SND_PCM_NONBLOCK*/)) < 0) { + errprintf("AUDIO: Audio recording device 'default' open error: %s\n", + snd_strerror(err)); return -1; } - return 0; + } + if ((err = snd_pcm_open(audio_out, out_audio_device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + errprintf("AUDIO: Audio playback device '%s' open error: %s, trying default\n", + out_audio_device_name, snd_strerror(err)); + if ((err = snd_pcm_open(audio_out, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + errprintf("AUDIO: Audio playback device 'default' open error: %s\n", + snd_strerror(err)); + return -1; + } + } + + /* set format and sampling rate */ + for (initresult = -1, priority = format_priorities; + initresult && priority; priority++) { + + *format_in = *priority; + initresult = init_audio_device(*audio_in, *format_in, channels, speed_in, fragment_size_in); + } + + if (initresult) + return initresult; /* no chance */ + + for (initresult = -1, priority = format_priorities; + initresult && priority; priority++) { + + *format_out = *priority; + initresult = init_audio_device(*audio_out, *format_out, channels, speed_out, fragment_size_out); + } + +#if 0 + /* not implemented in ALSA */ + if ((err = snd_pcm_link(*audio_out, *audio_in)) < 0) { + errprintf("AUDIO: Error linking in/out devices: %s\n", + snd_strerror(err)); + return -1; + } +#endif + + if (debug > 2) { + /* TODO: redirect output to dbgprintf */ + snd_output_t *output; + snd_output_stdio_attach(&output, stderr, 0); + dbgprintf(2, "AUDIO: Dump of IN PCM:"); + snd_pcm_dump(*audio_in, output); + dbgprintf(2, "AUDIO: Dump of OUT PCM:"); + snd_pcm_dump(*audio_out, output); + } + + return initresult; } -/* - * returns number of bytes per sample for the specified - * OSS format (e.g. AFMT_U8) - * - * returns >= 1 on success, 0 otherwise (when format not supported) - */ -int sample_size_from_format(int format) { +int audio_stop(snd_pcm_t *audio_in, snd_pcm_t *audio_out) { + + int err, err0; + + err = 0; + if ((err0 = snd_pcm_drop(audio_in)) < 0) { + errprintf("AUDIO: Unable to reset audio capture: %s\n", snd_strerror(err0)); + err = -1; + } + if ((err0 = snd_pcm_drop(audio_out)) < 0) { + errprintf("AUDIO: Unable to reset audio playback: %s\n", snd_strerror(err0)); + err = -1; + } + return err; +} + +/*--------------------------------------------------------------------------*/ + +int close_audio_devices(snd_pcm_t *audio_in, snd_pcm_t *audio_out) +{ + + int err, err0; + + audio_stop(audio_in, audio_out); + + err = 0; + if ((err0 = snd_pcm_close(audio_in)) < 0) { + errprintf("AUDIO: Unable to close audio capture: %s\n", snd_strerror(err0)); + err = -1; + } + if ((err0 = snd_pcm_close(audio_out)) < 0) { + errprintf("AUDIO: Unable to close audio playback: %s\n", snd_strerror(err0)); + err = -1; + } + return err; +} + +/*--------------------------------------------------------------------------*/ + +int sample_size_from_format(int format) +{ switch(format) { - case AFMT_U8: - case AFMT_S8: - case AFMT_MU_LAW: - return 1; - - case AFMT_S16_LE: - case AFMT_S16_BE: - case AFMT_U16_LE: - case AFMT_U16_BE: + case SND_PCM_FORMAT_S16_LE: + case SND_PCM_FORMAT_S16_BE: + case SND_PCM_FORMAT_U16_LE: + case SND_PCM_FORMAT_U16_BE: return 2; - + + case SND_PCM_FORMAT_U8: + case SND_PCM_FORMAT_S8: + case SND_PCM_FORMAT_MU_LAW: + case SND_PCM_FORMAT_A_LAW: + return 1; + default: return 0; } } -/* - * returns the number of available fragents that can be read immediately - * from specified sound device file descriptor fd - */ -int audio_get_read_fragments_number(int fd) { - audio_buf_info info; - - if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) { - perror("SNDCTL_DSP_GETISPACE"); - } - return info.fragments; -} - -/* - * returns the total number of fragents available for OSS at - * specified sound device file descriptor fd for reading - */ -int audio_get_read_fragments_total(int fd) { - audio_buf_info info; - - if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) == -1) { - perror("SNDCTL_DSP_GETISPACE"); - } - return info.fragstotal; -} - -/* - * returns the number of fragents that can be written (non-blocking) to - * specified sound device file descriptor fd - */ -int audio_get_write_fragments_number(int fd) { - audio_buf_info info; - - if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) == -1) { - perror("SNDCTL_DSP_GETOSPACE"); - } - return info.fragments; -} - -/* - * returns the total number of fragents available for OSS at - * specified sound device file descriptor fd for writing - */ -int audio_get_write_fragments_total(int fd) { - audio_buf_info info; - - if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) == -1) { - perror("SNDCTL_DSP_GETOSPACE"); - } - return info.fragstotal; -} +/*--------------------------------------------------------------------------*/ diff --git a/src/sound.h b/src/sound.h index eadafe0..a19cc5a 100644 --- a/src/sound.h +++ b/src/sound.h @@ -21,27 +21,60 @@ * */ -#include +#include -#define DEFAULT_FRAGMENT_SIZE 1024 -#define DEFAULT_AUDIO_DEVICE_NAME_IN "/dev/dsp" -#define DEFAULT_AUDIO_DEVICE_NAME_OUT "/dev/dsp" +#define DEFAULT_FRAGMENT_SIZE 128 +#define DEFAULT_AUDIO_DEVICE_NAME_IN "default" +#define DEFAULT_AUDIO_DEVICE_NAME_OUT "default" extern int default_audio_priorities[]; -int init_audio_device(int audio_fd, int format, int channels, int *speed, - int *fragment_size); +/*! + * @brief Opens the audio device(s). + * + * @param in_audio_device_name name of input device. + * @param out_audio_device_name name of output device. + * @param channels requestes number of channels (1/2). + * @param format_priorities list of sorted integers with valid sound formats + * (e.g. SND_PCM_FORMAT_U8). the first working one will be used. + * @param audio_in filled with input PCM handle. + * @param audio_out filled with output PCM handle. + * @param fragment_size_in in/out fragment size for input. + * @param fragment_size_out in/out fragment size for output. + * @param speed_in in/out requested/actual input speed. + * @param speed_out in/out requested/actual output speed. + * @return 0 if successful, -1 on error. +*/ int open_audio_devices(char *in_audio_device_name, char *out_audio_device_name, int channels, int *format_priorities, - int *audio_fd_in, int *audio_fd_out, + snd_pcm_t **audio_in, snd_pcm_t **audio_out, int *fragment_size_in, int *fragment_size_out, int *format_in, int *format_out, - int *speed_in, int *speed_out); -int close_audio_devices(int audio_fd_in, int audio_fd_out); -int audio_stop(int audio_fd_in, int audio_fd_out); + unsigned int *speed_in, unsigned int *speed_out); + +/*! + * @brief Close audio devices.. + * + * @param audio_in input device to close. + * @param audio_out output device to close. + * @return 0 if successful, -1 on error. + */ +int close_audio_devices(snd_pcm_t *audio_in, snd_pcm_t *audio_out); + +/*! + * @brief Stops audio playback and recording on specified devices. + * + * @param audio_in input device to stop. + * @param audio_out output device to stop. + * @return 0 if successful, -1 on error. + */ +int audio_stop(snd_pcm_t *audio_in, snd_pcm_t *audio_out); + +/*! + * @brief Get number of bytes per sample for the specified format. + * + * @param format ALSA format (e.g. SND_PCM_FORMAT_U8). + * @return >= 1 on success, 0 otherwise (when format not supported). + */ int sample_size_from_format(int format); -int audio_get_read_fragments_number(int fd); -int audio_get_read_fragments_total(int fd); -int audio_get_write_fragments_number(int fd); -int audio_get_write_fragments_total(int fd); diff --git a/src/util.c b/src/util.c index 8e0785f..6e0643a 100644 --- a/src/util.c +++ b/src/util.c @@ -171,10 +171,10 @@ void execute(char *command) { char *argv[] = {"sh", "-c", command, NULL}; if (result == -1) { /* error */ - fprintf(stderr, "Fork error.\n"); + errprintf("Fork error.\n"); } else if (result == 0) { /* we are the child */ if (execvp("sh", argv)) { - fprintf(stderr, "Exec error.\n"); + errprintf("Exec error.\n"); exit(1); } } @@ -225,18 +225,16 @@ int touch_dotdir(void) { char *filename; if (!(homedir = get_homedir())) { - fprintf(stderr, "Warning: Couldn't get home dir.\n"); + errprintf("Warning: Couldn't get home dir.\n"); return -1; } if (asprintf(&filename, "%s/." PACKAGE, homedir) < 0) { - fprintf(stderr, - "Warning: Couldn't allocate memory for configuration directory.\n"); + errprintf("Warning: Couldn't allocate memory for configuration directory.\n"); return -1; } if (touch_dir(filename) < 0) { - if (debug) - fprintf(stderr, "Warning: Can't reach configuration directory.\n"); + dbgprintf(1, "Warning: Can't reach configuration directory.\n"); return -1; } free(filename); @@ -357,9 +355,20 @@ int output_codeset_set(char* codeset) { if (!codeset) codeset = output_codeset; if (!bind_textdomain_codeset(PACKAGE, codeset)) { - fprintf(stderr, "Error setting gettext output codeset to %s.\n", codeset); + errprintf("Error setting gettext output codeset to %s.\n", codeset); return -1; } return 0; } +/*--------------------------------------------------------------------------*/ + +uint64_t microsec_time() +{ + /* TODO: optimize this using HR timer, since gettimeofday is expensive */ + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec * ((uint64_t) 1000000) + tv.tv_usec; +} + +/*--------------------------------------------------------------------------*/ diff --git a/src/util.h b/src/util.h index c81cb66..4dabf37 100644 --- a/src/util.h +++ b/src/util.h @@ -32,6 +32,8 @@ #endif #include +#include + #define SHORT_INTERVAL 10000 /* 10 milliseconds */ @@ -53,4 +55,11 @@ char* filename_extension(char* fn); int output_codeset_save(void); int output_codeset_set(char* codeset); +/*! + * @brief Get time in microsecond. + * + * @return time in microseconds since arbitrary start point. + */ +uint64_t microsec_time(); + #endif /* util.h */