/******************************************************* | |
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 <pjmedia-audiodev/audiodev_imp.h> | |
#include <pj/assert.h> | |
#include <pj/log.h> | |
#include <pj/os.h> | |
#include <pj/string.h> | |
#include <pj/unicode.h> | |
#include <pjmedia/errno.h> | |
#if defined(PJMEDIA_AUDIO_DEV_HAS_BDIMAD) && PJMEDIA_AUDIO_DEV_HAS_BDIMAD != 0 | |
#include <math.h> | |
#include <wchar.h> | |
/************************** | |
* bdIMAD | |
***************************/ | |
#include "../../third_party/bdsound/include/bdimad.h" | |
/* 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;i<captureDeviceCount;i++) { | |
wf->dev_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;i<playbackDeviceCount;i++) { | |
wf->dev_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; i<wf->dev_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 */ | |