/* Internal: Create both player and recorder stream */ static pj_status_t create_bidir_stream(struct pa_aud_factory *pa, const pjmedia_aud_param *param, pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_snd_strm) { pj_pool_t *pool; pjmedia_aud_dev_index rec_id, play_id; struct pa_aud_stream *stream; PaStream *paStream = NULL; PaStreamParameters inputParam; PaStreamParameters outputParam; int sampleFormat; const PaDeviceInfo *paRecDevInfo = NULL; const PaDeviceInfo *paPlayDevInfo = NULL; const PaHostApiInfo *paRecHostApiInfo = NULL; const PaHostApiInfo *paPlayHostApiInfo = NULL; const PaStreamInfo *paSI; unsigned paFrames, paRate, paInputLatency, paOutputLatency; PaError err; PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL); rec_id = param->rec_id; if (rec_id < 0) { rec_id = pa_get_default_input_dev(param->channel_count); if (rec_id < 0) { /* No such device. */ return PJMEDIA_EAUD_NODEFDEV; } } paRecDevInfo = Pa_GetDeviceInfo(rec_id); if (!paRecDevInfo) { /* Assumed it is "No such device" error. */ return PJMEDIA_EAUD_INVDEV; } play_id = param->play_id; if (play_id < 0) { play_id = pa_get_default_output_dev(param->channel_count); if (play_id < 0) { /* No such device. */ return PJMEDIA_EAUD_NODEFDEV; } } paPlayDevInfo = Pa_GetDeviceInfo(play_id); if (!paPlayDevInfo) { /* Assumed it is "No such device" error. */ return PJMEDIA_EAUD_INVDEV; } if (param->bits_per_sample == 8) sampleFormat = paUInt8; else if (param->bits_per_sample == 16) sampleFormat = paInt16; else if (param->bits_per_sample == 32) sampleFormat = paInt32; else return PJMEDIA_EAUD_SAMPFORMAT; pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL); if (!pool) return PJ_ENOMEM; stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream); stream->pool = pool; pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name); stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; stream->play_id = play_id; stream->rec_id = rec_id; stream->user_data = user_data; stream->samples_per_sec = param->clock_rate; stream->samples_per_frame = param->samples_per_frame; stream->bytes_per_sample = param->bits_per_sample / 8; stream->channel_count = param->channel_count; stream->rec_cb = rec_cb; stream->play_cb = play_cb; stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, stream->samples_per_frame * stream->bytes_per_sample); stream->rec_buf_count = 0; stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, stream->samples_per_frame * stream->bytes_per_sample); stream->play_buf_count = 0; pj_bzero(&inputParam, sizeof(inputParam)); inputParam.device = rec_id; inputParam.channelCount = param->channel_count; inputParam.hostApiSpecificStreamInfo = NULL; inputParam.sampleFormat = sampleFormat; if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) inputParam.suggestedLatency = param->input_latency_ms / 1000.0; else inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0; paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi); pj_bzero(&outputParam, sizeof(outputParam)); outputParam.device = play_id; outputParam.channelCount = param->channel_count; outputParam.hostApiSpecificStreamInfo = NULL; outputParam.sampleFormat = sampleFormat; if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) outputParam.suggestedLatency=param->output_latency_ms / 1000.0; else outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0; paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi); /* Frames in PortAudio is number of samples in a single channel */ paFrames = param->samples_per_frame / param->channel_count; /* If both input and output are on the same device, open a single stream * for both input and output. */ if (rec_id == play_id) { err = Pa_OpenStream( &paStream, &inputParam, &outputParam, param->clock_rate, paFrames, paClipOff, &PaRecorderPlayerCallback, stream ); if (err == paNoError) { /* Set play stream and record stream to the same stream */ stream->play_strm = stream->rec_strm = paStream; } } else { err = -1; } /* .. otherwise if input and output are on the same device, OR if we're * unable to open a bidirectional stream, then open two separate * input and output stream. */ if (paStream == NULL) { /* Open input stream */ err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL, param->clock_rate, paFrames, paClipOff, &PaRecorderCallback, stream ); if (err == paNoError) { /* Open output stream */ err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, param->clock_rate, paFrames, paClipOff, &PaPlayerCallback, stream ); if (err != paNoError) Pa_CloseStream(stream->rec_strm); } } if (err != paNoError) { pj_pool_release(pool); return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err); } paSI = Pa_GetStreamInfo(stream->rec_strm); paRate = (unsigned)(paSI->sampleRate); paInputLatency = (unsigned)(paSI->inputLatency * 1000); paSI = Pa_GetStreamInfo(stream->play_strm); paOutputLatency = (unsigned)(paSI->outputLatency * 1000); PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and " "playback, sample rate=%d, ch=%d, " "bits=%d, %d samples per frame, input latency=%d ms, " "output latency=%d ms", paRecDevInfo->name, paRecHostApiInfo->name, paPlayDevInfo->name, paPlayHostApiInfo->name, paRate, param->channel_count, param->bits_per_sample, param->samples_per_frame, paInputLatency, paOutputLatency)); *p_snd_strm = &stream->base; return PJ_SUCCESS; }
/* * Open both player and recorder. */ PJ_DEF(pj_status_t) pjmedia_snd_open( int *prec_id, int *pplay_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; PaStream *paStream = NULL; PaStreamParameters inputParam; PaStreamParameters outputParam; int sampleFormat; const PaDeviceInfo *paRecDevInfo = NULL; const PaDeviceInfo *paPlayDevInfo = NULL; const PaHostApiInfo *paRecHostApiInfo = NULL; const PaHostApiInfo *paPlayHostApiInfo = NULL; const PaStreamInfo *paSI; unsigned paFrames, paRate, paInputLatency, paOutputLatency; PaError err; if (*prec_id < 0) { *prec_id = pa_get_default_input_dev(channel_count); if (*prec_id < 0) { /* No such device. */ return PJMEDIA_ENOSNDREC; } } int rec_id=*prec_id; paRecDevInfo = Pa_GetDeviceInfo(rec_id); if (!paRecDevInfo) { /* Assumed it is "No such device" error. */ return PJMEDIA_ESNDINDEVID; } if (*pplay_id < 0) { *pplay_id = pa_get_default_output_dev(channel_count); if (*pplay_id < 0) { /* No such device. */ return PJMEDIA_ENOSNDPLAY; } } int play_id=*pplay_id; 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->play_id = play_id; stream->rec_id = rec_id; stream->user_data = user_data; stream->samples_per_sec = clock_rate; stream->samples_per_frame = 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_bzero(&inputParam, sizeof(inputParam)); inputParam.device = rec_id; inputParam.channelCount = channel_count; inputParam.hostApiSpecificStreamInfo = NULL; inputParam.sampleFormat = sampleFormat; inputParam.suggestedLatency = paRecDevInfo->defaultLowInputLatency; paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi); pj_bzero(&outputParam, sizeof(outputParam)); outputParam.device = play_id; outputParam.channelCount = channel_count; outputParam.hostApiSpecificStreamInfo = NULL; outputParam.sampleFormat = sampleFormat; outputParam.suggestedLatency = paPlayDevInfo->defaultLowOutputLatency; paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi); /* Frames in PortAudio is number of samples in a single channel */ paFrames = samples_per_frame / channel_count; /* If both input and output are on the same device, open a single stream * for both input and output. */ if (rec_id == play_id) { err = Pa_OpenStream( &paStream, &inputParam, &outputParam, clock_rate, paFrames, paClipOff, &PaRecorderPlayerCallback, stream ); if (err == paNoError) { /* Set play stream and record stream to the same stream */ stream->play_strm = stream->rec_strm = paStream; } } else { err = -1; } /* .. otherwise if input and output are on the same device, OR if we're * unable to open a bidirectional stream, then open two separate * input and output stream. */ if (paStream == NULL) { /* Open input stream */ err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL, clock_rate, paFrames, paClipOff, &PaRecorderCallback, stream ); if (err == paNoError) { /* Open output stream */ err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, clock_rate, paFrames, paClipOff, &PaPlayerCallback, stream ); if (err != paNoError) Pa_CloseStream(stream->rec_strm); } } if (err != paNoError) { pj_pool_release(pool); return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); } paSI = Pa_GetStreamInfo(stream->rec_strm); paRate = (unsigned)(paSI->sampleRate); paInputLatency = (unsigned)(paSI->inputLatency * 1000); paSI = Pa_GetStreamInfo(stream->play_strm); paOutputLatency = (unsigned)(paSI->outputLatency * 1000); PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and " "playback, sample rate=%d, ch=%d, " "bits=%d, %d samples per frame, input latency=%d ms, " "output latency=%d ms", paRecDevInfo->name, paRecHostApiInfo->name, paPlayDevInfo->name, paPlayHostApiInfo->name, paRate, channel_count, bits_per_sample, samples_per_frame, paInputLatency, paOutputLatency)); *p_snd_strm = stream; return PJ_SUCCESS; }
/* Internal: create playback stream */ static pj_status_t create_play_stream(struct pa_aud_factory *pa, const pjmedia_aud_param *param, pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_snd_strm) { pj_pool_t *pool; pjmedia_aud_dev_index play_id; struct pa_aud_stream *stream; PaStreamParameters outputParam; int sampleFormat; const PaDeviceInfo *paDevInfo = NULL; const PaHostApiInfo *paHostApiInfo = NULL; const PaStreamInfo *paSI; unsigned paFrames, paRate, paLatency; PaError err; PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL); play_id = param->play_id; if (play_id < 0) { play_id = pa_get_default_output_dev(param->channel_count); if (play_id < 0) { /* No such device. */ return PJMEDIA_EAUD_NODEFDEV; } } paDevInfo = Pa_GetDeviceInfo(play_id); if (!paDevInfo) { /* Assumed it is "No such device" error. */ return PJMEDIA_EAUD_INVDEV; } if (param->bits_per_sample == 8) sampleFormat = paUInt8; else if (param->bits_per_sample == 16) sampleFormat = paInt16; else if (param->bits_per_sample == 32) sampleFormat = paInt32; else return PJMEDIA_EAUD_SAMPFORMAT; pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL); if (!pool) return PJ_ENOMEM; stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream); stream->pool = pool; pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); stream->dir = PJMEDIA_DIR_PLAYBACK; stream->play_id = play_id; stream->rec_id = -1; stream->user_data = user_data; stream->samples_per_sec = param->clock_rate; stream->samples_per_frame = param->samples_per_frame; stream->bytes_per_sample = param->bits_per_sample / 8; stream->channel_count = param->channel_count; stream->play_cb = play_cb; stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, stream->samples_per_frame * stream->bytes_per_sample); stream->play_buf_count = 0; pj_bzero(&outputParam, sizeof(outputParam)); outputParam.device = play_id; outputParam.channelCount = param->channel_count; outputParam.hostApiSpecificStreamInfo = NULL; outputParam.sampleFormat = sampleFormat; if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) outputParam.suggestedLatency=param->output_latency_ms / 1000.0; else outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0; paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); /* Frames in PortAudio is number of samples in a single channel */ paFrames = param->samples_per_frame / param->channel_count; err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, param->clock_rate, paFrames, paClipOff, &PaPlayerCallback, stream ); if (err != paNoError) { pj_pool_release(pool); return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err); } paSI = Pa_GetStreamInfo(stream->play_strm); paRate = (unsigned)(paSI->sampleRate); paLatency = (unsigned)(paSI->outputLatency * 1000); PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d" ", ch=%d, " "bits=%d, %d samples per frame, latency=%d ms", play_id, paDevInfo->name, paHostApiInfo->name, paRate, param->channel_count, param->bits_per_sample, param->samples_per_frame, paLatency)); *p_snd_strm = &stream->base; return PJ_SUCCESS; }
PJ_DEF(pj_status_t) pjmedia_snd_open_player( int *pindex, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjmedia_snd_play_cb play_cb, void *user_data, pjmedia_snd_stream **p_snd_strm) { pj_pool_t *pool; pjmedia_snd_stream *stream; PaStreamParameters outputParam; int sampleFormat; const PaDeviceInfo *paDevInfo = NULL; const PaHostApiInfo *paHostApiInfo = NULL; const PaStreamInfo *paSI; unsigned paFrames, paRate, paLatency; PaError err; if (*pindex < 0) { *pindex = pa_get_default_output_dev(channel_count); if (*pindex < 0) { /* No such device. */ return PJMEDIA_ENOSNDPLAY; } } int index=*pindex; paDevInfo = Pa_GetDeviceInfo(index); if (!paDevInfo) { /* 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_calloc(pool, 1, sizeof(*stream)); stream->pool = pool; pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); stream->dir = stream->dir = PJMEDIA_DIR_PLAYBACK; stream->play_id = index; stream->rec_id = -1; stream->user_data = user_data; stream->samples_per_sec = clock_rate; stream->samples_per_frame = samples_per_frame; stream->bytes_per_sample = bits_per_sample / 8; stream->channel_count = channel_count; stream->play_cb = play_cb; pj_bzero(&outputParam, sizeof(outputParam)); outputParam.device = index; outputParam.channelCount = channel_count; outputParam.hostApiSpecificStreamInfo = NULL; outputParam.sampleFormat = sampleFormat; outputParam.suggestedLatency = 1.0 * samples_per_frame / clock_rate;; paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); /* Frames in PortAudio is number of samples in a single channel */ paFrames = samples_per_frame / channel_count; err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, clock_rate, paFrames, paClipOff, &PaPlayerCallback, stream ); if (err != paNoError) { pj_pool_release(pool); return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); } paSI = Pa_GetStreamInfo(stream->play_strm); paRate = (unsigned)(paSI->sampleRate); paLatency = (unsigned)(paSI->outputLatency * 1000); PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d" ", ch=%d, " "bits=%d, %d samples per frame, latency=%d ms", index, paDevInfo->name, paHostApiInfo->name, paRate, channel_count, bits_per_sample, samples_per_frame, paLatency)); *p_snd_strm = stream; return PJ_SUCCESS; }