From b24558f7cbc9fabf6f2c5824c9ae9ef8b0b73f3e Mon Sep 17 00:00:00 2001 From: Riza Sulistyo Date: Fri, 8 Mar 2013 08:02:48 +0000 Subject: Re #1636: add initial support for bdIMAD git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4432 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/build/Makefile | 2 +- pjmedia/build/pjmedia_audiodev.vcproj | 4 + pjmedia/include/pjmedia-audiodev/config.h | 8 + pjmedia/include/pjmedia-audiodev/errno.h | 13 + pjmedia/src/pjmedia-audiodev/audiodev.c | 7 + pjmedia/src/pjmedia-audiodev/bdimad_dev.c | 1185 +++++++++++++++++++++++++++++ pjmedia/src/pjmedia-audiodev/errno.c | 14 + 7 files changed, 1232 insertions(+), 1 deletion(-) create mode 100644 pjmedia/src/pjmedia-audiodev/bdimad_dev.c (limited to 'pjmedia') diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile index 026d89c9..d30e6bca 100644 --- a/pjmedia/build/Makefile +++ b/pjmedia/build/Makefile @@ -81,7 +81,7 @@ export PJMEDIA_CFLAGS += $(_CFLAGS) export PJMEDIA_AUDIODEV_SRCDIR = ../src/pjmedia-audiodev export PJMEDIA_AUDIODEV_OBJS += audiodev.o audiotest.o errno.o \ coreaudio_dev.o legacy_dev.o null_dev.o pa_dev.o wmme_dev.o \ - alsa_dev.o bb10_dev.o + alsa_dev.o bb10_dev.o bdimad_dev.o export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS) diff --git a/pjmedia/build/pjmedia_audiodev.vcproj b/pjmedia/build/pjmedia_audiodev.vcproj index b70741e8..693b77ad 100644 --- a/pjmedia/build/pjmedia_audiodev.vcproj +++ b/pjmedia/build/pjmedia_audiodev.vcproj @@ -3564,6 +3564,10 @@ /> + + diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h index 899345bd..6ba4605a 100644 --- a/pjmedia/include/pjmedia-audiodev/config.h +++ b/pjmedia/include/pjmedia-audiodev/config.h @@ -96,6 +96,14 @@ PJ_BEGIN_DECL # define PJMEDIA_AUDIO_DEV_HAS_WMME 1 #endif + +/** + * This setting controls whether BDIMAD support should be included. + */ +#ifndef PJMEDIA_AUDIO_DEV_HAS_BDIMAD +# define PJMEDIA_AUDIO_DEV_HAS_BDIMAD 0 +#endif + /** * This setting controls whether Symbian APS support should be included. diff --git a/pjmedia/include/pjmedia-audiodev/errno.h b/pjmedia/include/pjmedia-audiodev/errno.h index 69bdc7a0..19d1c821 100644 --- a/pjmedia/include/pjmedia-audiodev/errno.h +++ b/pjmedia/include/pjmedia-audiodev/errno.h @@ -104,6 +104,19 @@ PJ_BEGIN_DECL #define PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(err) \ ((int)PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_START-err) +/** + * Mapping from BDIMAD error codes to pjmedia error space. + */ +#define PJMEDIA_AUDIODEV_BDIMAD_ERROR_START \ + (PJMEDIA_AUDIODEV_ERRNO_START + 40000) +#define PJMEDIA_AUDIODEV_BDIMAD_ERROR_END \ + (PJMEDIA_AUDIODEV_BDIMAD_ERROR_START + 2000 - 1) +/** + * Convert BDIMAD error codes to PJLIB error space. + */ +#define PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(err) \ + ((int)PJMEDIA_AUDIODEV_BDIMAD_ERROR_START+err) + /************************************************************ * Audio Device API error codes ***********************************************************/ diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c index 53e63209..f9ee4175 100644 --- a/pjmedia/src/pjmedia-audiodev/audiodev.c +++ b/pjmedia/src/pjmedia-audiodev/audiodev.c @@ -86,6 +86,10 @@ pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf); pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_AUDIO_DEV_HAS_BDIMAD +pjmedia_aud_dev_factory* pjmedia_bdimad_factory(pj_pool_factory *pf); +#endif + #if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS pjmedia_aud_dev_factory* pjmedia_symb_vas_factory(pj_pool_factory *pf); #endif @@ -406,6 +410,9 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf) #if PJMEDIA_AUDIO_DEV_HAS_WMME aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory; #endif +#if PJMEDIA_AUDIO_DEV_HAS_BDIMAD + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_bdimad_factory; +#endif #if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_vas_factory; #endif diff --git a/pjmedia/src/pjmedia-audiodev/bdimad_dev.c b/pjmedia/src/pjmedia-audiodev/bdimad_dev.c new file mode 100644 index 00000000..1f3c5a04 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/bdimad_dev.c @@ -0,0 +1,1185 @@ +/******************************************************* + + bdimad_dev.c + + Author: bdSound Development Team (techsupport@bdsound.com) + + Date: 30/10/2012 + Version 1.0.206 + + Copyright (c) 2012 bdSound s.r.l. (www.bdsound.com) + All Rights Reserved. + + *******************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJMEDIA_AUDIO_DEV_HAS_BDIMAD) && PJMEDIA_AUDIO_DEV_HAS_BDIMAD != 0 + +#include +#include + +/************************** +* bdIMAD +***************************/ +#include + +/* Maximum supported audio devices */ +#define BD_IMAD_MAX_DEV_COUNT 100 +/* Maximum supported device name */ +#define BD_IMAD_MAX_DEV_LENGTH_NAME 256 +/* Only mono mode */ +#define BD_IMAD_MAX_CHANNELS 1 +/* Frequency default value (admitted 8000 Hz, 16000 Hz, 32000 Hz and 48000Hz) */ +#define BD_IMAD_DEFAULT_FREQ 48000 +/* Default milliseconds per buffer */ +#define BD_IMAD_MSECOND_PER_BUFFER 16 +/* Only for supported systems */ +#define BD_IMAD_STARTING_INPUT_VOLUME 50 +/* Only for supported systems */ +#define BD_IMAD_STARTING_OUTPUT_VOLUME 100 +/* Diagnostic Enable/Disable */ +#define BD_IMAD_DIAGNOSTIC BD_IMAD_DIAGNOSTIC_DISABLE + +/* Diagnostic folder path */ +wchar_t * bdImadPjDiagnosticFolderPath = L""; + +#define THIS_FILE "bdimad_dev.c" + +/* BD device info */ +struct bddev_info +{ + pjmedia_aud_dev_info info; + unsigned deviceId; +}; + +/* BD factory */ +struct bd_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *base_pool; + pj_pool_t *pool; + pj_pool_factory *pf; + unsigned dev_count; + struct bddev_info *dev_info; +}; + +/* Sound stream. */ +struct bd_stream +{ + /** Base stream. */ + pjmedia_aud_stream base; + /** Settings. */ + pjmedia_aud_param param; + /** Memory pool. */ + pj_pool_t *pool; + + /** Capture callback. */ + pjmedia_aud_rec_cb rec_cb; + /** Playback callback. */ + pjmedia_aud_play_cb play_cb; + /** Application data. */ + void *user_data; + + /** Frame format */ + pjmedia_format_id fmt_id; + /** Silence pattern */ + pj_uint8_t silence_char; + /** Bytes per frame */ + unsigned bytes_per_frame; + /** Samples per frame */ + unsigned samples_per_frame; + /** Channel count */ + int channel_count; + + /** Extended frame buffer */ + pjmedia_frame_ext *xfrm; + /** Total ext frm size */ + unsigned xfrm_size; + + /** Check running variable */ + int go; + + /** Timestamp iterator for capture */ + pj_timestamp timestampCapture; + /** Timestamp iterator for playback */ + pj_timestamp timestampPlayback; + + /** bdIMAD current session instance */ + bdIMADpj bdIMADpjInstance; + /** bdIMAD current session settings */ + bdIMADpj_Setting_t *bdIMADpjSettingsPtr; + /** bdIMAD current session warnings */ + bdIMADpj_Warnings_t *bdIMADpjWarningPtr; + + pj_bool_t quit_flag; + + pj_bool_t rec_thread_exited; + pj_bool_t rec_thread_initialized; + pj_thread_desc rec_thread_desc; + pj_thread_t *rec_thread; + + pj_bool_t play_thread_exited; + pj_bool_t play_thread_initialized; + pj_thread_desc play_thread_desc; + pj_thread_t *play_thread; + + /* Sometime the record callback does not return framesize as configured + * (e.g: in OSS), while this module must guarantee returning framesize + * as configured in the creation settings. In this case, we need a buffer + * for the recorded samples. + */ + pj_int16_t *rec_buf; + unsigned rec_buf_count; + + /* Sometime the player callback does not request framesize as configured + * (e.g: in Linux OSS) while sound device will always get samples from + * the other component as many as configured samples_per_frame. + */ + pj_int16_t *play_buf; + unsigned play_buf_count; +}; + +/* Prototypes */ + +// pjmedia_aud_dev_factory_op +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(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 factory_refresh(pjmedia_aud_dev_factory *f); + +// pjmedia_aud_stream_op +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); + +/* End Prototypes */ + +/* 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 +}; + +/* End Operations */ + +/* Utility functions */ + +char* BD_IMAD_PJ_WCHARtoCHAR(wchar_t *orig) +{ + size_t origsize = wcslen(orig)+1; + const size_t newsize = origsize*sizeof(wchar_t); + char *nstring = (char*)calloc(newsize, sizeof(char)); + wcstombs(nstring, orig, newsize); + return nstring; +} + +#ifdef __cplusplus +extern "C" { +#endif +void manage_code(const unsigned char * pCode, unsigned char *pMsg1, + unsigned char * pMsg2); +#ifdef __cplusplus +} +#endif + +/* End Utility functions */ + +/**************************************************************************** +* Factory operations +*/ + +/* Init BDIMAD audio driver */ +pjmedia_aud_dev_factory* pjmedia_bdimad_factory(pj_pool_factory *pf) +{ + struct bd_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "BDIMAD_DRIVER", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct bd_factory); + f->pf = pf; + f->base_pool = pool; + f->base.op = &factory_op; + f->pool = NULL; + f->dev_count = 0; + f->dev_info = NULL; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + pj_status_t ret = factory_refresh(f); + if (ret != PJ_SUCCESS) return ret; + + PJ_LOG(4, (THIS_FILE, "BDIMAD initialized")); + + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + struct bd_factory *wf = (struct bd_factory*)f; + unsigned int i = 0; + wchar_t *deviceNamep=NULL; + wchar_t captureDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME]; + unsigned int captureDeviceCount = 0; + wchar_t playbackDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME]; + unsigned int playbackDeviceCount = 0; + + + if(wf->pool != NULL) { + pj_pool_release(wf->pool); + wf->pool = NULL; + } + + // Enumerate capture sound devices + while(bdIMADpj_getDeviceName(BD_IMAD_CAPTURE_DEVICES, &deviceNamep) != + BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY) + { + wcscpy(captureDevName[captureDeviceCount], deviceNamep); + captureDeviceCount++; + } + + // Enumerate playback sound devices + while(bdIMADpj_getDeviceName(BD_IMAD_PLAYBACK_DEVICES, &deviceNamep) != + BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY) + { + wcscpy(playbackDevName[playbackDeviceCount], deviceNamep); + playbackDeviceCount++; + } + + // Set devices info + wf->dev_count = captureDeviceCount + playbackDeviceCount; + wf->pool = pj_pool_create(wf->pf, "BD_IMAD_DEVICES", 1000, 1000, NULL); + wf->dev_info = (struct bddev_info*)pj_pool_calloc(wf->pool, wf->dev_count, + sizeof(struct bddev_info)); + + // Capture device properties + for(i=0;idev_info[i].deviceId = i; + wf->dev_info[i].info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_EC; + wf->dev_info[i].info.default_samples_per_sec = BD_IMAD_DEFAULT_FREQ; + strcpy(wf->dev_info[i].info.driver, "BD_IMAD"); + wf->dev_info[i].info.ext_fmt_cnt = 0; + wf->dev_info[i].info.input_count = BD_IMAD_MAX_CHANNELS; + wf->dev_info[i].info.output_count = 0; + strcpy(wf->dev_info[i].info.name, + BD_IMAD_PJ_WCHARtoCHAR(captureDevName[i])); + wf->dev_info[i].info.routes = 0; + } + + // Playback device properties + for(i=0;idev_info[captureDeviceCount+i].deviceId = captureDeviceCount+i; + wf->dev_info[captureDeviceCount+i].info.caps = + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + wf->dev_info[captureDeviceCount+i].info.default_samples_per_sec = + BD_IMAD_DEFAULT_FREQ; + strcpy(wf->dev_info[captureDeviceCount+i].info.driver, "BD_IMAD"); + wf->dev_info[captureDeviceCount+i].info.ext_fmt_cnt = 0; + wf->dev_info[captureDeviceCount+i].info.input_count = 0; + wf->dev_info[captureDeviceCount+i].info.output_count = + BD_IMAD_MAX_CHANNELS; + strcpy(wf->dev_info[captureDeviceCount+i].info.name, + BD_IMAD_PJ_WCHARtoCHAR(playbackDevName[i])); + wf->dev_info[captureDeviceCount+i].info.routes = 0; + } + + PJ_LOG(4, (THIS_FILE, "BDIMAD found %d devices:", wf->dev_count)); + for(i=0; idev_count; i++) { + PJ_LOG(4, + (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)", + i, + wf->dev_info[i].info.name, + wf->dev_info[i].info.input_count, + wf->dev_info[i].info.output_count)); + } + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct bd_factory *wf = (struct bd_factory*)f; + pj_pool_t *pool = wf->base_pool; + + pj_pool_release(wf->pool); + wf->base_pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + struct bd_factory *wf = (struct bd_factory*)f; + return wf->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) +{ + struct bd_factory *wf = (struct bd_factory*)f; + + PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info)); + + 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) +{ + struct bd_factory *wf = (struct bd_factory*)f; + struct bddev_info *di = &wf->dev_info[index]; + + pj_bzero(param, sizeof(*param)); + if (di->info.input_count && di->info.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + param->channel_count = di->info.output_count; + } else if(di->info.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; + param->channel_count = di->info.input_count; + } else if(di->info.output_count) { + param->dir = PJMEDIA_DIR_PLAYBACK; + param->play_id = index; + param->rec_id = PJMEDIA_AUD_INVALID_DEV; + param->channel_count = di->info.output_count; + } else { + return PJMEDIA_EAUD_INVDEV; + } + + param->bits_per_sample = BD_IMAD_BITS_X_SAMPLE; + param->clock_rate = di->info.default_samples_per_sec; + param->flags = di->info.caps; + param->samples_per_frame = di->info.default_samples_per_sec * + param->channel_count * + BD_IMAD_MSECOND_PER_BUFFER / + 1000; + + if(di->info.caps & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) { + param->input_vol = BD_IMAD_STARTING_INPUT_VOLUME; + } + + if(di->info.caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + param->output_vol = BD_IMAD_STARTING_OUTPUT_VOLUME; + } + + if(di->info.caps & PJMEDIA_AUD_DEV_CAP_EC) { + param->ec_enabled = PJ_TRUE; + } + + return PJ_SUCCESS; +} + +/* callbacks to set data */ +void bdimad_CaptureCallback(void *buffer, int samples, void *user_data) +{ + pj_status_t status = PJ_SUCCESS; + pjmedia_frame frame; + unsigned nsamples; + + struct bd_stream *strm = (struct bd_stream*)user_data; + + if(!strm->go) + goto on_break; + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session, e.g: in MacOS + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("bd_CaptureCallback", + strm->rec_thread_desc, + &strm->rec_thread); + if (status != PJ_SUCCESS) + goto on_break; + + strm->rec_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Recorder thread started")); + } + + /* Calculate number of samples we've got */ + nsamples = samples * strm->channel_count + strm->rec_buf_count; + + /* + RECORD + */ + if (strm->fmt_id == PJMEDIA_FORMAT_L16) { + if (nsamples >= strm->samples_per_frame) { + /* If buffer is not empty, combine the buffer with the just incoming + * samples, then call put_frame. + */ + if (strm->rec_buf_count) { + unsigned chunk_count = 0; + + chunk_count = strm->samples_per_frame - strm->rec_buf_count; + pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, + (pj_int16_t*)buffer, chunk_count); + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = (void*) strm->rec_buf; + frame.size = strm->bytes_per_frame; + frame.timestamp.u64 = strm->timestampCapture.u64; + frame.bit_info = 0; + + status = (*strm->rec_cb)(strm->user_data, &frame); + + buffer = (pj_int16_t*) buffer + chunk_count; + nsamples -= strm->samples_per_frame; + strm->rec_buf_count = 0; + strm->timestampCapture.u64 += strm->samples_per_frame / + strm->channel_count; + } + + /* Give all frames we have */ + while (nsamples >= strm->samples_per_frame && status == 0) { + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = (void*) buffer; + frame.size = strm->bytes_per_frame; + frame.timestamp.u64 = strm->timestampCapture.u64; + frame.bit_info = 0; + + status = (*strm->rec_cb)(strm->user_data, &frame); + + buffer = (pj_int16_t*) buffer + strm->samples_per_frame; + nsamples -= strm->samples_per_frame; + strm->timestampCapture.u64 += strm->samples_per_frame / + strm->channel_count; + } + + /* Store the remaining samples into the buffer */ + if (nsamples && status == 0) { + strm->rec_buf_count = nsamples; + pjmedia_copy_samples(strm->rec_buf, (pj_int16_t*)buffer, + nsamples); + } + + } else { + /* Not enough samples, let's just store them in the buffer */ + pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, + (pj_int16_t*)buffer, + samples * strm->channel_count); + strm->rec_buf_count += samples * strm->channel_count; + } + } else { + pj_assert(!"Frame type not supported"); + } + + strm->timestampCapture.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + + if (status==0) + return; + +on_break: + strm->rec_thread_exited = 1; +} + +/* callbacks to get data */ +int bdimad_PlaybackCallback(void *buffer, int samples, void *user_data) +{ + pj_status_t status = PJ_SUCCESS; + pjmedia_frame frame; + struct bd_stream *strm = (struct bd_stream*)user_data; + unsigned nsamples_req = samples * strm->channel_count; + + if(!strm->go) + goto on_break; + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session, e.g: in MacOS + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (strm->play_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(strm->play_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("bd_PlaybackCallback", + strm->play_thread_desc, + &strm->play_thread); + if (status != PJ_SUCCESS) + goto on_break; + + strm->play_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Player thread started")); + } + + /* + PLAY + */ + if(strm->fmt_id == PJMEDIA_FORMAT_L16) { + /* Check if any buffered samples */ + if (strm->play_buf_count) { + /* samples buffered >= requested by sound device */ + if (strm->play_buf_count >= nsamples_req) { + pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, + nsamples_req); + strm->play_buf_count -= nsamples_req; + pjmedia_move_samples(strm->play_buf, + strm->play_buf + nsamples_req, + strm->play_buf_count); + + return nsamples_req; + } + + /* samples buffered < requested by sound device */ + pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, + strm->play_buf_count); + nsamples_req -= strm->play_buf_count; + buffer = (pj_int16_t*)buffer + strm->play_buf_count; + strm->play_buf_count = 0; + } + + /* Fill output buffer as requested */ + while (nsamples_req && status == 0) { + if (nsamples_req >= strm->samples_per_frame) { + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = buffer; + frame.size = strm->bytes_per_frame; + frame.timestamp.u64 = strm->timestampPlayback.u64; + frame.bit_info = 0; + + status = (*strm->play_cb)(strm->user_data, &frame); + if (status != PJ_SUCCESS) + return 0; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + nsamples_req -= strm->samples_per_frame; + buffer = (pj_int16_t*)buffer + strm->samples_per_frame; + } else { + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = strm->play_buf; + frame.size = strm->bytes_per_frame; + frame.timestamp.u64 = strm->timestampPlayback.u64; + frame.bit_info = 0; + + status = (*strm->play_cb)(strm->user_data, &frame); + if (status != PJ_SUCCESS) + return 0; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, + nsamples_req); + strm->play_buf_count = strm->samples_per_frame - + nsamples_req; + pjmedia_move_samples(strm->play_buf, + strm->play_buf+nsamples_req, + strm->play_buf_count); + nsamples_req = 0; + } + + strm->timestampPlayback.u64 += strm->samples_per_frame / + strm->channel_count; + } + } else { + pj_assert(!"Frame type not supported"); + } + + if(status != PJ_SUCCESS) { + return 0; + } + + strm->timestampPlayback.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + + if (status == 0) + return samples; + +on_break: + strm->play_thread_exited = 1; + return 0; +} + +/* Internal: Get format name */ +static const char *get_fmt_name(pj_uint32_t id) +{ + static char name[8]; + + if (id == PJMEDIA_FORMAT_L16) + return "PCM"; + pj_memcpy(name, &id, 4); + name[4] = '\0'; + return name; +} + +/* Internal: create BD device. */ +static pj_status_t init_streams(struct bd_factory *wf, + struct bd_stream *strm, + const pjmedia_aud_param *prm) +{ + unsigned ptime; + enum bdIMADpj_Status errorInitAEC; + wchar_t *deviceNamep=NULL; + wchar_t captureDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME]; + int captureDeviceCount = 0; + wchar_t playbackDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME]; + int playbackDeviceCount = 0; + + PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL); + PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL); + + ptime = prm->samples_per_frame * + 1000 / + (prm->clock_rate * prm->channel_count); + strm->bytes_per_frame = (prm->clock_rate * + ((prm->channel_count * prm->bits_per_sample) / 8)) * + ptime / + 1000; + strm->timestampCapture.u64 = 0; + strm->timestampPlayback.u64 = 0; + + //BD_IMAD_PJ + bdIMADpj_CreateStructures(&strm->bdIMADpjSettingsPtr, + &strm->bdIMADpjWarningPtr); + + strm->bdIMADpjSettingsPtr->FrameSize_ms = ptime; + strm->bdIMADpjSettingsPtr->DiagnosticEnable = BD_IMAD_DIAGNOSTIC; + strm->bdIMADpjSettingsPtr->DiagnosticFolderPath = + bdImadPjDiagnosticFolderPath; + strm->bdIMADpjSettingsPtr->validate = (void *)manage_code; + + if(prm->clock_rate != 8000 && prm->clock_rate != 16000 + && prm->clock_rate != 32000 && prm->clock_rate != 48000) { + PJ_LOG(4, (THIS_FILE, + "BDIMAD support 8000 Hz, 16000 Hz, 32000 Hz and 48000 Hz " + "frequency.")); + } + strm->bdIMADpjSettingsPtr->SamplingFrequency = prm->clock_rate; + + if(prm->channel_count > BD_IMAD_MAX_CHANNELS) { + PJ_LOG(4, (THIS_FILE, + "BDIMAD doesn't support a number of channels upper than %d.", + BD_IMAD_MAX_CHANNELS)); + } + + // Enumerate capture sound devices + while(bdIMADpj_getDeviceName(BD_IMAD_CAPTURE_DEVICES, &deviceNamep) != + BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY) + { + wcscpy(captureDevName[captureDeviceCount], deviceNamep); + captureDeviceCount++; + } + strm->bdIMADpjSettingsPtr->CaptureDevice = captureDevName[(int)prm->rec_id]; + + // Enumerate playback sound devices + while(bdIMADpj_getDeviceName(BD_IMAD_PLAYBACK_DEVICES, &deviceNamep) != + BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY) + { + wcscpy(playbackDevName[playbackDeviceCount], deviceNamep); + playbackDeviceCount++; + } + strm->bdIMADpjSettingsPtr->PlayDevice = + playbackDevName[(int)(prm->play_id-captureDeviceCount)]; + + strm->bdIMADpjSettingsPtr->cb_emptyCaptureBuffer = &bdimad_CaptureCallback; + strm->bdIMADpjSettingsPtr->cb_emptyCaptureBuffer_user_data = (void*)strm; + strm->bdIMADpjSettingsPtr->cb_fillPlayBackBuffer = &bdimad_PlaybackCallback; + strm->bdIMADpjSettingsPtr->cb_fillPlayBackBuffer_user_data = (void*)strm; + + if(strm->bdIMADpjInstance != NULL) + bdIMADpj_FreeAEC(&strm->bdIMADpjInstance); + strm->bdIMADpjInstance = NULL; + + errorInitAEC = bdIMADpj_InitAEC(&strm->bdIMADpjInstance, + &strm->bdIMADpjSettingsPtr, + &strm->bdIMADpjWarningPtr); + + { + int auxInt = (prm->ec_enabled == PJ_TRUE ? 1 : 0); + bdIMADpj_setParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_AEC_ENABLE, + &auxInt); + auxInt = 1; + //Mic control On by default + bdIMADpj_setParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_MIC_CONTROL_ENABLE, + &auxInt); + } + + if(errorInitAEC != BD_PJ_OK && + errorInitAEC != BD_PJ_WARN_BDIMAD_WARNING_ASSERTED) + { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(errorInitAEC); + } + + return PJ_SUCCESS; +} + +/**************************************** + API: create stream +*****************************************/ +static pj_status_t stream_stopBDIMAD(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + pj_status_t status = PJ_SUCCESS; + int i, err = 0; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + strm->go = 0; + + for (i=0; !strm->rec_thread_exited && i<100; ++i) + pj_thread_sleep(10); + for (i=0; !strm->play_thread_exited && i<100; ++i) + pj_thread_sleep(10); + + pj_thread_sleep(1); + + PJ_LOG(5,(THIS_FILE, "Stopping stream..")); + + strm->play_thread_initialized = 0; + strm->rec_thread_initialized = 0; + + PJ_LOG(5,(THIS_FILE, "Done, status=%d", err)); + + return status; +} + +static pj_status_t stream_stop(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + if(strm->bdIMADpjInstance != NULL) { + return stream_stopBDIMAD(s); + } else { + return PJMEDIA_EAUD_ERR; + } +} + +static pj_status_t stream_destroyBDIMAD(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + int i = 0; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + stream_stopBDIMAD(s); + + // DeInit BDIMAD + bdIMADpj_FreeAEC(&strm->bdIMADpjInstance); + PJ_LOG(4, (THIS_FILE, "Free AEC")); + + bdIMADpj_FreeStructures(&strm->bdIMADpjSettingsPtr, + &strm->bdIMADpjWarningPtr); + PJ_LOG(4, (THIS_FILE, "Free AEC Structure")); + + strm->bdIMADpjInstance = NULL; + strm->bdIMADpjSettingsPtr = NULL; + strm->bdIMADpjWarningPtr = NULL; + + strm->quit_flag = 1; + for (i=0; !strm->rec_thread_exited && i<100; ++i) { + pj_thread_sleep(1); + } + for (i=0; !strm->play_thread_exited && i<100; ++i) { + pj_thread_sleep(1); + } + + PJ_LOG(5,(THIS_FILE, "Destroying stream..")); + + pj_pool_release(strm->pool); + return PJ_SUCCESS; +} + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + if(strm->bdIMADpjInstance != NULL) { + return stream_destroyBDIMAD(s); + } else { + return PJMEDIA_EAUD_ERR; + } +} + +/* API: set capability */ +static pj_status_t stream_set_capBDIMAD(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct bd_stream *strm = (struct bd_stream*)s; + bdIMADpj_Status res = BD_PJ_OK; + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + /* Output volume setting */ + float vol = (float)*(unsigned*)pval; + + if(vol > 100.0f) vol = 100.0f; + if(vol < 0.0f) vol = 0.0f; + + vol = vol / 100.0f; + res = bdIMADpj_setParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_SPK_VOLUME, &vol); + + + if(res == BD_PJ_OK) { + strm->param.output_vol = *(unsigned*)pval; + return PJ_SUCCESS; + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } + + if(cap == PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) { + /* Input volume setting */ + float vol = (float)*(unsigned*)pval; + + if(vol > 100.0f) vol = 100.0f; + if(vol < 0.0f) vol = 0.0f; + + vol = vol / 100.0f; + res = bdIMADpj_setParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_MIC_VOLUME, &vol); + if(res == BD_PJ_OK) { + strm->param.input_vol = *(unsigned*)pval; + return PJ_SUCCESS; + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } + + if(cap == PJMEDIA_AUD_DEV_CAP_EC) { + int aecOnOff = (*(pj_bool_t*)pval == PJ_TRUE ? 1 : 0); + + /* AEC setting */ + res = bdIMADpj_setParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_AEC_ENABLE, + &aecOnOff); + if(res == BD_PJ_OK) { + strm->param.ec_enabled = (aecOnOff == 1 ? PJ_TRUE : PJ_FALSE); + return PJ_SUCCESS; + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } + + return PJMEDIA_EAUD_INVCAP; +} + +static pj_status_t factory_create_streamBDIMAD(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 bd_factory *wf = (struct bd_factory*)f; + pj_pool_t *pool; + struct bd_stream *strm; + pj_uint8_t silence_char; + pj_status_t status; + + switch (param->ext_fmt.id) { + case PJMEDIA_FORMAT_L16: + silence_char = '\0'; + break; + default: + return PJMEDIA_EAUD_BADFORMAT; + } + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(wf->pf, "BDIMAD_STREAM", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct bd_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + strm->fmt_id = (pjmedia_format_id)param->ext_fmt.id; + strm->silence_char = silence_char; + strm->channel_count = param->channel_count; + strm->samples_per_frame = param->samples_per_frame; + + if (param->dir & PJMEDIA_DIR_CAPTURE_PLAYBACK) { + status = init_streams(wf, strm, param); + + if (status != PJ_SUCCESS) { + stream_destroyBDIMAD(&strm->base); + return status; + } + } else { + stream_destroyBDIMAD(&strm->base); + return PJMEDIA_EAUD_ERR; + } + + strm->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, + strm->bytes_per_frame); + if (!strm->rec_buf) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + strm->rec_buf_count = 0; + + strm->play_buf = (pj_int16_t*)pj_pool_alloc(pool, + strm->bytes_per_frame); + if (!strm->play_buf) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + strm->play_buf_count = 0; + + /* Apply the remaining settings */ + if(param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + stream_set_capBDIMAD(&strm->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + } + if(param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) { + stream_set_capBDIMAD(&strm->base, + PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + ¶m->input_vol); + } + if(param->flags & PJMEDIA_AUD_DEV_CAP_EC) { + stream_set_capBDIMAD(&strm->base, + PJMEDIA_AUD_DEV_CAP_EC, + ¶m->ec_enabled); + } + + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +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) +{ + return factory_create_streamBDIMAD(f, param, rec_cb, + play_cb, user_data, p_aud_strm); +} +// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- +// ---------------------------------------------------------------------- + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + // Get the output volume setting + if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + // Get the input volume setting + if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &pi->input_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING; + } + + // Get the AEC setting + if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC, &pi->ec_enabled) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_EC; + } + + return PJ_SUCCESS; +} + +static pj_status_t stream_get_capBDIMAD(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct bd_stream *strm = (struct bd_stream*)s; + bdIMADpj_Status res = BD_PJ_OK; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if(cap == PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) + { + /* Input volume setting */ + float vol; + res = bdIMADpj_getParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_MIC_VOLUME, &vol); + if(res == BD_PJ_OK) { + vol = vol * 100; + if(vol > 100.0f) vol = 100.0f; + if(vol < 0.0f) vol = 0.0f; + *(unsigned int *)pval = (unsigned int)vol; + return PJ_SUCCESS; + } else{ + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } else if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + /* Output volume setting */ + float vol; + res = bdIMADpj_getParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_SPK_VOLUME, &vol); + if(res == BD_PJ_OK) { + vol = vol * 100; + if(vol > 100.0f) vol = 100.0f; + if(vol < 0.0f) vol = 0.0f; + *(unsigned int *)pval = (unsigned int)vol; + return PJ_SUCCESS; + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } + else if(cap == PJMEDIA_AUD_DEV_CAP_EC) { + int aecIsOn; + res = bdIMADpj_getParameter(strm->bdIMADpjInstance, + BD_PARAM_IMAD_PJ_AEC_ENABLE, &aecIsOn); + if(res == BD_PJ_OK) { + *(pj_bool_t*)pval = (aecIsOn == 1 ? PJ_TRUE : PJ_FALSE); + return PJ_SUCCESS; + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res); + } + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +static pj_status_t stream_startBDIMAD(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + strm->go = 1; + + 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 bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + if(strm->bdIMADpjInstance != NULL) { + return stream_get_capBDIMAD(s, cap, pval); + } else { + return PJMEDIA_EAUD_ERR; + } +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + if(strm->bdIMADpjInstance != NULL) { + return stream_set_capBDIMAD(s, cap, pval); + } else { + return PJMEDIA_EAUD_ERR; + } +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *s) +{ + struct bd_stream *strm = (struct bd_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + if(strm->bdIMADpjInstance != NULL) { + return stream_startBDIMAD(s); + } else { + return PJMEDIA_EAUD_ERR; + } +} + +#if defined (_MSC_VER) +#pragma comment ( lib, "bdClientValidation.lib" ) +#pragma comment ( lib, "bdIMADpj.lib" ) +#endif + + +#endif /* PJMEDIA_AUDIO_DEV_HAS_BDIMAD */ + diff --git a/pjmedia/src/pjmedia-audiodev/errno.c b/pjmedia/src/pjmedia-audiodev/errno.c index 49f837bb..498e43d9 100644 --- a/pjmedia/src/pjmedia-audiodev/errno.c +++ b/pjmedia/src/pjmedia-audiodev/errno.c @@ -155,6 +155,20 @@ PJ_DEF(pj_str_t) pjmedia_audiodev_strerror(pj_status_t statcode, } else #endif +/* See if the error comes from BDIMAD */ +#if PJMEDIA_AUDIO_DEV_HAS_BDIMAD + + if (statcode >= PJMEDIA_AUDIODEV_BDIMAD_ERROR_START && + statcode < PJMEDIA_AUDIODEV_BDIMAD_ERROR_END) + { + pj_status_t native_err; + native_err = statcode - PJMEDIA_AUDIODEV_BDIMAD_ERROR_START; + + pj_ansi_snprintf(buf, bufsize, "BDIMAD native error %d", native_err); + return pj_str(buf); + } else +#endif + /* Audiodev error */ if (statcode >= PJMEDIA_AUDIODEV_ERRNO_START && statcode < PJMEDIA_AUDIODEV_ERRNO_END) -- cgit v1.2.3