| /* $Id$ */ |
| /* |
| * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2007-2009 Keystream AB and Konftel AB, All rights reserved. |
| * Author: <dan.aberg@keystream.se> |
| * |
| * 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 <pjmedia_audiodev.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/pool.h> |
| #include <pjmedia/errno.h> |
| |
| #if defined(PJMEDIA_AUDIO_DEV_HAS_ALSA) && PJMEDIA_AUDIO_DEV_HAS_ALSA |
| |
| #include <sys/syscall.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <sys/select.h> |
| #include <pthread.h> |
| #include <errno.h> |
| #include <alsa/asoundlib.h> |
| |
| |
| #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 && index<af->dev_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 && index<af->dev_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 */ |