#define SWITCH_PA_CALL_ID_VARIABLE "pa_call_id"
#define MIN_STREAM_SAMPLE_RATE 8000
+#define STREAM_SAMPLES_PER_PACKET(stream) ((stream->codec_ms * stream->sample_rate) / 1000)
SWITCH_MODULE_LOAD_FUNCTION(mod_portaudio_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_portaudio_shutdown);
TFLAG_AUTO_ANSWER = (1 << 10)
} TFLAGS;
-struct private_object {
- unsigned int flags;
- switch_core_session_t *session;
- switch_caller_profile_t *caller_profile;
- char call_id[50];
- int sample_rate;
- int codec_ms;
- switch_mutex_t *flag_mutex;
- char *hold_file;
- switch_file_handle_t fh;
- switch_file_handle_t *hfh;
- switch_frame_t hold_frame;
- unsigned char holdbuf[SWITCH_RECOMMENDED_BUFFER_SIZE];
- struct private_object *next;
-};
-
-typedef struct private_object private_t;
-
struct audio_stream {
int indev;
int outdev;
int codec_ms;
/*! The PA input device */
int indev;
+ /*! Input channels being used */
+ uint8_t inchan_used[MAX_IO_CHANNELS];
/*! The PA output device */
int outdev;
+ /*! Output channels being used */
+ uint8_t outchan_used[MAX_IO_CHANNELS];
/*! How many channels to create (for both indev and outdev) */
int channels;
/*! The io stream helper to buffer audio */
PABLIO_Stream *stream;
- /*! How often to write */
- switch_timer_t write_timer;
+ /* It can be shared after all :-) */
+ switch_mutex_t *mutex;
} shared_audio_stream_t;
+typedef struct private_object private_t;
/* Endpoint that can be called via portaudio/endpoint/<endpoint-name> */
typedef struct _audio_endpoint {
/*! Friendly name for this endpoint */
/*! Channel index within the output stream where we get the audio for this endpoint */
int outchan;
+
+ /*! Associated private information if involved in a call */
+ private_t *master;
+
+ /*! For timed writes */
+ switch_timer_t write_timer;
+
+ /*! Let's be safe */
+ switch_mutex_t *mutex;
} audio_endpoint_t;
+struct private_object {
+ unsigned int flags;
+ switch_core_session_t *session;
+ switch_caller_profile_t *caller_profile;
+ char call_id[50];
+ int sample_rate;
+ int codec_ms;
+ switch_mutex_t *flag_mutex;
+ char *hold_file;
+ switch_file_handle_t fh;
+ switch_file_handle_t *hfh;
+ switch_frame_t hold_frame;
+ unsigned char holdbuf[SWITCH_RECOMMENDED_BUFFER_SIZE];
+ audio_endpoint_t *audio_endpoint;
+ struct private_object *next;
+};
+
+
static struct {
int debug;
int port;
}
return NULL;
}
+
static void destroy_audio_streams()
{
int close_wait = 4;
}
globals.destroying_streams = 0;
}
+
static switch_status_t validate_main_audio_stream()
{
if (globals.read_timer.timer_interface) {
/*.receive_message */ channel_receive_message
};
+static int create_shared_audio_stream(shared_audio_stream_t *stream);
+static int destroy_shared_audio_stream(shared_audio_stream_t *stream);
+static int take_stream_channel(shared_audio_stream_t *stream, int index, int input)
+{
+ int rc = 0;
+ if (!stream) {
+ return rc;
+ }
+
+ switch_mutex_lock(stream->mutex);
+
+ if (!stream->stream && create_shared_audio_stream(stream)) {
+ rc = -1;
+ goto done;
+ }
+
+ if (input) {
+ if (stream->inchan_used[index]) {
+ rc = -1;
+ goto done;
+ }
+ stream->inchan_used[index] = 1;
+ } else {
+ if (!input && stream->outchan_used[index]) {
+ rc = -1;
+ goto done;
+ }
+ stream->outchan_used[index] = 1;
+ }
+
+done:
+ switch_mutex_unlock(stream->mutex);
+ return rc;
+}
+
+static int release_stream_channel(shared_audio_stream_t *stream, int index, int input)
+{
+ int i = 0;
+ int destroy_stream = 1;
+ int rc = 0;
+
+ if (!stream) {
+ return rc;
+ }
+
+ switch_mutex_lock(stream->mutex);
+
+ if (input) {
+ if (stream->inchan_used[index]) {
+ rc = -1;
+ goto done;
+ }
+ stream->inchan_used[index] = 1;
+ } else {
+ if (!input && stream->outchan_used[index]) {
+ rc = -1;
+ goto done;
+ }
+ stream->outchan_used[index] = 1;
+ }
+
+ for (i = 0; i < stream->channels; i++) {
+ if (stream->inchan_used[i] || stream->outchan_used[i]) {
+ destroy_stream = 0;
+ }
+ }
+ if (destroy_stream) {
+ destroy_shared_audio_stream(stream);
+ }
+done:
+ switch_mutex_unlock(stream->mutex);
+ return rc;
+}
+
/* Make sure when you have 2 sessions in the same scope that you pass the appropriate one to the routines
that allocate memory or you will have 1 channel with memory allocated from another channel's pool!
*/
switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags,
switch_call_cause_t *cancel_cause)
{
+ char name[128];
+ const char *id = NULL;
+ private_t *tech_pvt = NULL;
+ switch_channel_t *channel = NULL;
+ switch_caller_profile_t *caller_profile = NULL;
+ switch_call_cause_t retcause = SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;
- if ((*new_session = switch_core_session_request(portaudio_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND, flags, pool)) != 0) {
- private_t *tech_pvt;
- switch_channel_t *channel;
- switch_caller_profile_t *caller_profile;
-
- switch_core_session_add_stream(*new_session, NULL);
- if ((tech_pvt = (private_t *) switch_core_session_alloc(*new_session, sizeof(private_t))) != 0) {
- memset(tech_pvt, 0, sizeof(*tech_pvt));
- switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(*new_session));
- channel = switch_core_session_get_channel(*new_session);
- switch_core_session_set_private(*new_session, tech_pvt);
- tech_pvt->session = *new_session;
- } else {
- switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "Hey where is my memory pool?\n");
- switch_core_session_destroy(new_session);
- return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;
- }
-
- if (outbound_profile) {
- char name[128];
- const char *id = !zstr(outbound_profile->caller_id_number) ? outbound_profile->caller_id_number : "na";
- switch_snprintf(name, sizeof(name), "portaudio/%s", id);
+ if (!outbound_profile) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing caller profile\n");
+ return retcause;
+ }
- switch_channel_set_name(channel, name);
+ if (!(*new_session = switch_core_session_request(portaudio_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND, flags, pool))) {
+ return retcause;
+ }
- caller_profile = switch_caller_profile_clone(*new_session, outbound_profile);
- switch_channel_set_caller_profile(channel, caller_profile);
- tech_pvt->caller_profile = caller_profile;
- if (outbound_profile->destination_number && !strcasecmp(outbound_profile->destination_number, "auto_answer")) {
- switch_set_flag(tech_pvt, TFLAG_AUTO_ANSWER);
- }
+ switch_core_session_add_stream(*new_session, NULL);
+ if ((tech_pvt = (private_t *) switch_core_session_alloc(*new_session, sizeof(private_t))) != 0) {
+ memset(tech_pvt, 0, sizeof(*tech_pvt));
+ switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(*new_session));
+ channel = switch_core_session_get_channel(*new_session);
+ switch_core_session_set_private(*new_session, tech_pvt);
+ tech_pvt->session = *new_session;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "Hey where is my memory pool?\n");
+ switch_core_session_destroy(new_session);
+ return retcause;
+ }
- } else {
- switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_ERROR, "Doh! no caller profile\n");
- switch_core_session_destroy(new_session);
- return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;
+ if (outbound_profile->destination_number && !strncasecmp(outbound_profile->destination_number, "endpoint", sizeof("endpoint"))) {
+ audio_endpoint_t *endpoint = NULL;
+ char *endpoint_name = switch_core_strdup(outbound_profile->pool, outbound_profile->destination_number);
+ endpoint_name = strchr(endpoint_name, '/');
+ if (!endpoint_name) {
+ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "No portaudio endpoint specified\n");
+ goto error;
+ }
+ endpoint = switch_core_hash_find(globals.endpoints, endpoint_name);
+ if (!endpoint) {
+ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(*new_session), SWITCH_LOG_CRIT, "Invalid portaudio endpoint %s\n", endpoint_name);
+ goto error;
+ }
+ /* check that there is no call there yet */
+ switch_mutex_lock(endpoint->mutex);
+ if (endpoint->master) {
+ switch_mutex_unlock(endpoint->mutex);
+ retcause = SWITCH_CAUSE_USER_BUSY;
+ goto error;
+ }
+ /* try to acquire the stream */
+ if (take_stream_channel(endpoint->in_stream, endpoint->inchan, 1)) {
+ switch_mutex_unlock(endpoint->mutex);
+ retcause = SWITCH_CAUSE_USER_BUSY;
+ goto error;
+ }
+ if (take_stream_channel(endpoint->out_stream, endpoint->outchan, 0)) {
+ release_stream_channel(endpoint->in_stream, endpoint->inchan, 1);
+ switch_mutex_unlock(endpoint->mutex);
+ retcause = SWITCH_CAUSE_USER_BUSY;
+ goto error;
+ }
+ switch_snprintf(name, sizeof(name), "portaudio/endpoint-%s", endpoint_name);
+ switch_set_flag(tech_pvt, TFLAG_AUTO_ANSWER);
+ endpoint->master = tech_pvt;
+ tech_pvt->audio_endpoint = endpoint;
+ switch_mutex_unlock(endpoint->mutex);
+ } else {
+ id = !zstr(outbound_profile->caller_id_number) ? outbound_profile->caller_id_number : "na";
+ switch_snprintf(name, sizeof(name), "portaudio/%s", id);
+ if (outbound_profile->destination_number && !strcasecmp(outbound_profile->destination_number, "auto_answer")) {
+ switch_set_flag(tech_pvt, TFLAG_AUTO_ANSWER);
}
-
- switch_set_flag_locked(tech_pvt, TFLAG_OUTBOUND);
- switch_channel_set_state(channel, CS_INIT);
- return SWITCH_CAUSE_SUCCESS;
}
+ switch_channel_set_name(channel, name);
+ caller_profile = switch_caller_profile_clone(*new_session, outbound_profile);
+ switch_channel_set_caller_profile(channel, caller_profile);
+ tech_pvt->caller_profile = caller_profile;
- return SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER;
+ switch_set_flag_locked(tech_pvt, TFLAG_OUTBOUND);
+ switch_channel_set_state(channel, CS_INIT);
+ return SWITCH_CAUSE_SUCCESS;
+
+error:
+ if (new_session && *new_session) {
+ switch_core_session_destroy(new_session);
+ }
+ return retcause;
}
if (!stream) {
continue;
}
+ switch_mutex_init(&stream->mutex, SWITCH_MUTEX_NESTED, module_pool);
stream->indev = -1;
stream->outdev = -1;
stream->sample_rate = globals.sample_rate;
if (cnum < 0 || cnum > stream->channels) {
return NULL;
}
+
+ if (check_input && stream->indev < 0) {
+ return NULL;
+ }
+
+ if (!check_input && stream->outdev < 0) {
+ return NULL;
+ }
*chanindex = cnum;
if (!endpoint) {
continue;
}
+ switch_mutex_init(&endpoint->mutex, SWITCH_MUTEX_NESTED, module_pool);
endpoint->inchan = -1;
endpoint->outchan = -1;
switch_snprintf(endpoint->name, sizeof(endpoint->name), "%s", endpoint_name);
return SWITCH_STATUS_SUCCESS;
}
+
PaError open_audio_stream(PABLIO_Stream **stream, const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters)
{
if (inputParameters->device != -1) {
return OpenAudioStream(stream, NULL, outputParameters, globals.sample_rate, paClipOff, globals.read_codec.implementation->samples_per_packet, 0);
}
+PaError open_shared_audio_stream(shared_audio_stream_t *shstream, const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters)
+{
+ PaError err;
+ if (inputParameters->device != -1) {
+ err = OpenAudioStream(&shstream->stream, inputParameters, outputParameters, shstream->sample_rate,
+ paClipOff, STREAM_SAMPLES_PER_PACKET(shstream), globals.dual_streams);
+ } else {
+ err = OpenAudioStream(&shstream->stream, NULL, outputParameters, shstream->sample_rate,
+ paClipOff, STREAM_SAMPLES_PER_PACKET(shstream), 0);
+ }
+ if (err != paNoError) {
+ shstream->stream = NULL;
+ }
+ return err;
+}
+
+static int create_shared_audio_stream(shared_audio_stream_t *shstream)
+{
+ PaStreamParameters inputParameters, outputParameters;
+ PaError err;
+ switch_event_t *event;
+
+ inputParameters.device = shstream->indev;
+ if (shstream->indev != -1) {
+ inputParameters.channelCount = shstream->channels;
+ inputParameters.sampleFormat = SAMPLE_TYPE;
+ inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+ }
+ outputParameters.device = shstream->outdev;
+ outputParameters.channelCount = shstream->channels;
+ outputParameters.sampleFormat = SAMPLE_TYPE;
+ outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
+ outputParameters.hostApiSpecificStreamInfo = NULL;
+
+ err = open_shared_audio_stream(shstream, &inputParameters, &outputParameters);
+ if (err != paNoError) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening audio device retrying\n");
+ switch_yield(1000000);
+ err = open_shared_audio_stream(shstream, &inputParameters, &outputParameters);
+ }
+
+ if (err != paNoError) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open audio device\n");
+ if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, MY_EVENT_ERROR_AUDIO_DEV) == SWITCH_STATUS_SUCCESS) {
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Reason", Pa_GetErrorText(err));
+ switch_event_fire(&event);
+ }
+ return -1;
+ }
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Created audio stream: %d channels %d\n",
+ shstream->sample_rate, shstream->channels);
+ return 0;
+}
+
+static int destroy_shared_audio_stream(shared_audio_stream_t *shstream)
+{
+ CloseAudioStream(shstream->stream);
+ shstream->stream = NULL;
+ return 0;
+}
+
static audio_stream_t *create_audio_stream(int indev, int outdev)
{
PaStreamParameters inputParameters, outputParameters;