Hi, some time ago I posted a patch that added audio put and get frame callback to PJSUA, even to the Python wrapper. It suffered from deadlocks and occasional crashes (especially with more calls) so yesterday I posted a new version that behaves better, but still deadlocks or crashes might occur. I revised the idea of accessing audio frames at a high level and created file descriptor media port available in PJSUA, Python_PJSUA and PJSUA2, see attached patch. Key features: * It works similarly to audio recorder or player. * It works with raw data -- no headers or format handling. * It can read/write from/to a file descriptor or Windows file handle. * File descriptor may be virtually everything: file, pipe, socket etc. * On UNIX it can operate either in blocking mode or in non-blocking mode with buffering. * It can be used to communicate the data with the same or different process. Feel free to use it if you find it useful. Examples are in pjsip_apps/src/python/samples/audio_fd.py and pjsip_apps/src/swig/python/audio_fd.py. Benny or other authors, do you think it can be integrated to the trunk? I would be helpful and grateful. Cheers, - Vali -------------- next part -------------- Index: pjmedia/build/Makefile =================================================================== --- pjmedia/build/Makefile (revision 4770) +++ pjmedia/build/Makefile (working copy) @@ -59,7 +59,7 @@ export PJMEDIA_SRCDIR = ../src/pjmedia export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ alaw_ulaw.o alaw_ulaw_table.o avi_player.o \ - bidirectional.o clock_thread.o codec.o conference.o \ + bidirectional.o fd_port.o clock_thread.o codec.o conference.o \ conf_switch.o converter.o converter_libswscale.o \ delaybuf.o echo_common.o \ echo_port.o echo_suppress.o endpoint.o errno.o \ Index: pjmedia/build/pjmedia.vcproj =================================================================== --- pjmedia/build/pjmedia.vcproj (revision 4770) +++ pjmedia/build/pjmedia.vcproj (working copy) @@ -3455,6 +3455,10 @@ </FileConfiguration> </File> <File + RelativePath="..\src\pjmedia\fd_port.c" + > + </File> + <File RelativePath="..\src\pjmedia\clock_thread.c" > <FileConfiguration @@ -7276,6 +7280,10 @@ > </File> <File + RelativePath="..\include\pjmedia\fd_port.h" + > + </File> + <File RelativePath="..\include\pjmedia\bidirectional.h" > </File> Index: pjmedia/include/pjmedia.h =================================================================== --- pjmedia/include/pjmedia.h (revision 4770) +++ pjmedia/include/pjmedia.h (working copy) @@ -27,6 +27,7 @@ #include <pjmedia/alaw_ulaw.h> #include <pjmedia/avi_stream.h> #include <pjmedia/bidirectional.h> +#include <pjmedia/fd_port.h> #include <pjmedia/circbuf.h> #include <pjmedia/clock.h> #include <pjmedia/codec.h> Index: pjmedia/include/pjmedia/fd_port.h =================================================================== --- pjmedia/include/pjmedia/fd_port.h (revision 0) +++ pjmedia/include/pjmedia/fd_port.h (working copy) @@ -0,0 +1,113 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny at 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 + */ +#ifndef __PJMEDIA_FD_PORT_H__ +#define __PJMEDIA_FD_PORT_H__ + +/** + * @file fd_port.h + * @brief File descriptor media port. + */ +#include <pjmedia/port.h> + + + +/** + * @defgroup PJMEDIA_FD_PORT File Descriptor Port + * @ingroup PJMEDIA_PORT + * @brief Reads from or writes audio frames to a file descriptor. + * @{ + * + * File descriptor port profides a simple way to pass audio frames + * to/from an open file descriptor (a file, socket, pipe etc.) + * which can be the same or a different process + * (e.g. a speech recognizer or synthesizer). + * + * It can operate either in blocking or non-blocking mode. In blocking + * mode the user must make sure the I/O operations complete quicky, + * in non-blocking mode the data is buffered automatically. + */ + + +PJ_BEGIN_DECL + +/** + * File descriptor media port options. + */ +enum pjmedia_file_descriptor_option +{ + /** + * Use file descriptors in blocking mode. + */ + PJMEDIA_FD_BLOCK = 0, + + /** + * Use file-descriptors in non-blocking mode and buffer data. + */ + PJMEDIA_FD_NONBLOCK = 1, + + /** + * Parameters fd_in and fd_out are Windows file handles + * instead of integer file descriptors (Windows only). + */ + PJMEDIA_FD_HANDLES = 2 +}; + + +/** + * Create file descriptor media port. + * + * @param pool Pool to allocate memory. + * @param sampling_rate Sampling rate of the port. + * @param channel_count Number of channels. + * @param samples_per_frame Number of samples per frame. + * @param bits_per_sample Number of bits per sample. + * @param fd_in Descriptor open for reading (i.e. audio source) + * or -1 to disable audio input. + * @param fd_out Descriptor open for writing (i.e. audio sink) + * or -1 to disable audio output. + * @param flags 0 or PJMEDIA_FD_BLOCK for default, i.e. blocking mode, + * or ORed pjmedia_file_descriptor_option flags: + * - PJMEDIA_FD_NONBLOCK (1) for non-blocking mode + * (O_NONBLOCK is fcntl'ed on the descriptors if specified). + * - PJMEDIA_FD_HANDLES (2): Parameters fd_in and fd_out are Windows + * file handles instead of integer file descriptors (Windows only). + * @param p_port Pointer to receive the port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_fd_port_create( pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + int fd_in, + int fd_out, + unsigned flags, + pjmedia_port **p_port ); + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_FD_PORT_H__ */ Property changes on: pjmedia/include/pjmedia/fd_port.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Id \ No newline at end of property Index: pjmedia/include/pjmedia/signatures.h =================================================================== --- pjmedia/include/pjmedia/signatures.h (revision 4770) +++ pjmedia/include/pjmedia/signatures.h (working copy) @@ -147,6 +147,7 @@ #define PJMEDIA_SIG_IS_CLASS_PORT_AUD(s) ((s)>>24=='P' && (s)>>16=='A') #define PJMEDIA_SIG_PORT_BIDIR PJMEDIA_SIG_CLASS_PORT_AUD('B','D') +#define PJMEDIA_SIG_PORT_FD PJMEDIA_SIG_CLASS_PORT_AUD('F','D') #define PJMEDIA_SIG_PORT_CONF PJMEDIA_SIG_CLASS_PORT_AUD('C','F') #define PJMEDIA_SIG_PORT_CONF_PASV PJMEDIA_SIG_CLASS_PORT_AUD('C','P') #define PJMEDIA_SIG_PORT_CONF_SWITCH PJMEDIA_SIG_CLASS_PORT_AUD('C','S') Index: pjmedia/src/pjmedia/fd_port.c =================================================================== --- pjmedia/src/pjmedia/fd_port.c (revision 0) +++ pjmedia/src/pjmedia/fd_port.c (working copy) @@ -0,0 +1,383 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny at 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/fd_port.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> + +#if PJ_WIN32 +# define WIN32_LEAN_AND_MEAN +# include <Windows.h> +# include <io.h> +#else +# include <unistd.h> +# include <fcntl.h> +# include <errno.h> +#endif + +#define SIGNATURE PJMEDIA_SIG_PORT_FD + +#ifdef O_NONBLOCK + +/** Buffer size */ +#define FD_BUF_SIZE 131072U /* 128 KiB */ + +/** Audio data cyclic buffer */ +typedef struct fd_buf_t { + /** + * Position in the buffer to write next data to. + * Position to read data from is ((FD_BUF_SIZE + pos - len) % FD_BUF_SIZE). + */ + pj_size_t pos; + + /** Bytes stored in the buffer. */ + pj_size_t len; + char data[FD_BUF_SIZE]; +} fd_buf_t; + +#endif /* O_NONBLOCK */ + +/** File descriptor port implementation. */ +struct fd_port { + pjmedia_port base; + + int fd_in; + int fd_out; +#ifdef O_NONBLOCK + fd_buf_t *buf_in; + fd_buf_t *buf_out; +#endif /* O_NONBLOCK */ + pj_timestamp timestamp; +}; + +/** Get frame (blocking mode) */ +static pj_status_t fd_port_get_frame_b(pjmedia_port *this_port, + pjmedia_frame *frame); +/** Put frame (blocking mode) */ +static pj_status_t fd_port_put_frame_b(pjmedia_port *this_port, + pjmedia_frame *frame); + +#ifdef O_NONBLOCK +/** Get frame (non-blocking mode) */ +static pj_status_t fd_port_get_frame_nb(pjmedia_port *this_port, + pjmedia_frame *frame); +/** Put frame (non-blocking mode) */ +static pj_status_t fd_port_put_frame_nb(pjmedia_port *this_port, + pjmedia_frame *frame); +#endif /* O_NONBLOCK */ + +/** Destroy and free buffers */ +static pj_status_t fd_port_on_destroy(pjmedia_port *this_port); + + +PJ_DEF(pj_status_t) pjmedia_fd_port_create( pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + int fd_in, + int fd_out, + unsigned flags, + pjmedia_port **p_port ) +{ + struct fd_port *fdport; + const pj_str_t name = pj_str("fd-port"); + + PJ_ASSERT_RETURN(pool && sampling_rate && channel_count && + samples_per_frame && bits_per_sample && p_port && + ((fd_in >= 0) || (fd_out >= 0)), + PJ_EINVAL); +#ifndef O_NONBLOCK + PJ_ASSERT_RETURN(!(flags & PJMEDIA_FD_NONBLOCK), PJ_ENOTSUP); +#endif +#if !PJ_WIN32 + PJ_ASSERT_RETURN(!(flags & PJMEDIA_FD_HANDLES), PJ_EINVAL); +#endif + + fdport = PJ_POOL_ZALLOC_T(pool, struct fd_port); + PJ_ASSERT_RETURN(fdport != NULL, PJ_ENOMEM); + + /* Create the port */ + pjmedia_port_info_init(&fdport->base.info, &name, SIGNATURE, sampling_rate, + channel_count, bits_per_sample, samples_per_frame); + +#if PJ_WIN32 + if (!(flags & PJMEDIA_FD_HANDLES)) + { + /* PJMEDIA_FD_UNUSED should be equal to INVALID_HANLDE_VALUE */ + if (fd_in >= 0) fdport->fd_in = (int)_get_osfhandle(fd_in); + if (fd_out >= 0) fdport->fd_out = (int)_get_osfhandle(fd_out); + } + else +#endif /* PJ_WIN32 */ + { + fdport->fd_in = fd_in; + fdport->fd_out = fd_out; + } + + if (fdport->fd_in >= 0) { +#ifdef O_NONBLOCK + if (flags & PJMEDIA_FD_NONBLOCK) { + int fl = fcntl(fdport->fd_in, F_GETFL); + if (fl < 0) return pj_get_os_error(); + fl |= O_NONBLOCK; + fl = fcntl(fdport->fd_in, F_SETFL, fl); + if (fl < 0) return pj_get_os_error(); + fdport->buf_in = PJ_POOL_ALLOC_T(pool, fd_buf_t); + PJ_ASSERT_RETURN(fdport->buf_in, PJ_ENOMEM); + fdport->buf_in->pos = 0; + fdport->buf_in->len = 0; + fdport->base.get_frame = &fd_port_get_frame_nb; + } + else +#endif /* O_NONBLOCK */ + { + fdport->base.get_frame = &fd_port_get_frame_b; + } + } + + if (fdport->fd_out >= 0) { +#ifdef O_NONBLOCK + if (flags & PJMEDIA_FD_NONBLOCK) { + int fl = fcntl(fdport->fd_out, F_GETFL); + if (fl < 0) return pj_get_os_error(); + fl |= O_NONBLOCK; + fl = fcntl(fdport->fd_out, F_SETFL, fl); + if (fl < 0) return pj_get_os_error(); + fdport->buf_out = PJ_POOL_ALLOC_T(pool, fd_buf_t); + PJ_ASSERT_RETURN(fdport->buf_out, PJ_ENOMEM); + fdport->buf_out->pos = 0; + fdport->buf_out->len = 0; + fdport->base.put_frame = &fd_port_put_frame_nb; + } + else +#endif /* O_NONBLOCK */ + { + fdport->base.put_frame = &fd_port_put_frame_b; + } + } + + fdport->base.on_destroy = &fd_port_on_destroy; + + *p_port = &fdport->base; + + return PJ_SUCCESS; +} + + +static pj_status_t fd_port_put_frame_b(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + const struct fd_port *fdport; + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, + PJ_EINVALIDOP); + + fdport = (struct fd_port*) this_port; + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { +#if PJ_WIN32 + DWORD written; + BOOL succ = WriteFile((HANDLE)(pj_size_t)fdport->fd_out, frame->buf, (DWORD)frame->size, &written, NULL); + if (!succ) return pj_get_os_error(); + if (written != frame->size) return PJ_EUNKNOWN; +#else /* PJ_WIN32 */ + int ret = write(fdport->fd_out, frame->buf, frame->size); + if (ret < 0) return pj_get_os_error(); + if (ret != frame->size) return PJ_EUNKNOWN; +#endif /* PJ_WIN32 else */ + } + + return PJ_SUCCESS; +} + + +static pj_status_t fd_port_get_frame_b(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct fd_port *fdport; + pj_size_t size; +#if PJ_WIN32 + BOOL succ; + DWORD ret; +#else /* PJ_WIN32 */ + int ret; +#endif /* PJ_WIN32 else */ + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, + PJ_EINVALIDOP); + + fdport = (struct fd_port*) this_port; + size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); +#if PJ_WIN32 + succ = ReadFile((HANDLE)(pj_size_t)fdport->fd_in, frame->buf, (DWORD)size, &ret, NULL); + if (!succ) ret = 0; + if (succ) { +#else /* PJ_WIN32 */ + ret = read(fdport->fd_in, frame->buf, size); + if (ret > 0) { +#endif /* PJ_WIN32 else */ + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + frame->size = ret; + /* Is this the correct timestamp calculation? */ + frame->timestamp.u64 = fdport->timestamp.u64; + fdport->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info); + } else { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + return pj_get_os_error(); + } + + return PJ_SUCCESS; +} + + +#ifdef O_NONBLOCK + +/** Assumes buf->len + len <= FD_BUF_SIZE */ +static void fd_buf_append(fd_buf_t *buf, const char *data, pj_size_t len) +{ + pj_size_t sz = len; + if (buf->pos + sz > FD_BUF_SIZE) + sz = FD_BUF_SIZE - buf->pos; + memcpy(buf->data + buf->pos, data, sz); + buf->pos += sz; + buf->len += len; + len -= sz; + if (buf->pos >= FD_BUF_SIZE) + buf->pos = 0; + memcpy(buf->data, data + sz, len); +} + + +static pj_status_t fd_port_put_frame_nb(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct fd_port *fdport; + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, + PJ_EINVALIDOP); + + fdport = (struct fd_port*) this_port; + /* Write out the buffer first */ + while (fdport->buf_out->len) { + int ret; + pj_size_t sz = fdport->buf_out->len; + pj_size_t pos = (FD_BUF_SIZE + fdport->buf_out->pos - sz) % FD_BUF_SIZE; + if (pos + sz > FD_BUF_SIZE) sz = FD_BUF_SIZE - pos; + ret = write(fdport->fd_out, fdport->buf_out->data + pos, sz); + if (ret <= 0) { + if ((ret == 0) || (errno == EAGAIN) || (errno == EWOULDBLOCK)) + break; + return pj_get_os_error(); + } + fdport->buf_out->len -= ret; + } + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if (fdport->buf_out->len) { + /* Append the frame to the buffer */ + if (fdport->buf_out->len + frame->size > FD_BUF_SIZE) + return PJ_ETOOMANY; + fd_buf_append(fdport->buf_out, (char*)frame->buf, frame->size); + } else { + /* Try to write the frame immediately */ + int ret = write(fdport->fd_out, frame->buf, frame->size); + if ((ret < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK)) + return pj_get_os_error(); + if (ret < 0) ret = 0; + if (ret < frame->size) { + /* Buffer the rest */ + if (fdport->buf_out->len + frame->size - ret > FD_BUF_SIZE) + return PJ_ETOOMANY; + fd_buf_append(fdport->buf_out, (char*)frame->buf + ret, frame->size - ret); + } + } + } + + return PJ_SUCCESS; +} + + +static pj_status_t fd_port_get_frame_nb(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct fd_port *fdport; + pj_size_t size; + + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, + PJ_EINVALIDOP); + + fdport = (struct fd_port*) this_port; + /* Buffer as much data first */ + while (fdport->buf_in->len < FD_BUF_SIZE) { + int ret; + pj_size_t sz = FD_BUF_SIZE - fdport->buf_in->len; + if (fdport->buf_in->pos + sz > FD_BUF_SIZE) + sz = FD_BUF_SIZE - fdport->buf_in->pos; + ret = read(fdport->fd_in, + fdport->buf_in->data + fdport->buf_in->pos, sz); + if (ret <= 0) { + if ((ret == 0) || (errno == EAGAIN) || (errno == EWOULDBLOCK)) + break; + return pj_get_os_error(); + } + fdport->buf_in->pos += ret; + fdport->buf_in->len -= ret; + if (fdport->buf_in->pos >= FD_BUF_SIZE) fdport->buf_in->pos = 0; + } + size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); + if (fdport->buf_in->len >= size) { + /* Enough data for whole frame */ + pj_size_t sz = size; + pj_size_t pos = (FD_BUF_SIZE + fdport->buf_in->pos - fdport->buf_in->len) % FD_BUF_SIZE; + if (pos + sz > FD_BUF_SIZE) sz = FD_BUF_SIZE - pos; + memcpy(frame->buf, fdport->buf_in->data + pos, sz); + if (sz < size) + memcpy((char*)frame->buf + sz, fdport->buf_in->data, size - sz); + fdport->buf_in->len -= size; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + frame->size = size; + /* Is this the correct timestamp calculation? */ + frame->timestamp.u64 = fdport->timestamp.u64; + fdport->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info); + } else { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + } + + return PJ_SUCCESS; +} + +#endif /* O_NONBLOCK */ + + +/* + * Destroy port. + */ +static pj_status_t fd_port_on_destroy(pjmedia_port *this_port) +{ + PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, + PJ_EINVALIDOP); + + /* Destroy signature */ + this_port->info.signature = 0; + + return PJ_SUCCESS; +} Property changes on: pjmedia/src/pjmedia/fd_port.c ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Id \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: pjsip-apps/src/python/_pjsua.c =================================================================== --- pjsip-apps/src/python/_pjsua.c (revision 4770) +++ pjsip-apps/src/python/_pjsua.c (working copy) @@ -2670,6 +2670,62 @@ } /* + * py_pjsua_audio_fd_create + */ +static PyObject *py_pjsua_audio_fd_create(PyObject *pSelf, PyObject *pArgs) +{ + pj_status_t status; + int id = PJSUA_INVALID_ID; + int fd_in, fd_out; + unsigned options; + PJ_UNUSED_ARG(pSelf); + + if (!PyArg_ParseTuple(pArgs, "iiI", &fd_in, &fd_out, &options)) + return NULL; + + status = pjsua_audio_fd_create(fd_in, fd_out, options, &id); + return Py_BuildValue("ii", status, id); +} + +/* + * py_pjsua_audio_fd_get_conf_port + */ +static PyObject *py_pjsua_audio_fd_get_conf_port(PyObject *pSelf, + PyObject *pArgs) +{ + + int id, port_id; + + PJ_UNUSED_ARG(pSelf); + + if (!PyArg_ParseTuple(pArgs, "i", &id)) { + return NULL; + } + + port_id = pjsua_audio_fd_get_conf_port(id); + + return Py_BuildValue("i", port_id); +} + +/* + * py_pjsua_audio_fd_destroy + */ +static PyObject *py_pjsua_audio_fd_destroy(PyObject *pSelf, PyObject *pArgs) +{ + int id; + int status; + + PJ_UNUSED_ARG(pSelf); + + if (!PyArg_ParseTuple(pArgs, "i", &id)) { + return NULL; + } + + status = pjsua_audio_fd_destroy(id); + return Py_BuildValue("i", status); +} + +/* * py_pjsua_enum_snd_devs */ static PyObject *py_pjsua_enum_snd_devs(PyObject *pSelf, PyObject *pArgs) @@ -2998,6 +3054,20 @@ static char pjsua_recorder_destroy_doc[] = "int _pjsua.recorder_destroy (int id) " "Destroy recorder (this will complete recording)."; +static char pjsua_audio_fd_create_doc[] = + "int, int _pjsua.audio_fd_create (int fd_in, int fd_out, unsigned flags) " + "Create a file descriptor port, and automatically connect this port " + "to the conference bridge. Descriptors may be -1 to disable the direction. " + "Flags may be zero for blocking I/O or ORed 1 for non-blocking I/O, " + "2 for accepting Windows file handles insted of integer descriptors. " + "Handles are preferred on Windows platform. Use " + "msvcrt.get_osfhandle(a.fileno()) or win32file._get_osfhandle(a.fileno())."; +static char pjsua_audio_fd_get_conf_port_doc[] = + "int _pjsua.audio_fd_get_conf_port (int id) " + "Get conference port associated with file descriptor port."; +static char pjsua_audio_fd_destroy_doc[] = + "int _pjsua.audio_fd_destroy (int id) " + "Destroy audio file descriptor port."; static char pjsua_enum_snd_devs_doc[] = "_pjsua.PJMedia_Snd_Dev_Info[] _pjsua.enum_snd_devs (int count) " "Enum sound devices."; @@ -4281,6 +4351,18 @@ pjsua_recorder_destroy_doc }, { + "audio_fd_create", py_pjsua_audio_fd_create, METH_VARARGS, + pjsua_audio_fd_create_doc + }, + { + "audio_fd_get_conf_port", py_pjsua_audio_fd_get_conf_port, METH_VARARGS, + pjsua_audio_fd_get_conf_port_doc + }, + { + "audio_fd_destroy", py_pjsua_audio_fd_destroy, METH_VARARGS, + pjsua_audio_fd_destroy_doc + }, + { "enum_snd_devs", py_pjsua_enum_snd_devs, METH_VARARGS, pjsua_enum_snd_devs_doc }, Index: pjsip-apps/src/python/pjsua.py =================================================================== --- pjsip-apps/src/python/pjsua.py (revision 4770) +++ pjsip-apps/src/python/pjsua.py (working copy) @@ -2694,7 +2694,66 @@ err = _pjsua.recorder_destroy(rec_id) self._err_check("recorder_destroy()", self, err) + def create_audio_fd(self, fd_in = -1, fd_out = -1, flags = 0): + """Create audio file descriptor port. + Keyword arguments + fd_in -- Input file descriptor/handle or -1 to disable + fd_out -- Output file descriptor/handle or -1 to disable + flags -- 0 for blocking I/O (default) or ORed + - 1 for non-blocking I/O with buffering (UNIX only) + - 2 fd_in and fd_out are Windows file handle_events + instead of integer descriptors (Windows only) + + File descriptors and handles can be obtained as follows: + f = io.open("file.wav", "rb") + descriptor = f.fileno() + handle = msvcrt.get_osfhandle(descriptor) + # or + handle = win32file._get_osfhandle(descriptor) + + On Windows, passing handles is preferred to descriptors, since + descriptors only work with the same MSVCRT DLL (not static) + version across all libraries. + + Return: + File descriptor audio port ID + + """ + lck = self.auto_lock() + err, fdp_id = _pjsua.audio_fd_create(fd_in, fd_out, flags) + self._err_check("create_audio_fd()", self, err) + return fdp_id + + def audio_fd_get_slot(self, fdp_id): + """Get the conference port ID for the specified file descriptor port. + + Keyword arguments: + fdp_id -- the file descriptor port ID + + Return: + Conference slot number for the file descriptor port + + """ + lck = self.auto_lock() + slot = _pjsua.audio_fd_get_conf_port(fdp_id) + if slot < 1: + self._err_check("audio_fd_get_slot()", self, -1, + "Invalid file descriptor port id") + return slot + + def audio_fd_destroy(self, fdp_id): + """Destroy the file descriptor port. + + Keyword arguments: + fdp_id -- the audio callback ID. + + """ + lck = self.auto_lock() + err = _pjsua.audio_fd_destroy(fdp_id) + self._err_check("audio_fd_destroy()", self, err) + + # Internal functions @staticmethod Index: pjsip-apps/src/python/samples/audio_fd.py =================================================================== --- pjsip-apps/src/python/samples/audio_fd.py (revision 0) +++ pjsip-apps/src/python/samples/audio_fd.py (working copy) @@ -0,0 +1,152 @@ +# $Id: audio_cb.py 2171 2008-07-24 09:01:33Z bennylp $ +# +# SIP account and registration sample. In this sample, the program +# will block to wait until registration is complete +# +# Copyright (C) 2003-2008 Benny Prijono <benny at 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 +# +import sys +import pjsua as pj +import io +try: + # On Windows, pass handles rather than descriptors + import msvcrt +except: + pass + +# File name to read 8kHz PCM from +FN_IN = "in.pcm" +# File name to write 8kHz PCM to +FN_OUT = "out.pcm" +# Flags passed to FD port +FD_NONBLOCK = 1 +FD_HANDLES = 2 + + +def log_cb(level, str, len): + print str, + + +class MyCallCallback(pj.CallCallback): + + def __init__(self, call=None): + pj.CallCallback.__init__(self, call) + + def on_state(self): + if self.call.info().state == pj.CallState.DISCONNECTED: + global g_current_call + g_current_call = None + print "Call hung up" + + def on_media_state(self): + info = self.call.info() + call_slot = info.conf_slot + if (info.media_state == pj.MediaState.ACTIVE) and (call_slot >= 0): + print "Call slot:", call_slot + global g_fdp_id + fdp_slot = lib.audio_fd_get_slot(g_fdp_id) + print "Audio FD:", g_fdp_id, " slot:", fdp_slot + lib.conf_connect(call_slot, fdp_slot) + lib.conf_connect(fdp_slot, call_slot) + + +class MyAccountCallback(pj.AccountCallback): + + def __init__(self, account=None): + pj.AccountCallback.__init__(self, account) + + # Notification on incoming call + def on_incoming_call(self, call): + global g_current_call + if g_current_call: + call.answer(486, "Busy") + return + + call.set_callback(MyCallCallback(call)) + info = call.info() + print "Incoming call from", info.remote_uri + call.answer() + g_current_call = call + + +lib = pj.Lib() + +try: + mcfg = pj.MediaConfig() + mcfg.clock_rate = 8000 + mcfg.no_vad = True + lib.init(log_cfg = pj.LogConfig(level=4, callback=log_cb), + media_cfg = mcfg) + + # This is a MUST if not using a HW sound + lib.set_null_snd_dev() + + # Create UDP transport which listens to any available port + transport = lib.create_transport(pj.TransportType.UDP, + pj.TransportConfig(0)) + print "\nListening on", transport.info().host, + print "port", transport.info().port, "\n" + + lib.start(True) + + # Create local account + acc = lib.create_account_for_transport(transport, cb=MyAccountCallback()) + + try: + f_in = io.open(FN_IN, "rb") + fd_in = f_in.fileno() + except: + fd_in = -1 + print "\nInput file", FN_IN, "cannot be opened for reading\n" + + try: + f_out = io.open(FN_OUT, "wb") + fd_out = f_out.fileno() + except: + fd_out = -1 + print "\nOutput file", FN_OUT, "cannot be opened for writing\n" + + # Try to pass Windows handles instead of descriptors + flags = 0 + if 'msvcrt' in sys.modules: + if fd_in >= 0: fd_in = msvcrt.get_osfhandle(fd_in) + if fd_out >= 0: fd_out = msvcrt.get_osfhandle(fd_out) + flags |= FD_HANDLES + + g_current_call = None + g_fdp_id = lib.create_audio_fd(fd_in, fd_out, flags) + print "Audio file descriptor port ID:", g_fdp_id + + print "\nWaiting for incoming call" + my_sip_uri = "sip:" + transport.info().host + \ + ":" + str(transport.info().port) + print "My SIP URI is", my_sip_uri + print "\nPress ENTER to quit" + sys.stdin.readline() + + # Shutdown the library + lib.audio_fd_destroy(g_fdp_id) + transport = None + acc.delete() + acc = None + lib.destroy() + lib = None + +except pj.Error, e: + print "Exception: " + str(e) + lib.destroy() + Index: pjsip-apps/src/swig/python/audio_fd.py =================================================================== --- pjsip-apps/src/swig/python/audio_fd.py (revision 0) +++ pjsip-apps/src/swig/python/audio_fd.py (working copy) @@ -0,0 +1,155 @@ +import pjsua2 as pj +import sys +import io +import traceback +try: + # On Windows, pass handles rather than descriptors + import msvcrt +except: + pass + +# File name to read 8kHz PCM from +FN_IN = "in.pcm" +# File name to write 8kHz PCM to +FN_OUT = "out.pcm" + + +# +# Custom log writer +# +class MyLogWriter(pj.LogWriter): + def write(self, entry): + print entry.msg, + + +# +# Class to receive call events +# +class MyCall(pj.Call): + def onCallState(self, prm): + info = self.getInfo() + if info.state == pj.PJSIP_INV_STATE_DISCONNECTED: + global g_current_call + g_current_call = None + print "Call hung up" + + def onCallMediaState(self, prm): + if not self.hasMedia(): return + info = self.getInfo() + + # Do not use iterators, may cause crash! + #for mi in info.media: + + for i in range(0, len(info.media)): + mi = info.media[i] + if mi.type != pj.PJMEDIA_TYPE_AUDIO or \ + mi.status != pj.PJSUA_CALL_MEDIA_ACTIVE: + continue + m = self.getMedia(mi.index) + if not m: continue + global g_fdp + am = pj.AudioMedia.typecastFromMedia(m) + g_fdp.startTransmit(am) + am.startTransmit(g_fdp) + + +# +# Class to receive account events +# +class MyAccount(pj.Account): + def onIncomingCall(self, prm): + global g_current_call + call = MyCall(self, prm.callId) + op = pj.CallOpParam(True) + if g_current_call: + op.statusCode = pj.PJSIP_SC_BUSY_HERE + call.hangup(op) + return + info = call.getInfo() + print "Incoming call from", info.remoteUri + op.statusCode = pj.PJSIP_SC_OK + g_current_call = call + call.answer(op) + + +# +# Write PJ Error info +# +def handleError(error): + print + print 'Exception:' + print ' ', error.info() + print 'Traceback:' + print traceback.print_stack() + print + + +ep = pj.Endpoint() +try: + ep.libCreate() +except pj.Error, error: + handleError(error) + sys.exit(1) + +acc = None +g_current_call = None +g_fdp = None +try: + logger = MyLogWriter() + cfg = pj.EpConfig() + cfg.logConfig.msgLogging = False + cfg.logConfig.writer = logger + cfg.medConfig.clockRate = 8000 + cfg.medConfig.noVad = True + ep.libInit(cfg) + ep.audDevManager().setNullDev() + + tcfg = pj.TransportConfig() + tcfg.port = 0 # Any available + tid = ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, tcfg) + tinfo = ep.transportGetInfo(tid) + + acfg = pj.AccountConfig() + acfg.idUri = "sip:" + tinfo.localName + acc = MyAccount() + acc.create(acfg) + + ep.libStart() + + try: + f_in = io.open(FN_IN, "rb") + fd_in = f_in.fileno() + except: + fd_in = -1 + print "\nInput file", FN_IN, "cannot be opened for reading\n" + + try: + f_out = io.open(FN_OUT, "wb") + fd_out = f_out.fileno() + except: + fd_out = -1 + print "\nOutput file", FN_OUT, "cannot be opened for writing\n" + + # Try to pass Windows handles instead of descriptors + flags = 0 + if 'msvcrt' in sys.modules: + if fd_in >= 0: fd_in = msvcrt.get_osfhandle(fd_in) + if fd_out >= 0: fd_out = msvcrt.get_osfhandle(fd_out) + flags |= pj.PJMEDIA_FD_HANDLES + + g_fdp = pj.AudioMediaFD() + g_fdp.createFD(fd_in, fd_out, flags) + print "Audio file descriptor port ID:", g_fdp.getPortId() + + print "\nWaiting for incoming call" + print "My SIP URI is", acfg.idUri + print "\nPress ENTER to quit" + sys.stdin.readline() +except pj.Error, error: + handleError(error) + +# Shutdown the library -- destroy objects manually, do not let GC +if g_fdp: del g_fdp +if acc: del acc +ep.libDestroy() +del ep Index: pjsip-apps/src/swig/python/Makefile =================================================================== --- pjsip-apps/src/swig/python/Makefile (revision 4770) +++ pjsip-apps/src/swig/python/Makefile (working copy) @@ -22,7 +22,7 @@ cp gcc.exe g++.exe pjsua2_wrap.cpp: ../pjsua2.i ../symbols.i Makefile $(SRCS) - swig $(SWIG_FLAGS) -python -o pjsua2_wrap.cpp ../pjsua2.i + swig $(SWIG_FLAGS) -python -threads -o pjsua2_wrap.cpp ../pjsua2.i clean distclean realclean: rm -rf $(PYTHON_SO) pjsua2_wrap.cpp pjsua2_wrap.h pjsua2.py build *.pyc Index: pjsip-apps/src/swig/symbols.i =================================================================== --- pjsip-apps/src/swig/symbols.i (revision 4770) +++ pjsip-apps/src/swig/symbols.i (working copy) @@ -53,6 +53,8 @@ enum pjmedia_file_player_option {PJMEDIA_FILE_NO_LOOP = 1}; +enum pjmedia_file_descriptor_option {PJMEDIA_FD_BLOCK = 0, PJMEDIA_FD_NONBLOCK = 1, PJMEDIA_FD_HANDLES = 2}; + typedef enum pjmedia_type {PJMEDIA_TYPE_NONE, PJMEDIA_TYPE_AUDIO, PJMEDIA_TYPE_VIDEO, PJMEDIA_TYPE_APPLICATION, PJMEDIA_TYPE_UNKNOWN} pjmedia_type; typedef enum pjmedia_dir {PJMEDIA_DIR_NONE = 0, PJMEDIA_DIR_ENCODING = 1, PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING, PJMEDIA_DIR_DECODING = 2, PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING, PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING, PJMEDIA_DIR_ENCODING_DECODING = 3, PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING, PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING} pjmedia_dir; Index: pjsip-apps/src/swig/symbols.lst =================================================================== --- pjsip-apps/src/swig/symbols.lst (revision 4770) +++ pjsip-apps/src/swig/symbols.lst (working copy) @@ -13,6 +13,7 @@ pjmedia-videodev/videodev.h pjmedia_vid_dev_index pjmedia_vid_dev_std_index pjmedia-audiodev/audiodev.h pjmedia_aud_dev_route pjmedia_aud_dev_cap pjmedia/wav_port.h pjmedia_file_writer_option pjmedia_file_player_option +pjmedia/fd_port.h pjmedia_file_descriptor_option pjmedia/types.h pjmedia_type pjmedia_dir pjmedia_tp_proto pjmedia/format.h pjmedia_format_id Index: pjsip/include/pjsua-lib/pjsua.h =================================================================== --- pjsip/include/pjsua-lib/pjsua.h (revision 4770) +++ pjsip/include/pjsua-lib/pjsua.h (working copy) @@ -6161,6 +6161,76 @@ /***************************************************************************** + * File descriptor audio I/O. + */ + +/** + * Create a file descriptor I/O, and automatically connect this port to + * the conference bridge. + * + * File descriptor port profides a simple way to pass audio frames + * to/from an open file descriptor (a file, socket, pipe etc.) + * which can be the same or a different process + * (e.g. a speech recognizer or synthesizer). + * + * It can operate either in blocking or non-blocking mode. In blocking + * mode the user must make sure the I/O operations complete quicky, + * in non-blocking mode the data is buffered automatically. + * + * @param fd_in Descriptor open for reading (i.e. audio source) + * or -1 to disable audio input. + * @param fd_out Descriptor open for writing (i.e. audio sink) + * or -1 to disable audio output. + * @param flags 0 or PJMEDIA_FD_BLOCK for default, i.e. blocking mode, + * or ORed pjmedia_file_descriptor_option flags: + * - PJMEDIA_FD_NONBLOCK (1) for non-blocking mode + * (O_NONBLOCK is fcntl'ed on the descriptors if specified). + * - PJMEDIA_FD_HANDLES (2): Parameters fd_in and fd_out are Windows + * file handles instead of integer file descriptors (Windows only). + * @param p_id Pointer to receive the file descriptor port instance. + * The id space is shared with recorders. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_audio_fd_create(int fd_in, + int fd_out, + unsigned flags, + pjsua_recorder_id *p_id); + + +/** + * Get conference port associated with the file descriptor port. + * + * @param id The file descriptor port (i.e. recorder) ID. + * + * @return Conference port ID associated with this file descriptor port. + */ +PJ_DECL(pjsua_conf_port_id) pjsua_audio_fd_get_conf_port(pjsua_recorder_id id); + + +/** + * Get the media port for the file descriptor port. + * + * @param id The file descriptor port (i.e. recorder) ID. + * @param p_port The media port associated with the file descriptor port. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsua_audio_fd_get_port(pjsua_recorder_id id, + pjmedia_port **p_port); + + +/** + * Destroy file descriptor port. + * + * @param id The file descriptor ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_audio_fd_destroy(pjsua_recorder_id id); + + +/***************************************************************************** * Sound devices. */ Index: pjsip/include/pjsua2/media.hpp =================================================================== --- pjsip/include/pjsua2/media.hpp (revision 4770) +++ pjsip/include/pjsua2/media.hpp (working copy) @@ -444,6 +444,59 @@ int recorderId; }; +/** + * Audio File Descriptor I/O. + */ +class AudioMediaFD : public AudioMedia +{ +public: + /** + * Constructor. + */ + AudioMediaFD(); + + /** + * Create a file discriptor I/O, and automatically connect this port to + * the conference bridge. + * + * @param fd_in Descriptor open for reading (i.e. audio source) + * or -1 to disable audio input. + * @param fd_out Descriptor open for writing (i.e. audio sink) + * or -1 to disable audio output. + * @param options Optional options, which can be used to specify the + * file descriptor types and behavidour. + * ORed pjmedia_file_descriptor_option flags: + * - PJMEDIA_FD_NONBLOCK (1) for non-blocking mode (UNIX only; + * O_NONBLOCK is fcntl'ed on the descriptors if specified). + * - PJMEDIA_FD_HANDLES (2): Parameters fd_in and fd_out are Windows + * file handles instead of integer file descriptors (Windows only). + */ + void createFD(int fd_in = -1, + int fd_out = -1, + unsigned options = 0) throw(Error); + + /** + * Typecast from base class AudioMedia. This is useful for application + * written in language that does not support downcasting such as Python. + * + * @param media The object to be downcasted + * + * @return The object as AudioMediaFD instance + */ + static AudioMediaFD* typecastFromAudioMedia(AudioMedia *media); + + /** + * Virtual destructor. + */ + virtual ~AudioMediaFD(); + +private: + /** + * File Descriptor I/O Id (i.e. shared with Recorder Id). + */ + int recorderId; +}; + /************************************************************************* * Sound device management */ Index: pjsip/src/pjsua-lib/pjsua_aud.c =================================================================== --- pjsip/src/pjsua-lib/pjsua_aud.c (revision 4770) +++ pjsip/src/pjsua-lib/pjsua_aud.c (working copy) @@ -1452,6 +1452,154 @@ /***************************************************************************** + * File descriptor audio I/O. + */ + +/* + * Create a file descriptor audio port, and automatically connect this port to + * the conference bridge. + * Warning! Shares ID space with recorders. + */ +PJ_DEF(pj_status_t) pjsua_audio_fd_create(int fd_in, + int fd_out, + unsigned flags, + pjsua_recorder_id *p_id) +{ + unsigned slot, rec_id; + pj_pool_t *pool = NULL; + pjmedia_port *port; + static const pj_str_t fd_name = {"fd-port", 7}; + pj_status_t status = PJ_SUCCESS; + + /* At least one FD must be present */ + PJ_ASSERT_RETURN((fd_in >= 0) || (fd_out >= 0), PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Creating file descriptor port for %s..", + ((fd_in >= 0) && (fd_out >= 0) ? "RW" : ((fd_in >= 0) ? "R" : "W")))); + pj_log_push_indent(); + + if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) { + pj_log_pop_indent(); + return PJ_ETOOMANY; + } + + PJSUA_LOCK(); + + for (rec_id=0; rec_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++rec_id) { + if (pjsua_var.recorder[rec_id].port == NULL) + break; + } + + if (rec_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) { + /* This is unexpected */ + pj_assert(0); + status = PJ_EBUG; + goto on_return; + } + + pool = pjsua_pool_create("fd-port", 256, 16); + if (!pool) { + status = PJ_ENOMEM; + goto on_return; + } + + status = pjmedia_fd_port_create(pool, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + fd_in, + fd_out, + flags, + &port); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create file descriptor port", status); + goto on_return; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pool, + port, &fd_name, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + goto on_return; + } + + pjsua_var.recorder[rec_id].port = port; + pjsua_var.recorder[rec_id].slot = slot; + pjsua_var.recorder[rec_id].pool = pool; + + if (p_id) *p_id = rec_id; + + ++pjsua_var.rec_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "File descriptor port created, id=%d, slot=%d", rec_id, slot)); + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_return: + PJSUA_UNLOCK(); + if (pool) pj_pool_release(pool); + pj_log_pop_indent(); + return status; +} + + +/* + * Get conference port associated with a file descriptor port. + * Warning! Shares ID space with recorders. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_audio_fd_get_conf_port(pjsua_recorder_id id) +{ + return pjsua_recorder_get_conf_port(id); +} + +/* + * Get the media port for a file descriptor port. + * Warning! Shares ID space with recorders. + */ +PJ_DEF(pj_status_t) pjsua_audio_fd_get_port( pjsua_recorder_id id, + pjmedia_port **p_port) +{ + return pjsua_recorder_get_port(id, p_port); +} + +/* + * Destroy a file descriptor port. + * Warning! Shares ID space with recorders. + */ +PJ_DEF(pj_status_t) pjsua_audio_fd_destroy(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Destroying file descriptor port (i.e. recorder) %d..", id)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + if (pjsua_var.recorder[id].port) { + pjsua_conf_remove_port(pjsua_var.recorder[id].slot); + pjmedia_port_destroy(pjsua_var.recorder[id].port); + pjsua_var.recorder[id].port = NULL; + pjsua_var.recorder[id].slot = 0xFFFF; + pj_pool_release(pjsua_var.recorder[id].pool); + pjsua_var.recorder[id].pool = NULL; + pjsua_var.rec_cnt--; + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** * Sound devices. */ Index: pjsip/src/pjsua2/media.cpp =================================================================== --- pjsip/src/pjsua2/media.cpp (revision 4770) +++ pjsip/src/pjsua2/media.cpp (working copy) @@ -367,6 +367,47 @@ } /////////////////////////////////////////////////////////////////////////////// +AudioMediaFD::AudioMediaFD() +: recorderId(PJSUA_INVALID_ID) +{ + +} + +AudioMediaFD::~AudioMediaFD() +{ + if (recorderId != PJSUA_INVALID_ID) { + unregisterMediaPort(); + pjsua_audio_fd_destroy(recorderId); + } +} + +void AudioMediaFD::createFD(int fd_in /* = -1 */, + int fd_out /* = -1 */, + unsigned options /* = 0 */) + throw(Error) +{ + if (recorderId != PJSUA_INVALID_ID) { + PJSUA2_RAISE_ERROR(PJ_EEXISTS); + } + + PJSUA2_CHECK_EXPR( pjsua_audio_fd_create(fd_in, + fd_out, + options, + &recorderId) ); + + /* Get media port id. */ + id = pjsua_audio_fd_get_conf_port(recorderId); + + registerMediaPort(NULL); +} + +AudioMediaFD* AudioMediaFD::typecastFromAudioMedia( + AudioMedia *media) +{ + return static_cast<AudioMediaFD*>(media); +} + +/////////////////////////////////////////////////////////////////////////////// void AudioDevInfo::fromPj(const pjmedia_aud_dev_info &dev_info) { name = dev_info.name;