📄 pasound.c
字号:
/* $Id: pasound.c 974 2007-02-19 01:13:53Z bennylp $ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/sound.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/string.h>
#include <portaudio.h>
#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND
#define THIS_FILE "pasound.c"
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;
pjmedia_dir dir;
int play_id;
int rec_id;
int bytes_per_sample;
pj_uint32_t samples_per_sec;
unsigned samples_per_frame;
int channel_count;
PaStream *rec_strm;
PaStream *play_strm;
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 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;
};
static int PaRecorderCallback(const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
pjmedia_snd_stream *stream = userData;
pj_status_t status;
PJ_UNUSED_ARG(output);
PJ_UNUSED_ARG(timeInfo);
if (stream->quit_flag)
goto on_break;
if (input == NULL)
return paContinue;
if (stream->rec_thread_initialized == 0) {
status = pj_thread_register("pa_rec", stream->rec_thread_desc,
&stream->rec_thread);
stream->rec_thread_initialized = 1;
PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
}
if (statusFlags & paInputUnderflow)
++stream->underflow;
if (statusFlags & paInputOverflow)
++stream->overflow;
stream->timestamp += frameCount;
status = (*stream->rec_cb)(stream->user_data, stream->timestamp,
(void*)input,
frameCount * stream->bytes_per_sample *
stream->channel_count);
if (status==0)
return paContinue;
on_break:
stream->rec_thread_exited = 1;
return paAbort;
}
static int PaPlayerCallback( const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
pjmedia_snd_stream *stream = userData;
pj_status_t status;
unsigned size = frameCount * stream->bytes_per_sample *
stream->channel_count;
PJ_UNUSED_ARG(input);
PJ_UNUSED_ARG(timeInfo);
if (stream->quit_flag)
goto on_break;
if (output == NULL)
return paContinue;
if (stream->play_thread_initialized == 0) {
status = pj_thread_register("portaudio", stream->play_thread_desc,
&stream->play_thread);
stream->play_thread_initialized = 1;
PJ_LOG(5,(THIS_FILE, "Player thread started"));
}
if (statusFlags & paOutputUnderflow)
++stream->underflow;
if (statusFlags & paOutputOverflow)
++stream->overflow;
stream->timestamp += frameCount;
status = (*stream->play_cb)(stream->user_data, stream->timestamp,
output, size);
if (status==0)
return paContinue;
on_break:
stream->play_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.
*/
PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory)
{
int err;
snd_mgr.factory = factory;
err = Pa_Initialize();
PJ_LOG(4,(THIS_FILE, "PortAudio sound library initialized, status=%d", err));
PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
Pa_GetHostApiCount()));
PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
pjmedia_snd_get_dev_count()));
return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
}
/*
* Get device count.
*/
PJ_DEF(int) pjmedia_snd_get_dev_count(void)
{
return Pa_GetDeviceCount();
}
/*
* Get device info.
*/
PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index)
{
static pjmedia_snd_dev_info info;
const PaDeviceInfo *pa_info;
pa_info = Pa_GetDeviceInfo(index);
if (!pa_info)
return NULL;
pj_bzero(&info, sizeof(info));
strncpy(info.name, pa_info->name, sizeof(info.name));
info.name[sizeof(info.name)-1] = '\0';
info.input_count = pa_info->maxInputChannels;
info.output_count = pa_info->maxOutputChannels;
info.default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
return &info;
}
/* Get PortAudio default input device ID */
static int pa_get_default_input_dev(int channel_count)
{
int i, count;
/* Enumerate the host api's for the default devices, and return
* the device with suitable channels.
*/
count = Pa_GetHostApiCount();
for (i=0; i < count; ++i) {
const PaHostApiInfo *pHAInfo;
pHAInfo = Pa_GetHostApiInfo(i);
if (!pHAInfo)
continue;
if (pHAInfo->defaultInputDevice >= 0) {
const PaDeviceInfo *paDevInfo;
paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
if (paDevInfo->maxInputChannels >= channel_count)
return pHAInfo->defaultInputDevice;
}
}
/* If still no device is found, enumerate all devices */
count = Pa_GetDeviceCount();
for (i=0; i<count; ++i) {
const PaDeviceInfo *paDevInfo;
paDevInfo = Pa_GetDeviceInfo(i);
if (paDevInfo->maxInputChannels >= channel_count)
return i;
}
return -1;
}
/* Get PortAudio default output device ID */
static int pa_get_default_output_dev(int channel_count)
{
int i, count;
/* Enumerate the host api's for the default devices, and return
* the device with suitable channels.
*/
count = Pa_GetHostApiCount();
for (i=0; i < count; ++i) {
const PaHostApiInfo *pHAInfo;
pHAInfo = Pa_GetHostApiInfo(i);
if (!pHAInfo)
continue;
if (pHAInfo->defaultOutputDevice >= 0) {
const PaDeviceInfo *paDevInfo;
paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
if (paDevInfo->maxOutputChannels >= channel_count)
return pHAInfo->defaultOutputDevice;
}
}
/* If still no device is found, enumerate all devices */
count = Pa_GetDeviceCount();
for (i=0; i<count; ++i) {
const PaDeviceInfo *paDevInfo;
paDevInfo = Pa_GetDeviceInfo(i);
if (paDevInfo->maxOutputChannels >= channel_count)
return i;
}
return -1;
}
/*
* Open stream.
*/
PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index,
unsigned clock_rate,
unsigned channel_count,
unsigned samples_per_frame,
unsigned bits_per_sample,
pjmedia_snd_rec_cb rec_cb,
void *user_data,
pjmedia_snd_stream **p_snd_strm)
{
pj_pool_t *pool;
pjmedia_snd_stream *stream;
PaStreamParameters inputParam;
int sampleFormat;
const PaDeviceInfo *paDevInfo = NULL;
const PaHostApiInfo *paHostApiInfo = NULL;
unsigned paFrames, paRate, paLatency;
const PaStreamInfo *paSI;
PaError err;
if (index < 0) {
index = pa_get_default_input_dev(channel_count);
if (index < 0) {
/* No such device. */
return PJMEDIA_ENOSNDREC;
}
}
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_zalloc(pool, sizeof(*stream));
stream->pool = pool;
pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
stream->dir = PJMEDIA_DIR_CAPTURE;
stream->rec_id = index;
stream->play_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->rec_cb = rec_cb;
pj_bzero(&inputParam, sizeof(inputParam));
inputParam.device = index;
inputParam.channelCount = channel_count;
inputParam.hostApiSpecificStreamInfo = NULL;
inputParam.sampleFormat = sampleFormat;
inputParam.suggestedLatency = paDevInfo->defaultLowInputLatency;
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->rec_strm, &inputParam, NULL,
clock_rate, paFrames,
paClipOff, &PaRecorderCallback, stream );
if (err != paNoError) {
pj_pool_release(pool);
return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
}
paSI = Pa_GetStreamInfo(stream->rec_strm);
paRate = (unsigned)paSI->sampleRate;
paLatency = (unsigned)(paSI->inputLatency * 1000);
PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
"rate=%d, ch=%d, "
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -