/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * 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 #if PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE #define THIS_FILE "legacy_dev.c" /* Legacy devices factory */ struct legacy_factory { pjmedia_aud_dev_factory base; pj_pool_t *pool; pj_pool_factory *pf; }; struct legacy_stream { pjmedia_aud_stream base; pj_pool_t *pool; pjmedia_aud_param param; pjmedia_snd_stream *snd_strm; pjmedia_aud_play_cb user_play_cb; pjmedia_aud_rec_cb user_rec_cb; void *user_user_data; unsigned input_latency; unsigned output_latency; }; /* Prototypes */ static pj_status_t factory_init(pjmedia_aud_dev_factory *f); static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info); static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param); static pj_status_t 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_aud_strm); static pj_status_t stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, void *value); static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, const void *value); static pj_status_t stream_start(pjmedia_aud_stream *strm); static pj_status_t stream_stop(pjmedia_aud_stream *strm); static pj_status_t stream_destroy(pjmedia_aud_stream *strm); /* Operations */ static pjmedia_aud_dev_factory_op factory_op = { &factory_init, &factory_destroy, &factory_get_dev_count, &factory_get_dev_info, &factory_default_param, &factory_create_stream, &factory_refresh }; static pjmedia_aud_stream_op stream_op = { &stream_get_param, &stream_get_cap, &stream_set_cap, &stream_start, &stream_stop, &stream_destroy }; /**************************************************************************** * Factory operations */ /* * Init legacy audio driver. */ pjmedia_aud_dev_factory* pjmedia_legacy_factory(pj_pool_factory *pf) { struct legacy_factory *f; pj_pool_t *pool; pool = pj_pool_create(pf, "legacy-snd", 512, 512, NULL); f = PJ_POOL_ZALLOC_T(pool, struct legacy_factory); f->pf = pf; f->pool = pool; f->base.op = &factory_op; return &f->base; } /* API: init factory */ static pj_status_t factory_init(pjmedia_aud_dev_factory *f) { struct legacy_factory *wf = (struct legacy_factory*)f; return pjmedia_snd_init(wf->pf); } /* API: destroy factory */ static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) { struct legacy_factory *wf = (struct legacy_factory*)f; pj_status_t status; status = pjmedia_snd_deinit(); if (status == PJ_SUCCESS) { pj_pool_t *pool = wf->pool; wf->pool = NULL; pj_pool_release(pool); } return status; } /* API: refresh the list of devices */ static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) { PJ_UNUSED_ARG(f); return PJ_ENOTSUP; } /* API: get number of devices */ static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) { PJ_UNUSED_ARG(f); return pjmedia_snd_get_dev_count(); } /* API: get device info */ static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info) { const pjmedia_snd_dev_info *si = pjmedia_snd_get_dev_info(index);; PJ_UNUSED_ARG(f); if (si == NULL) return PJMEDIA_EAUD_INVDEV; pj_bzero(info, sizeof(*info)); pj_ansi_strncpy(info->name, si->name, sizeof(info->name)); info->name[sizeof(info->name)-1] = '\0'; info->input_count = si->input_count; info->output_count = si->output_count; info->default_samples_per_sec = si->default_samples_per_sec; pj_ansi_strcpy(info->driver, "legacy"); info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; return PJ_SUCCESS; } /* API: create default device parameter */ static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param) { pjmedia_aud_dev_info di; pj_status_t status; status = factory_get_dev_info(f, index, &di); if (status != PJ_SUCCESS) return status; pj_bzero(param, sizeof(*param)); if (di.input_count && di.output_count) { param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; param->rec_id = index; param->play_id = index; } else if (di.input_count) { param->dir = PJMEDIA_DIR_CAPTURE; param->rec_id = index; param->play_id = PJMEDIA_AUD_INVALID_DEV; } else if (di.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 = di.default_samples_per_sec; param->channel_count = 1; param->samples_per_frame = di.default_samples_per_sec * 20 / 1000; param->bits_per_sample = 16; param->flags = di.caps; param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; return PJ_SUCCESS; } /* Callback from legacy sound device */ static pj_status_t snd_play_cb(/* in */ void *user_data, /* in */ pj_uint32_t timestamp, /* out */ void *output, /* out */ unsigned size) { struct legacy_stream *strm = (struct legacy_stream*)user_data; pjmedia_frame frame; pj_status_t status; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = output; frame.size = size; frame.timestamp.u64 = timestamp; status = strm->user_play_cb(strm->user_user_data, &frame); if (status != PJ_SUCCESS) return status; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { pj_bzero(output, size); } return PJ_SUCCESS; } /* Callback from legacy sound device */ static pj_status_t snd_rec_cb( /* in */ void *user_data, /* in */ pj_uint32_t timestamp, /* in */ void *input, /* in*/ unsigned size) { struct legacy_stream *strm = (struct legacy_stream*)user_data; pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = input; frame.size = size; frame.timestamp.u64 = timestamp; return strm->user_rec_cb(strm->user_user_data, &frame); } /* API: create stream */ static pj_status_t 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_aud_strm) { struct legacy_factory *wf = (struct legacy_factory*)f; pj_pool_t *pool; struct legacy_stream *strm; pj_status_t status; /* Initialize our stream data */ pool = pj_pool_create(wf->pf, "legacy-snd", 512, 512, NULL); strm = PJ_POOL_ZALLOC_T(pool, struct legacy_stream); strm->pool = pool; strm->user_rec_cb = rec_cb; strm->user_play_cb = play_cb; strm->user_user_data = user_data; pj_memcpy(&strm->param, param, sizeof(*param)); /* Set the latency if wanted */ if (param->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK && param->flags & (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)) { PJ_ASSERT_RETURN(param->input_latency_ms && param->output_latency_ms, PJMEDIA_EAUD_BADLATENCY); strm->input_latency = param->input_latency_ms; strm->output_latency = param->output_latency_ms; status = pjmedia_snd_set_latency(param->input_latency_ms, param->output_latency_ms); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } } /* Open the stream */ if (param->dir == PJMEDIA_DIR_CAPTURE) { status = pjmedia_snd_open_rec(param->rec_id, param->clock_rate, param->channel_count, param->samples_per_frame, param->bits_per_sample, &snd_rec_cb, strm, &strm->snd_strm); } else if (param->dir == PJMEDIA_DIR_PLAYBACK) { status = pjmedia_snd_open_player(param->play_id, param->clock_rate, param->channel_count, param->samples_per_frame, param->bits_per_sample, &snd_play_cb, strm, &strm->snd_strm); } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { status = pjmedia_snd_open(param->rec_id, param->play_id, param->clock_rate, param->channel_count, param->samples_per_frame, param->bits_per_sample, &snd_rec_cb, &snd_play_cb, strm, &strm->snd_strm); } else { pj_assert(!"Invalid direction!"); return PJ_EINVAL; } if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } *p_aud_strm = &strm->base; return PJ_SUCCESS; } /* API: Get stream info. */ static pj_status_t stream_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi) { struct legacy_stream *strm = (struct legacy_stream*)s; PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); pj_memcpy(pi, &strm->param, sizeof(*pi)); if (strm->input_latency) { pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; pi->input_latency_ms = strm->input_latency; } else { pi->flags &= ~PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; } if (strm->output_latency) { pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; pi->output_latency_ms = strm->output_latency; } else { pi->flags &= ~PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; } return PJ_SUCCESS; } /* API: get capability */ static pj_status_t stream_get_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, void *pval) { struct legacy_stream *strm = (struct legacy_stream*)s; PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL); if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && (strm->param.dir & PJMEDIA_DIR_CAPTURE)) { /* Recording latency */ if (strm->input_latency) { *(unsigned*)pval = strm->input_latency; return PJ_SUCCESS; } else { return PJMEDIA_EAUD_INVCAP; } } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) { /* Playback latency */ if (strm->output_latency) { *(unsigned*)pval = strm->output_latency; return PJ_SUCCESS; } else { return PJMEDIA_EAUD_INVCAP; } } else { return PJMEDIA_EAUD_INVCAP; } } /* API: set capability */ static pj_status_t stream_set_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, const void *pval) { PJ_UNUSED_ARG(s); PJ_UNUSED_ARG(cap); PJ_UNUSED_ARG(pval); return PJMEDIA_EAUD_INVCAP; } /* API: Start stream. */ static pj_status_t stream_start(pjmedia_aud_stream *s) { struct legacy_stream *strm = (struct legacy_stream*)s; return pjmedia_snd_stream_start(strm->snd_strm); } /* API: Stop stream. */ static pj_status_t stream_stop(pjmedia_aud_stream *s) { struct legacy_stream *strm = (struct legacy_stream*)s; return pjmedia_snd_stream_stop(strm->snd_strm); } /* API: Destroy stream. */ static pj_status_t stream_destroy(pjmedia_aud_stream *s) { struct legacy_stream *strm = (struct legacy_stream*)s; pj_status_t status; status = pjmedia_snd_stream_close(strm->snd_strm); if (status == PJ_SUCCESS) { pj_pool_t *pool = strm->pool; strm->pool = NULL; pj_pool_release(pool); } return status; } #endif /* PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE */