From 5b2cb511256b572db42309e52005d59a91f4768d Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 23 Mar 2006 13:15:59 +0000 Subject: Changed sound device framework to allow opening bidirectional streams from one device git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@352 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/src/pjmedia/conference.c | 75 +++++-------- pjmedia/src/pjmedia/nullsound.c | 29 ++++- pjmedia/src/pjmedia/pasound.c | 231 ++++++++++++++++++++++++++++++++------- pjmedia/src/pjmedia/sound_port.c | 103 ++++++++++++++--- 4 files changed, 332 insertions(+), 106 deletions(-) (limited to 'pjmedia/src') diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index c6cb1278..fad562c7 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -158,8 +158,7 @@ struct pjmedia_conf unsigned max_ports; /**< Maximum ports. */ unsigned port_cnt; /**< Current number of ports. */ unsigned connect_cnt; /**< Total number of connections */ - pjmedia_snd_port *snd_rec; /**< Sound recorder stream. */ - pjmedia_snd_port *snd_player; /**< Sound player stream. */ + pjmedia_snd_port *snd_dev_port; /**< Sound device port. */ pjmedia_port *master_port; /**< Port zero's port. */ pj_mutex_t *mutex; /**< Conference mutex. */ struct conf_port **ports; /**< Array of ports. */ @@ -342,40 +341,33 @@ static pj_status_t create_sound_port( pj_pool_t *pool, conf->port_cnt++; - /* Create sound devices: */ + /* Create sound device port: */ - /* Create recorder only if mic is not disabled. */ - if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0 && - (conf->options & PJMEDIA_CONF_NO_MIC) == 0) - { - status = pjmedia_snd_port_create_rec( pool, -1, conf->clock_rate, + if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { + + /* + * If capture is disabled then create player only port. + * Otherwise create bidirectional sound device port. + */ + if (conf->options & PJMEDIA_CONF_NO_MIC) { + status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, + conf->channel_count, + conf->samples_per_frame, + conf->bits_per_sample, + 0, /* options */ + &conf->snd_dev_port); + + } else { + status = pjmedia_snd_port_create( pool, -1, -1, conf->clock_rate, conf->channel_count, conf->samples_per_frame, conf->bits_per_sample, 0, /* Options */ - &conf->snd_rec); - if (status != PJ_SUCCESS) { - conf->snd_rec = NULL; - return status; + &conf->snd_dev_port); } - } - /* Create player device */ - if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { - status = pjmedia_snd_port_create_player(pool, -1, conf->clock_rate, - conf->channel_count, - conf->samples_per_frame, - conf->bits_per_sample, - 0, /* options */ - &conf->snd_player); - if (status != PJ_SUCCESS) { - if (conf->snd_rec) { - pjmedia_snd_port_destroy(conf->snd_rec); - conf->snd_rec = NULL; - } - conf->snd_player = NULL; + if (status != PJ_SUCCESS) return status; - } } @@ -433,7 +425,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, conf->master_port->info.channel_count = channel_count; conf->master_port->info.encoding_name = pj_str("pcm"); conf->master_port->info.has_info = 1; - conf->master_port->info.name = pj_str("master port"); + conf->master_port->info.name = pj_str("sound-dev"); conf->master_port->info.need_info = 0; conf->master_port->info.pt = 0xFF; conf->master_port->info.sample_rate = clock_rate; @@ -464,8 +456,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, /* If sound device was created, connect sound device to the * master port. */ - if (conf->snd_player) { - status = pjmedia_snd_port_connect( conf->snd_player, + if (conf->snd_dev_port) { + status = pjmedia_snd_port_connect( conf->snd_dev_port, conf->master_port ); if (status != PJ_SUCCESS) { pjmedia_conf_destroy(conf); @@ -473,15 +465,6 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, } } - if (conf->snd_rec) { - status = pjmedia_snd_port_connect( conf->snd_rec, - conf->master_port); - if (status != PJ_SUCCESS) { - pjmedia_conf_destroy(conf); - return status; - } - } - /* Done */ @@ -519,14 +502,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) { PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); - /* Destroy sound devices. */ - if (conf->snd_rec) { - pjmedia_snd_port_destroy(conf->snd_rec); - conf->snd_rec = NULL; - } - if (conf->snd_player) { - pjmedia_snd_port_destroy(conf->snd_player); - conf->snd_player = NULL; + /* Destroy sound device port. */ + if (conf->snd_dev_port) { + pjmedia_snd_port_destroy(conf->snd_dev_port); + conf->snd_dev_port = NULL; } /* Destroy mutex */ diff --git a/pjmedia/src/pjmedia/nullsound.c b/pjmedia/src/pjmedia/nullsound.c index 9d454c08..e60cfa31 100644 --- a/pjmedia/src/pjmedia/nullsound.c +++ b/pjmedia/src/pjmedia/nullsound.c @@ -47,7 +47,7 @@ PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) return &null_info; } -PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index, +PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, @@ -91,6 +91,33 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, return PJ_SUCCESS; } +PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + PJ_UNUSED_ARG(rec_id); + PJ_UNUSED_ARG(play_id); + PJ_UNUSED_ARG(clock_rate); + PJ_UNUSED_ARG(channel_count); + PJ_UNUSED_ARG(samples_per_frame); + PJ_UNUSED_ARG(bits_per_sample); + PJ_UNUSED_ARG(rec_cb); + PJ_UNUSED_ARG(play_cb); + PJ_UNUSED_ARG(user_data); + + *p_snd_strm = (void*)1; + + return PJ_SUCCESS; +} + + PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) { PJ_UNUSED_ARG(stream); diff --git a/pjmedia/src/pjmedia/pasound.c b/pjmedia/src/pjmedia/pasound.c index cb36b927..44bac29b 100644 --- a/pjmedia/src/pjmedia/pasound.c +++ b/pjmedia/src/pjmedia/pasound.c @@ -32,26 +32,35 @@ static struct snd_mgr pj_pool_factory *factory; } snd_mgr; +/* + * Sound stream descriptor. + * This struct may be used for both unidirectional or bidirectional sound + * streams. + */ struct pjmedia_snd_stream { - pj_pool_t *pool; - pj_str_t name; - PaStream *stream; - int dev_index; - int bytes_per_sample; - pj_uint32_t samples_per_sec; - int channel_count; - pj_uint32_t timestamp; - pj_uint32_t underflow; - pj_uint32_t overflow; - void *user_data; - pjmedia_snd_rec_cb rec_cb; - pjmedia_snd_play_cb play_cb; - pj_bool_t quit_flag; - pj_bool_t thread_has_exited; - pj_bool_t thread_initialized; - pj_thread_desc thread_desc; - pj_thread_t *thread; + pj_pool_t *pool; + pj_str_t name; + pjmedia_dir dir; + int bytes_per_sample; + pj_uint32_t samples_per_sec; + int channel_count; + + PaStream *stream; + void *user_data; + pjmedia_snd_rec_cb rec_cb; + pjmedia_snd_play_cb play_cb; + + pj_uint32_t timestamp; + pj_uint32_t underflow; + pj_uint32_t overflow; + + pj_bool_t quit_flag; + + pj_bool_t thread_exited; + pj_bool_t thread_initialized; + pj_thread_desc thread_desc; + pj_thread_t *thread; }; @@ -72,7 +81,8 @@ static int PaRecorderCallback(const void *input, goto on_break; if (stream->thread_initialized == 0) { - status = pj_thread_register("pa_rec", stream->thread_desc, &stream->thread); + status = pj_thread_register("pa_rec", stream->thread_desc, + &stream->thread); stream->thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Recorder thread started")); } @@ -92,7 +102,7 @@ static int PaRecorderCallback(const void *input, return paContinue; on_break: - stream->thread_has_exited = 1; + stream->thread_exited = 1; return paAbort; } @@ -115,7 +125,8 @@ static int PaPlayerCallback( const void *input, goto on_break; if (stream->thread_initialized == 0) { - status = pj_thread_register("pa_rec", stream->thread_desc, &stream->thread); + status = pj_thread_register("portaudio", stream->thread_desc, + &stream->thread); stream->thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Player thread started")); } @@ -134,11 +145,31 @@ static int PaPlayerCallback( const void *input, return paContinue; on_break: - stream->thread_has_exited = 1; + stream->thread_exited = 1; return paAbort; } +static int PaRecorderPlayerCallback( const void *input, + void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + int rc; + + rc = PaRecorderCallback(input, output, frameCount, timeInfo, + statusFlags, userData); + if (rc != paContinue) + return rc; + + rc = PaPlayerCallback(input, output, frameCount, timeInfo, + statusFlags, userData); + return rc; +} + + /* * Init sound library. */ @@ -194,7 +225,7 @@ PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) /* * Open stream. */ -PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index, +PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, @@ -215,7 +246,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index, int count = Pa_GetDeviceCount(); for (index=0; indexmaxInputChannels > 0) + if (paDevInfo->maxInputChannels >= (int)channel_count) break; } if (index == count) { @@ -245,9 +276,9 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_recorder( int index, stream = pj_pool_zalloc(pool, sizeof(*stream)); stream->pool = pool; - stream->name = pj_str("snd-rec"); + pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); + stream->dir = PJMEDIA_DIR_CAPTURE; stream->user_data = user_data; - stream->dev_index = index; stream->samples_per_sec = samples_per_frame; stream->bytes_per_sample = bits_per_sample / 8; stream->channel_count = channel_count; @@ -304,7 +335,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, int count = Pa_GetDeviceCount(); for (index=0; indexmaxOutputChannels > 0) + if (paDevInfo->maxOutputChannels >= (int)channel_count) break; } if (index == count) { @@ -334,12 +365,12 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, stream = pj_pool_calloc(pool, 1, sizeof(*stream)); stream->pool = pool; - stream->name = pj_str("player"); + pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); + stream->dir = stream->dir = PJMEDIA_DIR_PLAYBACK; stream->user_data = user_data; stream->samples_per_sec = samples_per_frame; stream->bytes_per_sample = bits_per_sample / 8; stream->channel_count = channel_count; - stream->dev_index = index; stream->play_cb = play_cb; pj_memset(&outputParam, 0, sizeof(outputParam)); @@ -373,6 +404,131 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, } +/* + * Open both player and recorder. + */ +PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_snd_stream *stream; + PaStreamParameters inputParam; + PaStreamParameters outputParam; + int sampleFormat; + const PaDeviceInfo *paRecDevInfo = NULL; + const PaDeviceInfo *paPlayDevInfo = NULL; + unsigned paFrames; + PaError err; + + if (rec_id == -1) { + int count = Pa_GetDeviceCount(); + for (rec_id=0; rec_idmaxInputChannels >= (int)channel_count) + break; + } + if (rec_id == count) { + /* No such device. */ + return PJMEDIA_ENOSNDREC; + } + } else { + paRecDevInfo = Pa_GetDeviceInfo(rec_id); + if (!paRecDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_ESNDINDEVID; + } + } + + if (play_id == -1) { + int count = Pa_GetDeviceCount(); + for (play_id=0; play_idmaxOutputChannels >= (int)channel_count) + break; + } + if (play_id == count) { + /* No such device. */ + return PJMEDIA_ENOSNDPLAY; + } + } else { + paPlayDevInfo = Pa_GetDeviceInfo(play_id); + if (!paPlayDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_ESNDINDEVID; + } + } + + if (bits_per_sample == 8) + sampleFormat = paUInt8; + else if (bits_per_sample == 16) + sampleFormat = paInt16; + else if (bits_per_sample == 32) + sampleFormat = paInt32; + else + return PJMEDIA_ESNDINSAMPLEFMT; + + pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + stream = pj_pool_zalloc(pool, sizeof(*stream)); + stream->pool = pool; + pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name); + stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + stream->user_data = user_data; + stream->samples_per_sec = samples_per_frame; + stream->bytes_per_sample = bits_per_sample / 8; + stream->channel_count = channel_count; + stream->rec_cb = rec_cb; + stream->play_cb = play_cb; + + pj_memset(&inputParam, 0, sizeof(inputParam)); + inputParam.device = rec_id; + inputParam.channelCount = channel_count; + inputParam.hostApiSpecificStreamInfo = NULL; + inputParam.sampleFormat = sampleFormat; + inputParam.suggestedLatency = paRecDevInfo->defaultLowInputLatency; + + pj_memset(&outputParam, 0, sizeof(outputParam)); + outputParam.device = play_id; + outputParam.channelCount = channel_count; + outputParam.hostApiSpecificStreamInfo = NULL; + outputParam.sampleFormat = sampleFormat; + outputParam.suggestedLatency = paPlayDevInfo->defaultLowInputLatency; + + /* Frames in PortAudio is number of samples in a single channel */ + paFrames = samples_per_frame / channel_count; + + err = Pa_OpenStream( &stream->stream, &inputParam, &outputParam, + clock_rate, paFrames, + paClipOff, &PaRecorderPlayerCallback, stream ); + if (err != paNoError) { + pj_pool_release(pool); + return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); + } + + PJ_LOG(5,(THIS_FILE, "%s opening device %s/%s for recording and playback, " + "sample rate=%d, channel count=%d, " + "%d bits per sample, %d samples per buffer", + (err==0 ? "Success" : "Error"), + paRecDevInfo->name, paPlayDevInfo->name, + clock_rate, channel_count, + bits_per_sample, samples_per_frame)); + + *p_snd_strm = stream; + + + return PJ_SUCCESS; +} + /* * Start stream. */ @@ -397,7 +553,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) int i, err; stream->quit_flag = 1; - for (i=0; !stream->thread_has_exited && i<100; ++i) + for (i=0; !stream->thread_exited && i<100; ++i) pj_thread_sleep(10); pj_thread_sleep(1); @@ -417,18 +573,15 @@ PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) { int i, err; - const PaDeviceInfo *paDevInfo; stream->quit_flag = 1; - for (i=0; !stream->thread_has_exited && i<100; ++i) - pj_thread_sleep(10); - - pj_thread_sleep(1); - - paDevInfo = Pa_GetDeviceInfo(stream->dev_index); + for (i=0; !stream->thread_exited && i<100; ++i) { + pj_thread_sleep(1); + } - PJ_LOG(5,(THIS_FILE, "Closing %s: %lu underflow, %lu overflow", - paDevInfo->name, + PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow", + (int)stream->name.slen, + stream->name.ptr, stream->underflow, stream->overflow)); err = Pa_CloseStream(stream->stream); diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index f3e285a2..aca74e16 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -23,7 +23,8 @@ struct pjmedia_snd_port { - int snd_index; + int rec_id; + int play_id; pjmedia_snd_stream *snd_stream; pjmedia_dir dir; pjmedia_port *port; @@ -123,17 +124,18 @@ static pj_status_t start_sound_device( pjmedia_snd_port *snd_port ) return PJ_SUCCESS; /* Open sound stream. */ - if (snd_port->dir == PJMEDIA_DIR_ENCODING) { - status = pjmedia_snd_open_recorder( snd_port->snd_index, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &rec_cb, - snd_port, - &snd_port->snd_stream); - } else { - status = pjmedia_snd_open_player( snd_port->snd_index, + if (snd_port->dir == PJMEDIA_DIR_CAPTURE) { + status = pjmedia_snd_open_rec( snd_port->rec_id, + snd_port->clock_rate, + snd_port->channel_count, + snd_port->samples_per_frame, + snd_port->bits_per_sample, + &rec_cb, + snd_port, + &snd_port->snd_stream); + + } else if (snd_port->dir == PJMEDIA_DIR_PLAYBACK) { + status = pjmedia_snd_open_player( snd_port->play_id, snd_port->clock_rate, snd_port->channel_count, snd_port->samples_per_frame, @@ -141,6 +143,21 @@ static pj_status_t start_sound_device( pjmedia_snd_port *snd_port ) &play_cb, snd_port, &snd_port->snd_stream); + + } else if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + status = pjmedia_snd_open( snd_port->rec_id, + snd_port->play_id, + snd_port->clock_rate, + snd_port->channel_count, + snd_port->samples_per_frame, + snd_port->bits_per_sample, + &rec_cb, + &play_cb, + snd_port, + &snd_port->snd_stream); + } else { + pj_assert(!"Invalid dir"); + status = PJ_EBUG; } if (status != PJ_SUCCESS) @@ -175,11 +192,50 @@ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) } +/* + * Create bidirectional port. + */ +PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, + int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + pjmedia_snd_port **p_port) +{ + pjmedia_snd_port *snd_port; + + PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + snd_port = pj_pool_zalloc(pool, sizeof(pjmedia_snd_port)); + PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + + snd_port->rec_id = rec_id; + snd_port->play_id = play_id; + snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + snd_port->clock_rate = clock_rate; + snd_port->channel_count = channel_count; + snd_port->samples_per_frame = samples_per_frame; + snd_port->bits_per_sample = bits_per_sample; + + *p_port = snd_port; + + /* Start sound device immediately. + * If there's no port connected, the sound callback will return + * empty signal. + */ + return start_sound_device( snd_port ); + +} + /* * Create sound recorder port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, - int index, + int dev_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, @@ -195,8 +251,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, snd_port = pj_pool_zalloc(pool, sizeof(pjmedia_snd_port)); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); - snd_port->snd_index = index; - snd_port->dir = PJMEDIA_DIR_ENCODING; + snd_port->rec_id = dev_id; + snd_port->dir = PJMEDIA_DIR_CAPTURE; snd_port->clock_rate = clock_rate; snd_port->channel_count = channel_count; snd_port->samples_per_frame = samples_per_frame; @@ -216,7 +272,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, * Create sound player port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool, - int index, + int dev_id, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, @@ -232,8 +288,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool, snd_port = pj_pool_zalloc(pool, sizeof(pjmedia_snd_port)); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); - snd_port->snd_index = index; - snd_port->dir = PJMEDIA_DIR_DECODING; + snd_port->play_id = dev_id; + snd_port->dir = PJMEDIA_DIR_PLAYBACK; snd_port->clock_rate = clock_rate; snd_port->channel_count = channel_count; snd_port->samples_per_frame = samples_per_frame; @@ -260,6 +316,17 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) } +/* + * Retrieve the sound stream associated by this sound device port. + */ +PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream( + pjmedia_snd_port *snd_port) +{ + PJ_ASSERT_RETURN(snd_port, NULL); + return snd_port->snd_stream; +} + + /* * Connect a port. */ -- cgit v1.2.3