/* $Id$ */ /* * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2007-2009 Keystream AB and Konftel AB, All rights reserved. * Author: * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #if defined(PJMEDIA_AUDIO_DEV_HAS_ALSA) && PJMEDIA_AUDIO_DEV_HAS_ALSA #include #include #include #include #include #include #include #include #define THIS_FILE "alsa_dev.c" #define ALSA_DEVICE_NAME "plughw:%d,%d" #define ALSASOUND_PLAYBACK 1 #define ALSASOUND_CAPTURE 2 #define MAX_SOUND_CARDS 5 #define MAX_SOUND_DEVICES_PER_CARD 5 #define MAX_DEVICES 16 /* Set to 1 to enable tracing */ #if 0 # define TRACE_(expr) PJ_LOG(5,expr) #else # define TRACE_(expr) #endif /* * Factory prototypes */ static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f); static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f); static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f); static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f); static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info); static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param); static pj_status_t alsa_factory_create_stream(pjmedia_aud_dev_factory *f, const pjmedia_aud_param *param, pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_strm); /* * Stream prototypes */ static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, void *value); static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, const void *value); static pj_status_t alsa_stream_start(pjmedia_aud_stream *strm); static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm); static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); struct alsa_factory { pjmedia_aud_dev_factory base; pj_pool_factory *pf; pj_pool_t *pool; pj_pool_t *base_pool; unsigned dev_cnt; pjmedia_aud_dev_info devs[MAX_DEVICES]; }; struct alsa_stream { pjmedia_aud_stream base; /* Common */ pj_pool_t *pool; struct alsa_factory *af; void *user_data; pjmedia_aud_param param; /* Running parameter */ int rec_id; /* Capture device id */ int quit; /* Playback */ snd_pcm_t *pb_pcm; snd_pcm_uframes_t pb_frames; /* samples_per_frame */ pjmedia_aud_play_cb pb_cb; unsigned pb_buf_size; char *pb_buf; pj_thread_t *pb_thread; /* Capture */ snd_pcm_t *ca_pcm; snd_pcm_uframes_t ca_frames; /* samples_per_frame */ pjmedia_aud_rec_cb ca_cb; unsigned ca_buf_size; char *ca_buf; pj_thread_t *ca_thread; }; static pjmedia_aud_dev_factory_op alsa_factory_op = { &alsa_factory_init, &alsa_factory_destroy, &alsa_factory_get_dev_count, &alsa_factory_get_dev_info, &alsa_factory_default_param, &alsa_factory_create_stream, &alsa_factory_refresh }; static pjmedia_aud_stream_op alsa_stream_op = { &alsa_stream_get_param, &alsa_stream_get_cap, &alsa_stream_set_cap, &alsa_stream_start, &alsa_stream_stop, &alsa_stream_destroy }; static void null_alsa_error_handler (const char *file, int line, const char *function, int err, const char *fmt, ...) { PJ_UNUSED_ARG(file); PJ_UNUSED_ARG(line); PJ_UNUSED_ARG(function); PJ_UNUSED_ARG(err); PJ_UNUSED_ARG(fmt); } static void alsa_error_handler (const char *file, int line, const char *function, int err, const char *fmt, ...) { char err_msg[128]; int index, len; va_list arg; #ifndef NDEBUG index = snprintf (err_msg, sizeof(err_msg), "ALSA lib %s:%i:(%s) ", file, line, function); #else index = snprintf (err_msg, sizeof(err_msg), "ALSA lib: "); #endif if (index < 1 || index >= (int)sizeof(err_msg)) { index = sizeof(err_msg)-1; err_msg[index] = '\0'; goto print_msg; } va_start (arg, fmt); if (index < sizeof(err_msg)-1) { len = vsnprintf( err_msg+index, sizeof(err_msg)-index, fmt, arg); if (len < 1 || len >= (int)sizeof(err_msg)-index) len = sizeof(err_msg)-index-1; index += len; err_msg[index] = '\0'; } va_end(arg); if (err && index < sizeof(err_msg)-1) { len = snprintf( err_msg+index, sizeof(err_msg)-index, ": %s", snd_strerror(err)); if (len < 1 || len >= (int)sizeof(err_msg)-index) len = sizeof(err_msg)-index-1; index += len; err_msg[index] = '\0'; } print_msg: PJ_LOG (4,(THIS_FILE, "%s", err_msg)); } static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) { pjmedia_aud_dev_info *adi; snd_pcm_t* pcm; int pb_result, ca_result; if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs)) return PJ_ETOOMANY; adi = &af->devs[af->dev_cnt]; TRACE_((THIS_FILE, "add_dev (%s): Enter", dev_name)); /* Try to open the device in playback mode */ pb_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_PLAYBACK, 0); if (pb_result >= 0) { TRACE_((THIS_FILE, "Try to open the device for playback - success")); snd_pcm_close (pcm); } else { TRACE_((THIS_FILE, "Try to open the device for playback - failure")); } /* Try to open the device in capture mode */ ca_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_CAPTURE, 0); if (ca_result >= 0) { TRACE_((THIS_FILE, "Try to open the device for capture - success")); snd_pcm_close (pcm); } else { TRACE_((THIS_FILE, "Try to open the device for capture - failure")); } /* Check if the device could be opened in playback or capture mode */ if (pb_result<0 && ca_result<0) { TRACE_((THIS_FILE, "Unable to open sound device %s", dev_name)); return PJMEDIA_EAUD_NODEV; } /* Reset device info */ pj_bzero(adi, sizeof(*adi)); /* Set device name */ strncpy(adi->name, dev_name, sizeof(adi->name)); /* Check the number of playback channels */ adi->output_count = (pb_result>=0) ? 1 : 0; /* Check the number of capture channels */ adi->input_count = (ca_result>=0) ? 1 : 0; /* Set the default sample rate */ adi->default_samples_per_sec = 8000; /* Driver name */ strcpy(adi->driver, "ALSA"); ++af->dev_cnt; PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); return PJ_SUCCESS; } /* Create ALSA audio driver. */ pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf) { struct alsa_factory *af; pj_pool_t *pool; pool = pj_pool_create(pf, "alsa_aud_base", 256, 256, NULL); af = PJ_POOL_ZALLOC_T(pool, struct alsa_factory); af->pf = pf; af->base_pool = pool; af->base.op = &alsa_factory_op; return &af->base; } /* API: init factory */ static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f) { pj_status_t status = alsa_factory_refresh(f); if (PJ_SUCCESS != status) return status; PJ_LOG(4,(THIS_FILE, "ALSA initialized")); return PJ_SUCCESS; } /* API: destroy factory */ static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f) { struct alsa_factory *af = (struct alsa_factory*)f; if (af->pool) pj_pool_release(af->pool); if (af->base_pool) { pj_pool_t *pool = af->base_pool; af->base_pool = NULL; pj_pool_release(pool); } /* Restore handler */ snd_lib_error_set_handler(NULL); return PJ_SUCCESS; } /* API: refresh the device list */ static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f) { struct alsa_factory *af = (struct alsa_factory*)f; char **hints, **n; int err; TRACE_((THIS_FILE, "pjmedia_snd_init: Enumerate sound devices")); if (af->pool != NULL) { pj_pool_release(af->pool); af->pool = NULL; } af->pool = pj_pool_create(af->pf, "alsa_aud", 256, 256, NULL); af->dev_cnt = 0; /* Enumerate sound devices */ err = snd_device_name_hint(-1, "pcm", (void***)&hints); if (err != 0) return PJMEDIA_EAUD_SYSERR; /* Set a null error handler prior to enumeration to suppress errors */ snd_lib_error_set_handler(null_alsa_error_handler); n = hints; while (*n != NULL) { char *name = snd_device_name_get_hint(*n, "NAME"); if (name != NULL && 0 != strcmp("null", name)) { add_dev(af, name); free(name); } n++; } /* Install error handler after enumeration, otherwise we'll get many * error messages about invalid card/device ID. */ snd_lib_error_set_handler(alsa_error_handler); err = snd_device_name_free_hint((void**)hints); PJ_LOG(4,(THIS_FILE, "ALSA driver found %d devices", af->dev_cnt)); return PJ_SUCCESS; } /* API: get device count */ static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f) { struct alsa_factory *af = (struct alsa_factory*)f; return af->dev_cnt; } /* API: get device info */ static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info) { struct alsa_factory *af = (struct alsa_factory*)f; PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); pj_memcpy(info, &af->devs[index], sizeof(*info)); info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; return PJ_SUCCESS; } /* API: create default parameter */ static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param) { struct alsa_factory *af = (struct alsa_factory*)f; pjmedia_aud_dev_info *adi; PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); adi = &af->devs[index]; pj_bzero(param, sizeof(*param)); if (adi->input_count && adi->output_count) { param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; param->rec_id = index; param->play_id = index; } else if (adi->input_count) { param->dir = PJMEDIA_DIR_CAPTURE; param->rec_id = index; param->play_id = PJMEDIA_AUD_INVALID_DEV; } else if (adi->output_count) { param->dir = PJMEDIA_DIR_PLAYBACK; param->play_id = index; param->rec_id = PJMEDIA_AUD_INVALID_DEV; } else { return PJMEDIA_EAUD_INVDEV; } param->clock_rate = adi->default_samples_per_sec; param->channel_count = 1; param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; param->bits_per_sample = 16; param->flags = adi->caps; param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; return PJ_SUCCESS; } static int pb_thread_func (void *arg) { struct alsa_stream* stream = (struct alsa_stream*) arg; snd_pcm_t* pcm = stream->pb_pcm; int size = stream->pb_buf_size; snd_pcm_uframes_t nframes = stream->pb_frames; void* user_data = stream->user_data; char* buf = stream->pb_buf; pj_timestamp tstamp; int result; pj_bzero (buf, size); tstamp.u64 = 0; TRACE_((THIS_FILE, "pb_thread_func(%u): Started", (unsigned)syscall(SYS_gettid))); snd_pcm_prepare (pcm); while (!stream->quit) { pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = buf; frame.size = size; frame.timestamp.u64 = tstamp.u64; frame.bit_info = 0; result = stream->pb_cb (user_data, &frame); if (result != PJ_SUCCESS || stream->quit) break; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero (buf, size); result = snd_pcm_writei (pcm, buf, nframes); if (result == -EPIPE) { PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!")); snd_pcm_prepare (pcm); } else if (result < 0) { PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!")); } tstamp.u64 += nframes; } snd_pcm_drain (pcm); TRACE_((THIS_FILE, "pb_thread_func: Stopped")); return PJ_SUCCESS; } static int ca_thread_func (void *arg) { struct alsa_stream* stream = (struct alsa_stream*) arg; snd_pcm_t* pcm = stream->ca_pcm; int size = stream->ca_buf_size; snd_pcm_uframes_t nframes = stream->ca_frames; void* user_data = stream->user_data; char* buf = stream->ca_buf; pj_timestamp tstamp; int result; struct sched_param param; pthread_t* thid; thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this()); param.sched_priority = sched_get_priority_max (SCHED_RR); PJ_LOG (5,(THIS_FILE, "ca_thread_func(%u): Set thread priority " "for audio capture thread.", (unsigned)syscall(SYS_gettid))); result = pthread_setschedparam (*thid, SCHED_RR, ¶m); if (result) { if (result == EPERM) PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, " "root access needed.")); else PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, " "error: %d", result)); } pj_bzero (buf, size); tstamp.u64 = 0; TRACE_((THIS_FILE, "ca_thread_func(%u): Started", (unsigned)syscall(SYS_gettid))); snd_pcm_prepare (pcm); while (!stream->quit) { pjmedia_frame frame; pj_bzero (buf, size); result = snd_pcm_readi (pcm, buf, nframes); if (result == -EPIPE) { PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!")); snd_pcm_prepare (pcm); continue; } else if (result < 0) { PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!")); } if (stream->quit) break; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = (void*) buf; frame.size = size; frame.timestamp.u64 = tstamp.u64; frame.bit_info = 0; result = stream->ca_cb (user_data, &frame); if (result != PJ_SUCCESS || stream->quit) break; tstamp.u64 += nframes; } snd_pcm_drain (pcm); TRACE_((THIS_FILE, "ca_thread_func: Stopped")); return PJ_SUCCESS; } static pj_status_t open_playback (struct alsa_stream* stream, const pjmedia_aud_param *param) { snd_pcm_hw_params_t* params; snd_pcm_format_t format; int result; unsigned int rate; snd_pcm_uframes_t tmp_buf_size; snd_pcm_uframes_t tmp_period_size; if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt) return PJMEDIA_EAUD_INVDEV; /* Open PCM for playback */ PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", stream->af->devs[param->play_id].name)); result = snd_pcm_open (&stream->pb_pcm, stream->af->devs[param->play_id].name, SND_PCM_STREAM_PLAYBACK, 0); if (result < 0) return PJMEDIA_EAUD_SYSERR; /* Allocate a hardware parameters object. */ snd_pcm_hw_params_alloca (¶ms); /* Fill it in with default values. */ snd_pcm_hw_params_any (stream->pb_pcm, params); /* Set interleaved mode */ snd_pcm_hw_params_set_access (stream->pb_pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Set format */ switch (param->bits_per_sample) { case 8: TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S8")); format = SND_PCM_FORMAT_S8; break; case 16: TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE")); format = SND_PCM_FORMAT_S16_LE; break; case 24: TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S24_LE")); format = SND_PCM_FORMAT_S24_LE; break; case 32: TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S32_LE")); format = SND_PCM_FORMAT_S32_LE; break; default: TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE")); format = SND_PCM_FORMAT_S16_LE; break; } snd_pcm_hw_params_set_format (stream->pb_pcm, params, format); /* Set number of channels */ TRACE_((THIS_FILE, "open_playback: set channels: %d", param->channel_count)); snd_pcm_hw_params_set_channels (stream->pb_pcm, params, param->channel_count); /* Set clock rate */ rate = param->clock_rate; TRACE_((THIS_FILE, "open_playback: set clock rate: %d", rate)); snd_pcm_hw_params_set_rate_near (stream->pb_pcm, params, &rate, NULL); TRACE_((THIS_FILE, "open_playback: clock rate set to: %d", rate)); /* Set period size to samples_per_frame frames. */ stream->pb_frames = (snd_pcm_uframes_t) param->samples_per_frame / param->channel_count; TRACE_((THIS_FILE, "open_playback: set period size: %d", stream->pb_frames)); tmp_period_size = stream->pb_frames; snd_pcm_hw_params_set_period_size_near (stream->pb_pcm, params, &tmp_period_size, NULL); TRACE_((THIS_FILE, "open_playback: period size set to: %d", tmp_period_size)); /* Set the sound device buffer size and latency */ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) tmp_buf_size = (rate / 1000) * param->output_latency_ms; else tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_PLAY_LATENCY; snd_pcm_hw_params_set_buffer_size_near (stream->pb_pcm, params, &tmp_buf_size); stream->param.output_latency_ms = tmp_buf_size / (rate / 1000); /* Set our buffer */ stream->pb_buf_size = stream->pb_frames * param->channel_count * (param->bits_per_sample/8); stream->pb_buf = (char*) pj_pool_alloc(stream->pool, stream->pb_buf_size); TRACE_((THIS_FILE, "open_playback: buffer size set to: %d", (int)tmp_buf_size)); TRACE_((THIS_FILE, "open_playback: playback_latency set to: %d ms", (int)stream->param.output_latency_ms)); /* Activate the parameters */ result = snd_pcm_hw_params (stream->pb_pcm, params); if (result < 0) { snd_pcm_close (stream->pb_pcm); return PJMEDIA_EAUD_SYSERR; } PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", stream->af->devs[param->play_id].name, rate, param->channel_count, param->bits_per_sample, stream->pb_frames, (int)stream->param.output_latency_ms)); return PJ_SUCCESS; } static pj_status_t open_capture (struct alsa_stream* stream, const pjmedia_aud_param *param) { snd_pcm_hw_params_t* params; snd_pcm_format_t format; int result; unsigned int rate; snd_pcm_uframes_t tmp_buf_size; snd_pcm_uframes_t tmp_period_size; if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt) return PJMEDIA_EAUD_INVDEV; /* Open PCM for capture */ PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", stream->af->devs[param->rec_id].name)); result = snd_pcm_open (&stream->ca_pcm, stream->af->devs[param->rec_id].name, SND_PCM_STREAM_CAPTURE, 0); if (result < 0) return PJMEDIA_EAUD_SYSERR; /* Allocate a hardware parameters object. */ snd_pcm_hw_params_alloca (¶ms); /* Fill it in with default values. */ snd_pcm_hw_params_any (stream->ca_pcm, params); /* Set interleaved mode */ snd_pcm_hw_params_set_access (stream->ca_pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Set format */ switch (param->bits_per_sample) { case 8: TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S8")); format = SND_PCM_FORMAT_S8; break; case 16: TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE")); format = SND_PCM_FORMAT_S16_LE; break; case 24: TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S24_LE")); format = SND_PCM_FORMAT_S24_LE; break; case 32: TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S32_LE")); format = SND_PCM_FORMAT_S32_LE; break; default: TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE")); format = SND_PCM_FORMAT_S16_LE; break; } snd_pcm_hw_params_set_format (stream->ca_pcm, params, format); /* Set number of channels */ TRACE_((THIS_FILE, "open_capture: set channels: %d", param->channel_count)); snd_pcm_hw_params_set_channels (stream->ca_pcm, params, param->channel_count); /* Set clock rate */ rate = param->clock_rate; TRACE_((THIS_FILE, "open_capture: set clock rate: %d", rate)); snd_pcm_hw_params_set_rate_near (stream->ca_pcm, params, &rate, NULL); TRACE_((THIS_FILE, "open_capture: clock rate set to: %d", rate)); /* Set period size to samples_per_frame frames. */ stream->ca_frames = (snd_pcm_uframes_t) param->samples_per_frame / param->channel_count; TRACE_((THIS_FILE, "open_capture: set period size: %d", stream->ca_frames)); tmp_period_size = stream->ca_frames; snd_pcm_hw_params_set_period_size_near (stream->ca_pcm, params, &tmp_period_size, NULL); TRACE_((THIS_FILE, "open_capture: period size set to: %d", tmp_period_size)); /* Set the sound device buffer size and latency */ if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) tmp_buf_size = (rate / 1000) * param->input_latency_ms; else tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_REC_LATENCY; snd_pcm_hw_params_set_buffer_size_near (stream->ca_pcm, params, &tmp_buf_size); stream->param.input_latency_ms = tmp_buf_size / (rate / 1000); /* Set our buffer */ stream->ca_buf_size = stream->ca_frames * param->channel_count * (param->bits_per_sample/8); stream->ca_buf = (char*) pj_pool_alloc (stream->pool, stream->ca_buf_size); TRACE_((THIS_FILE, "open_capture: buffer size set to: %d", (int)tmp_buf_size)); TRACE_((THIS_FILE, "open_capture: capture_latency set to: %d ms", (int)stream->param.input_latency_ms)); /* Activate the parameters */ result = snd_pcm_hw_params (stream->ca_pcm, params); if (result < 0) { snd_pcm_close (stream->ca_pcm); return PJMEDIA_EAUD_SYSERR; } PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", stream->af->devs[param->rec_id].name, rate, param->channel_count, param->bits_per_sample, stream->ca_frames, (int)stream->param.input_latency_ms)); return PJ_SUCCESS; } /* API: create stream */ static pj_status_t alsa_factory_create_stream(pjmedia_aud_dev_factory *f, const pjmedia_aud_param *param, pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_strm) { struct alsa_factory *af = (struct alsa_factory*)f; pj_status_t status; pj_pool_t* pool; struct alsa_stream* stream; pool = pj_pool_create (af->pf, "alsa%p", 1024, 1024, NULL); if (!pool) return PJ_ENOMEM; /* Allocate and initialize comon stream data */ stream = PJ_POOL_ZALLOC_T (pool, struct alsa_stream); stream->base.op = &alsa_stream_op; stream->pool = pool; stream->af = af; stream->user_data = user_data; stream->pb_cb = play_cb; stream->ca_cb = rec_cb; stream->quit = 0; pj_memcpy(&stream->param, param, sizeof(*param)); /* Init playback */ if (param->dir & PJMEDIA_DIR_PLAYBACK) { status = open_playback (stream, param); if (status != PJ_SUCCESS) { pj_pool_release (pool); return status; } } /* Init capture */ if (param->dir & PJMEDIA_DIR_CAPTURE) { status = open_capture (stream, param); if (status != PJ_SUCCESS) { if (param->dir & PJMEDIA_DIR_PLAYBACK) snd_pcm_close (stream->pb_pcm); pj_pool_release (pool); return status; } } *p_strm = &stream->base; return PJ_SUCCESS; } /* API: get running parameter */ static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi) { struct alsa_stream *stream = (struct alsa_stream*)s; PJ_ASSERT_RETURN(s && pi, PJ_EINVAL); pj_memcpy(pi, &stream->param, sizeof(*pi)); return PJ_SUCCESS; } /* API: get capability */ static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, void *pval) { struct alsa_stream *stream = (struct alsa_stream*)s; PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && (stream->param.dir & PJMEDIA_DIR_CAPTURE)) { /* Recording latency */ *(unsigned*)pval = stream->param.input_latency_ms; return PJ_SUCCESS; } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) { /* Playback latency */ *(unsigned*)pval = stream->param.output_latency_ms; return PJ_SUCCESS; } else { return PJMEDIA_EAUD_INVCAP; } } /* API: set capability */ static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, const void *value) { PJ_UNUSED_ARG(strm); PJ_UNUSED_ARG(cap); PJ_UNUSED_ARG(value); return PJMEDIA_EAUD_INVCAP; } /* API: start stream */ static pj_status_t alsa_stream_start (pjmedia_aud_stream *s) { struct alsa_stream *stream = (struct alsa_stream*)s; pj_status_t status = PJ_SUCCESS; stream->quit = 0; if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { status = pj_thread_create (stream->pool, "alsasound_playback", pb_thread_func, stream, 0, //ZERO, 0, &stream->pb_thread); if (status != PJ_SUCCESS) return status; } if (stream->param.dir & PJMEDIA_DIR_CAPTURE) { status = pj_thread_create (stream->pool, "alsasound_playback", ca_thread_func, stream, 0, //ZERO, 0, &stream->ca_thread); if (status != PJ_SUCCESS) { stream->quit = PJ_TRUE; pj_thread_join(stream->pb_thread); pj_thread_destroy(stream->pb_thread); stream->pb_thread = NULL; } } return status; } /* API: stop stream */ static pj_status_t alsa_stream_stop (pjmedia_aud_stream *s) { struct alsa_stream *stream = (struct alsa_stream*)s; stream->quit = 1; if (stream->pb_thread) { TRACE_((THIS_FILE, "alsa_stream_stop(%u): Waiting for playback to stop.", (unsigned)syscall(SYS_gettid))); pj_thread_join (stream->pb_thread); TRACE_((THIS_FILE, "alsa_stream_stop(%u): playback stopped.", (unsigned)syscall(SYS_gettid))); pj_thread_destroy(stream->pb_thread); stream->pb_thread = NULL; } if (stream->ca_thread) { TRACE_((THIS_FILE, "alsa_stream_stop(%u): Waiting for capture to stop.", (unsigned)syscall(SYS_gettid))); pj_thread_join (stream->ca_thread); TRACE_((THIS_FILE, "alsa_stream_stop(%u): capture stopped.", (unsigned)syscall(SYS_gettid))); pj_thread_destroy(stream->ca_thread); stream->ca_thread = NULL; } return PJ_SUCCESS; } static pj_status_t alsa_stream_destroy (pjmedia_aud_stream *s) { struct alsa_stream *stream = (struct alsa_stream*)s; alsa_stream_stop (s); if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { snd_pcm_close (stream->pb_pcm); stream->pb_pcm = NULL; } if (stream->param.dir & PJMEDIA_DIR_CAPTURE) { snd_pcm_close (stream->ca_pcm); stream->ca_pcm = NULL; } pj_pool_release (stream->pool); return PJ_SUCCESS; } #endif /* PJMEDIA_AUDIO_DEV_HAS_ALSA */