--- a/pjmedia/src/pjmedia-audiodev/audiodev.c
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -98,6 +98,10 @@ pjmedia_aud_dev_factory* pjmedia_symb_md
 pjmedia_aud_dev_factory* pjmedia_null_audio_factory(pj_pool_factory *pf);
 #endif
 
+#if PJMEDIA_AUDIO_DEV_HAS_TAPI_DEVICE
+pjmedia_aud_dev_factory* pjmedia_tapi_factory(pj_pool_factory *pf);
+#endif
+
 #define MAX_DRIVERS	16
 #define MAX_DEVS	64
 
@@ -409,6 +413,9 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_i
 #if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO
     aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_null_audio_factory;
 #endif
+#if PJMEDIA_AUDIO_DEV_HAS_TAPI_DEVICE
+    aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_tapi_factory;
+#endif
 
     /* Initialize each factory and build the device ID list */
     for (i=0; i<aud_subsys.drv_cnt; ++i) {
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/tapi_dev.c
@@ -0,0 +1,1307 @@
+/******************************************************************************
+
+                               Copyright (c) 2010
+                            Lantiq Deutschland GmbH
+                     Am Campeon 3; 85579 Neubiberg, Germany
+
+  For licensing information, see the file 'LICENSE' in the root folder of
+  this software module.
+
+******************************************************************************/
+#include <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_TAPI_DEVICE) && PJMEDIA_AUDIO_DEV_HAS_TAPI_DEVICE
+
+/* Linux includes */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <poll.h>
+
+/* TAPI includes */
+#include "drv_tapi_io.h"
+#include "vmmc_io.h"
+
+/* Maximum 2 devices */
+#define TAPI_AUDIO_PORT_NUM		2
+#define TAPI_BASE_NAME			"TAPI"
+#define TAPI_LL_DEV_BASE_PATH		"/dev/vmmc"
+#define TAPI_LL_DEV_FIRMWARE_NAME	"/lib/firmware/danube_firmware.bin"
+#define TAPI_LL_BBD_NAME		"/lib/firmware/danube_bbd_fxs.bin"
+
+#define TAPI_LL_DEV_SELECT_TIMEOUT_MS		2000
+#define TAPI_LL_DEV_MAX_PACKET_SIZE		800
+#define TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE	12
+#define TAPI_LL_DEV_ENC_FRAME_LEN_MS		20
+#define TAPI_LL_DEV_ENC_SMPL_PER_SEC		8000
+#define TAPI_LL_DEV_ENC_BITS_PER_SMPLS		16
+#define TAPI_LL_DEV_ENC_SMPL_PER_FRAME		160
+#define TAPI_LL_DEV_ENC_BYTES_PER_FRAME		(TAPI_LL_DEV_ENC_SMPL_PER_FRAME * (TAPI_LL_DEV_ENC_BITS_PER_SMPLS / 8))
+
+#define THIS_FILE	"tapi_dev.c"
+
+/* Set to 1 to enable tracing */
+#if 1
+#	define TRACE_(expr)	PJ_LOG(1,expr)
+#else
+#	define TRACE_(expr)
+#endif
+
+pj_int32_t ch_fd[TAPI_AUDIO_PORT_NUM];
+
+typedef struct
+{
+	pj_int32_t dev_fd;
+	pj_int32_t ch_fd[TAPI_AUDIO_PORT_NUM];
+	pj_int8_t data2phone_map[TAPI_AUDIO_PORT_NUM];
+} tapi_ctx;
+
+struct tapi_aud_factory
+{
+	pjmedia_aud_dev_factory	base;
+	pj_pool_t		*pool;
+	pj_pool_factory		*pf;
+	pj_uint32_t		dev_count;
+	pjmedia_aud_dev_info	*dev_info;
+	tapi_ctx		dev_ctx;
+};
+
+typedef struct tapi_aud_factory tapi_aud_factory_t;
+
+struct tapi_aud_stream
+{
+	pjmedia_aud_stream	base;
+	pj_pool_t		*pool;
+	pjmedia_aud_param	param;
+	pjmedia_aud_rec_cb	rec_cb;
+	pjmedia_aud_play_cb	play_cb;
+	void			*user_data;
+
+	pj_thread_desc		thread_desc;
+	pj_thread_t		*thread;
+	tapi_ctx		*dev_ctx;
+	pj_uint8_t		run_flag;
+	pj_timestamp		timestamp;
+};
+
+typedef struct tapi_aud_stream tapi_aud_stream_t;
+
+/* Factory prototypes */
+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);
+
+/* Stream prototypes */
+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);
+
+static pjmedia_aud_dev_factory_op tapi_fact_op =
+{
+	&factory_init,
+	&factory_destroy,
+	&factory_get_dev_count,
+	&factory_get_dev_info,
+	&factory_default_param,
+	&factory_create_stream
+};
+
+static pjmedia_aud_stream_op tapi_strm_op =
+{
+	&stream_get_param,
+	&stream_get_cap,
+	&stream_set_cap,
+	&stream_start,
+	&stream_stop,
+	&stream_destroy
+};
+
+/* TAPI configuration */
+static struct tapi_aud_stream streams[TAPI_AUDIO_PORT_NUM];
+
+void (*tapi_digit_callback)(pj_uint8_t port, pj_uint8_t digit) = NULL;
+void (*tapi_hook_callback)(pj_uint8_t port, pj_uint8_t event) = NULL;
+
+#define TAPI_TONE_LOCALE_NONE			32
+#define TAPI_TONE_LOCALE_BUSY_CODE		33
+#define TAPI_TONE_LOCALE_CONGESTION_CODE	34
+#define TAPI_TONE_LOCALE_DIAL_CODE		35
+#define TAPI_TONE_LOCALE_RING_CODE		36
+#define TAPI_TONE_LOCALE_WAITING_CODE		37
+
+static pj_uint8_t tapi_channel_revert = 0;
+static pj_uint8_t tapi_cid_type = 0;
+static pj_uint8_t tapi_locale = 0;
+
+void tapi_revert_channels(void)
+{
+	tapi_channel_revert = 1;
+	PJ_LOG(3, (THIS_FILE, "using reverted configuration for TAPI channels"));
+}
+
+void tapi_cid_select(char *cid)
+{
+	if (!stricmp(cid, "telecordia")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_TELCORDIA;
+		PJ_LOG(3, (THIS_FILE, "using TELECORDIA configuration for TAPI CID"));
+	} else if (!stricmp(cid, "etsi_fsk")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_ETSI_FSK;
+		PJ_LOG(3, (THIS_FILE, "using ETSI FSK configuration for TAPI CID"));
+	} else if (!stricmp(cid, "etsi_dtmf")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_ETSI_DTMF;
+		PJ_LOG(3, (THIS_FILE, "using ETSI DTMF configuration for TAPI CID"));
+	} else if (!stricmp(cid, "sin")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_SIN;
+		PJ_LOG(3, (THIS_FILE, "using SIN CID configuration for TAPI CID"));
+	} else if (!stricmp(cid, "ntt")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_NTT;
+		PJ_LOG(3, (THIS_FILE, "using NTT configuration for TAPI CID"));
+	} else if (!stricmp(cid, "kpn_dtmf")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_KPN_DTMF;
+		PJ_LOG(3, (THIS_FILE, "using KPN DTMF configuration for TAPI CID"));
+	} else if (!stricmp(cid, "kpn_dtmf_fsk")) {
+		tapi_cid_type = IFX_TAPI_CID_STD_KPN_DTMF_FSK;
+		PJ_LOG(3, (THIS_FILE, "using KPN DTMF FSK configuration for TAPI CID"));
+	}
+}
+
+void tapi_locale_select(char *country)
+{
+	IFX_TAPI_TONE_t tone;
+	pj_status_t status;
+	pj_uint8_t c;
+
+	tapi_locale = 1;
+
+	if (!stricmp(country, "croatia")) {
+		PJ_LOG(3, (THIS_FILE, "using localized tones for Croatia"));
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_BUSY_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 500;
+		tone.simple.cadence[1] = 500;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_CONGESTION_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 250;
+		tone.simple.cadence[1] = 250;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_DIAL_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 200;
+		tone.simple.cadence[1] = 300;
+		tone.simple.cadence[2] = 700;
+		tone.simple.cadence[3] = 800;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.frequencies[2] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[3] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_RING_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 1000;
+		tone.simple.cadence[1] = 4000;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_WAITING_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 300;
+		tone.simple.cadence[1] = 8000;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+	} else if (!stricmp(country, "germany")) {
+		PJ_LOG(3, (THIS_FILE, "using localized tones for Germany"));
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_BUSY_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 480;
+		tone.simple.cadence[1] = 480;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_CONGESTION_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 240;
+		tone.simple.cadence[1] = 240;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_DIAL_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 1000;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_RING_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 1000;
+		tone.simple.cadence[1] = 4000;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+
+		memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+		tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+		tone.simple.index = TAPI_TONE_LOCALE_WAITING_CODE;
+		tone.simple.freqA = 425;
+		tone.simple.levelA = 0;
+		tone.simple.cadence[0] = 200;
+		tone.simple.cadence[1] = 200;
+		tone.simple.cadence[2] = 200;
+		tone.simple.cadence[3] = 5000;
+		tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.frequencies[2] = IFX_TAPI_TONE_FREQA;
+		tone.simple.frequencies[3] = IFX_TAPI_TONE_FREQNONE;
+		tone.simple.loop = 0;
+		tone.simple.pause = 0;
+		for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+			status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+			if (status != PJ_SUCCESS)
+				TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+		}
+	}
+}
+
+static pj_int32_t
+tapi_dev_open(char* dev_path, const pj_int32_t ch_num)
+{
+	char devname[128] = { 0 };
+	pj_ansi_sprintf(devname,"%s%u%u", dev_path, 1, ch_num);
+	return open((const char*)devname, O_RDWR, 0644);
+}
+
+static pj_status_t
+tapi_dev_binary_buffer_create(const char *pPath, pj_uint8_t **ppBuf, pj_uint32_t *pBufSz)
+{
+	pj_status_t status = PJ_SUCCESS;
+	FILE *fd;
+	struct stat file_stat;
+
+	fd = fopen(pPath, "rb");
+	if (fd == NULL) {
+		TRACE_((THIS_FILE, "ERROR - binary file %s open failed!\n", pPath));
+		return PJ_EUNKNOWN;
+	}
+
+	if (stat(pPath, &file_stat) != 0) {
+		TRACE_((THIS_FILE, "ERROR - file %s statistics get failed!\n", pPath));
+		return PJ_EUNKNOWN;
+	}
+
+	*ppBuf = malloc(file_stat.st_size);
+	if (*ppBuf == NULL) {
+		TRACE_((THIS_FILE, "ERROR - binary file %s memory allocation failed!\n", pPath));
+		status = PJ_EUNKNOWN;
+		goto on_exit;
+	}
+
+	if (fread (*ppBuf, sizeof(pj_uint8_t), file_stat.st_size, fd) <= 0) {
+		TRACE_((THIS_FILE, "ERROR - file %s read failed!\n", pPath));
+		status = PJ_EUNKNOWN;
+		goto on_exit;
+	}
+
+	*pBufSz = file_stat.st_size;
+
+on_exit:
+	if (fd != NULL)
+		fclose(fd);
+
+	if (*ppBuf != NULL && status != PJ_SUCCESS)
+		free(*ppBuf);
+
+	return status;
+}
+
+static void
+tapi_dev_binary_buffer_delete(pj_uint8_t *pBuf)
+{
+	if (pBuf != NULL)
+		free(pBuf);
+}
+
+static pj_status_t
+tapi_dev_firmware_download(pj_int32_t fd, const char *pPath)
+{
+	pj_status_t status = PJ_SUCCESS;
+	pj_uint8_t *pFirmware = NULL;
+	pj_uint32_t binSz = 0;
+	VMMC_IO_INIT vmmc_io_init;
+
+	status = tapi_dev_binary_buffer_create(pPath, &pFirmware, &binSz);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - binary buffer create failed!\n"));
+		return PJ_EUNKNOWN;
+	}
+
+	memset(&vmmc_io_init, 0, sizeof(VMMC_IO_INIT));
+	vmmc_io_init.pPRAMfw = pFirmware;
+	vmmc_io_init.pram_size = binSz;
+
+	status = ioctl(fd, FIO_FW_DOWNLOAD, &vmmc_io_init);
+	if (status != PJ_SUCCESS)
+		TRACE_((THIS_FILE, "ERROR - FIO_FW_DOWNLOAD ioctl failed!"));
+
+	tapi_dev_binary_buffer_delete(pFirmware);
+
+	return status;
+}
+
+/* NOT USED */
+#if 0
+static int
+tapi_dev_bbd_download(int fd, const char *pPath)
+{
+	int status = PJ_SUCCESS;
+	unsigned char *pFirmware = NULL;
+	unsigned int binSz = 0;
+	VMMC_DWLD_t bbd_data;
+
+
+	/* Create binary buffer */
+	status = tapi_dev_binary_buffer_create(pPath, &pFirmware, &binSz);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - binary buffer create failed!\n"));
+		return status;
+	}
+
+	/* Download Voice Firmware */
+	memset(&bbd_data, 0, sizeof(VMMC_DWLD_t));
+	bbd_data.buf = pFirmware;
+	bbd_data.size = binSz;
+
+	status = ioctl(fd, FIO_BBD_DOWNLOAD, &bbd_data);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - FIO_BBD_DOWNLOAD failed!\n"));
+	}
+
+	/* Delete binary buffer */
+	tapi_dev_binary_buffer_delete(pFirmware);
+
+	return status;
+}
+#endif
+
+static pj_status_t tapi_dev_start(tapi_aud_factory_t *f)
+{
+	pj_uint8_t c, hook_status;
+	IFX_TAPI_TONE_t tone;
+	IFX_TAPI_DEV_START_CFG_t tapistart;
+	IFX_TAPI_MAP_DATA_t datamap;
+	IFX_TAPI_ENC_CFG_t enc_cfg;
+	IFX_TAPI_LINE_VOLUME_t line_vol;
+	IFX_TAPI_WLEC_CFG_t lec_cfg;
+	IFX_TAPI_JB_CFG_t jb_cfg;
+	IFX_TAPI_CID_CFG_t cid_cfg;
+	pj_status_t status;
+
+	/* Open device */
+	f->dev_ctx.dev_fd = tapi_dev_open(TAPI_LL_DEV_BASE_PATH, 0);
+
+	if (f->dev_ctx.dev_fd < 0) {
+		TRACE_((THIS_FILE, "ERROR - TAPI device open failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+		if (tapi_channel_revert)
+			ch_fd[c] = f->dev_ctx.ch_fd[c] = tapi_dev_open(TAPI_LL_DEV_BASE_PATH, c + 1);
+		else
+			ch_fd[c] = f->dev_ctx.ch_fd[c] = tapi_dev_open(TAPI_LL_DEV_BASE_PATH, TAPI_AUDIO_PORT_NUM - c);
+
+		if (f->dev_ctx.dev_fd < 0) {
+			TRACE_((THIS_FILE, "ERROR - TAPI channel%d open failed!", c));
+			return PJ_EUNKNOWN;
+		}
+		if (tapi_channel_revert)
+			f->dev_ctx.data2phone_map[c] = c & 0x1 ? 1 : 0;
+		else
+			f->dev_ctx.data2phone_map[c] = c & 0x1 ? 0 : 1;
+	}
+
+	status = tapi_dev_firmware_download(f->dev_ctx.dev_fd, TAPI_LL_DEV_FIRMWARE_NAME);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - Voice Firmware Download failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	/* Download coefficients */
+	/*
+	status = tapi_dev_bbd_download(f->dev_ctx.dev_fd, TAPI_LL_BBD_NAME);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - Voice Coefficients Download failed!"));
+		return PJ_EUNKNOWN;
+	}
+	*/
+
+	memset(&tapistart, 0x0, sizeof(IFX_TAPI_DEV_START_CFG_t));
+	tapistart.nMode = IFX_TAPI_INIT_MODE_VOICE_CODER;
+
+	/* Start TAPI */
+	status = ioctl(f->dev_ctx.dev_fd, IFX_TAPI_DEV_START, &tapistart);
+	if (status != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_DEV_START ioctl failed"));
+		return PJ_EUNKNOWN;
+	}
+
+
+	/* OpenWrt default tone */
+	memset(&tone, 0, sizeof(IFX_TAPI_TONE_t));
+	tone.simple.format = IFX_TAPI_TONE_TYPE_SIMPLE;
+	tone.simple.index = TAPI_TONE_LOCALE_NONE;
+	tone.simple.freqA = 400;
+	tone.simple.levelA = 0;
+	tone.simple.freqB = 450;
+	tone.simple.levelB = 0;
+	tone.simple.freqC = 550;
+	tone.simple.levelC = 0;
+	tone.simple.freqD = 600;
+	tone.simple.levelD = 0;
+	tone.simple.cadence[0] = 100;
+	tone.simple.cadence[1] = 150;
+	tone.simple.cadence[2] = 100;
+	tone.simple.cadence[3] = 150;
+	tone.simple.frequencies[0] = IFX_TAPI_TONE_FREQA | IFX_TAPI_TONE_FREQB;
+	tone.simple.frequencies[1] = IFX_TAPI_TONE_FREQNONE;
+	tone.simple.frequencies[2] = IFX_TAPI_TONE_FREQC | IFX_TAPI_TONE_FREQD;
+	tone.simple.frequencies[3] = IFX_TAPI_TONE_FREQNONE;
+	tone.simple.loop = 0;
+	tone.simple.pause = 0;
+	for (c = 0; c < TAPI_AUDIO_PORT_NUM; c++) {
+		/* OpenWrt default tone */
+		status = ioctl(ch_fd[c], IFX_TAPI_TONE_TABLE_CFG_SET, &tone);
+		if (status != PJ_SUCCESS)
+			TRACE_((THIS_FILE, "IFX_TAPI_TONE_TABLE_CFG_SET failed!\n"));
+
+		/* Perform mapping */
+		memset(&datamap, 0x0, sizeof(IFX_TAPI_MAP_DATA_t));
+		datamap.nDstCh = f->dev_ctx.data2phone_map[c];
+		datamap.nChType = IFX_TAPI_MAP_TYPE_PHONE;
+
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_MAP_DATA_ADD, &datamap);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_MAP_DATA_ADD ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Set Line feed */
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_LINE_FEED_SET, IFX_TAPI_LINE_FEED_STANDBY);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_FEED_SET ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Configure encoder for linear stream */
+		memset(&enc_cfg, 0x0, sizeof(IFX_TAPI_ENC_CFG_t));
+		enc_cfg.nFrameLen = IFX_TAPI_COD_LENGTH_20;
+		enc_cfg.nEncType = IFX_TAPI_COD_TYPE_LIN16_8;
+
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_ENC_CFG_SET, &enc_cfg);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_ENC_CFG_SET ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Suppress TAPI volume, otherwise PJSIP starts autogeneration */
+		memset(&line_vol, 0, sizeof(line_vol));
+		line_vol.nGainRx = -8;
+		line_vol.nGainTx = -8;
+
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_PHONE_VOLUME_SET, &line_vol);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_PHONE_VOLUME_SET ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Configure line echo canceller */
+		memset(&lec_cfg, 0, sizeof(lec_cfg));
+	        lec_cfg.nType = IFX_TAPI_WLEC_TYPE_NFE;
+	        lec_cfg.bNlp = IFX_TAPI_LEC_NLP_OFF;
+
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_WLEC_PHONE_CFG_SET, &lec_cfg);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_WLEC_PHONE_CFG_SET ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Configure jitter buffer */
+		memset(&jb_cfg, 0, sizeof(jb_cfg));
+		jb_cfg.nJbType = IFX_TAPI_JB_TYPE_ADAPTIVE;
+		jb_cfg.nPckAdpt = IFX_TAPI_JB_PKT_ADAPT_VOICE;
+		jb_cfg.nLocalAdpt = IFX_TAPI_JB_LOCAL_ADAPT_ON;
+		jb_cfg.nScaling = 0x10;
+		jb_cfg.nInitialSize = 0x2d0;
+		jb_cfg.nMinSize = 0x50;
+		jb_cfg.nMaxSize = 0x5a0;
+
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_JB_CFG_SET, &jb_cfg);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_JB_CFG_SET ioctl failed"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* Configure Caller ID type */
+		if (tapi_cid_type) {
+			memset(&cid_cfg, 0, sizeof(cid_cfg));
+			cid_cfg.nStandard = tapi_cid_type;
+			status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_CID_CFG_SET, &cid_cfg);
+			if (status != PJ_SUCCESS) {
+				TRACE_((THIS_FILE, "ERROR - IFX_TAPI_CID_CFG_SET ioctl failed"));
+				return PJ_EUNKNOWN;
+			}
+		}
+
+		/* check hook status */
+		hook_status = 0;
+		status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_LINE_HOOK_STATUS_GET, &hook_status);
+		if (status != PJ_SUCCESS) {
+			TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_HOOK_STATUS_GET ioctl failed!"));
+			return PJ_EUNKNOWN;
+		}
+
+		/* if off hook do initialization */
+		if (hook_status) {
+			status = ioctl(f->dev_ctx.ch_fd[c], IFX_TAPI_LINE_FEED_SET, IFX_TAPI_LINE_FEED_ACTIVE);
+			if (status != PJ_SUCCESS) {
+				TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_FEED_SET ioctl failed!"));
+				return PJ_EUNKNOWN;
+			}
+			status = ioctl(c, IFX_TAPI_ENC_START, 0);
+			if (status != PJ_SUCCESS) {
+				TRACE_((THIS_FILE, "ERROR - IFX_TAPI_ENC_START ioctl failed!"));
+				return PJ_EUNKNOWN;
+			}
+
+			status = ioctl(c, IFX_TAPI_DEC_START, 0);
+			if (status != PJ_SUCCESS) {
+				TRACE_((THIS_FILE, "ERROR - IFX_TAPI_DEC_START ioctl failed!"));
+				return PJ_EUNKNOWN;
+			}
+		}
+	}
+
+	return status;
+}
+
+static pj_status_t
+tapi_dev_stop(tapi_aud_factory_t *f)
+{
+	pj_status_t status = PJ_SUCCESS;
+	pj_uint8_t c;
+
+	if (ioctl(f->dev_ctx.dev_fd, IFX_TAPI_DEV_STOP, 0) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_DEV_STOP ioctl failed"));
+		status = PJ_EUNKNOWN;
+	}
+
+	close(f->dev_ctx.dev_fd);
+	for (c = TAPI_AUDIO_PORT_NUM; c > 0; c--)
+		close(f->dev_ctx.ch_fd[TAPI_AUDIO_PORT_NUM-c]);
+
+	return status;
+}
+
+static pj_status_t
+tapi_dev_codec_control(pj_int32_t fd, pj_uint8_t start)
+{
+	if (ioctl(fd, start ? IFX_TAPI_ENC_START : IFX_TAPI_ENC_STOP, 0) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_ENC_%s ioctl failed!",
+			start ? "START" : "STOP"));
+		return PJ_EUNKNOWN;
+	}
+
+	if (ioctl(fd, start ? IFX_TAPI_DEC_START : IFX_TAPI_DEC_STOP, 0) != IFX_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_DEC_%s ioctl failed!",
+			start ? "START" : "STOP"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t tapi_dev_event_on_hook(tapi_ctx *dev_ctx, pj_uint32_t dev_idx)
+{
+	PJ_LOG(1,(THIS_FILE, "TAPI: ONHOOK"));
+
+	if (ioctl(dev_ctx->ch_fd[dev_idx], IFX_TAPI_LINE_FEED_SET,
+		IFX_TAPI_LINE_FEED_STANDBY) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_FEED_SET ioctl failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	/* enc/dec stop */
+	if (tapi_dev_codec_control(dev_ctx->ch_fd[dev_idx], 0) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - codec start failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t tapi_dev_event_off_hook(tapi_ctx *dev_ctx, pj_uint32_t dev_idx)
+{
+	PJ_LOG(1,(THIS_FILE, "TAPI: OFFHOOK"));
+
+	if (ioctl(dev_ctx->ch_fd[dev_idx], IFX_TAPI_LINE_FEED_SET,
+		IFX_TAPI_LINE_FEED_ACTIVE) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_FEED_SET ioctl failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	/* enc/dec stop */
+	if (tapi_dev_codec_control(dev_ctx->ch_fd[dev_idx], 1) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - codec start failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+tapi_dev_event_digit(tapi_ctx *dev_ctx, pj_uint32_t dev_idx)
+{
+	PJ_LOG(1,(THIS_FILE, "TAPI: OFFHOOK"));
+
+	if (ioctl(dev_ctx->ch_fd[dev_idx], IFX_TAPI_LINE_FEED_SET,
+			IFX_TAPI_LINE_FEED_ACTIVE) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_FEED_SET ioctl failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	/* enc/dec stop */
+	if (tapi_dev_codec_control(dev_ctx->ch_fd[dev_idx], 1) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - codec start failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+tapi_dev_event_handler(tapi_aud_stream_t *stream)
+{
+	IFX_TAPI_EVENT_t tapiEvent;
+	tapi_ctx *dev_ctx = stream->dev_ctx;
+	pj_status_t status = PJ_SUCCESS;
+	unsigned int i;
+
+	for (i = 0; i < TAPI_AUDIO_PORT_NUM; i++) {
+		memset (&tapiEvent, 0, sizeof(tapiEvent));
+		tapiEvent.ch = dev_ctx->data2phone_map[i];
+		status = ioctl(dev_ctx->dev_fd, IFX_TAPI_EVENT_GET, &tapiEvent);
+
+		if ((status == PJ_SUCCESS) && (tapiEvent.id != IFX_TAPI_EVENT_NONE)) {
+			switch(tapiEvent.id) {
+			case IFX_TAPI_EVENT_FXS_ONHOOK:
+				status = tapi_dev_event_on_hook(dev_ctx, i);
+				if(tapi_hook_callback)
+					tapi_hook_callback(i, 0);
+				break;
+			case IFX_TAPI_EVENT_FXS_OFFHOOK:
+				status = tapi_dev_event_off_hook(dev_ctx, i);
+				if(tapi_hook_callback)
+					tapi_hook_callback(i, 1);
+				break;
+			case IFX_TAPI_EVENT_DTMF_DIGIT:
+				if(tapi_digit_callback)
+					tapi_digit_callback(i, tapiEvent.data.dtmf.ascii);
+				break;
+			case IFX_TAPI_EVENT_PULSE_DIGIT:
+				if(tapi_digit_callback)
+					if(tapiEvent.data.pulse.digit == 0xB)
+						tapi_digit_callback(i, '0');
+					else
+						tapi_digit_callback(i, '0' + tapiEvent.data.pulse.digit);
+				break;
+			case IFX_TAPI_EVENT_COD_DEC_CHG:
+			case IFX_TAPI_EVENT_TONE_GEN_END:
+			case IFX_TAPI_EVENT_CID_TX_SEQ_END:
+				break;
+			default:
+				PJ_LOG(1,(THIS_FILE, "unknown tapi event %08X", tapiEvent.id));
+				break;
+			}
+		}
+	}
+
+	return status;
+}
+
+static pj_status_t
+tapi_dev_data_handler(tapi_aud_stream_t *stream) {
+	pj_status_t status = PJ_SUCCESS;
+	tapi_ctx *dev_ctx = stream->dev_ctx;
+	pj_uint32_t dev_idx = stream->param.rec_id;
+	pj_uint8_t buf_rec[TAPI_LL_DEV_ENC_BYTES_PER_FRAME + TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE]={0};
+	pj_uint8_t buf_play[TAPI_LL_DEV_ENC_BYTES_PER_FRAME + TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE]={0};
+	pjmedia_frame frame_rec, frame_play;
+	pj_int32_t ret;
+
+	/* Get data from driver */
+	ret = read(dev_ctx->ch_fd[dev_idx], buf_rec, sizeof(buf_rec));
+	if (ret < 0) {
+		TRACE_((THIS_FILE, "ERROR - no data available from device!"));
+		return PJ_EUNKNOWN;
+	}
+
+	if (ret > 0) {
+		frame_rec.type = PJMEDIA_FRAME_TYPE_AUDIO;
+		frame_rec.buf = buf_rec + TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE;
+		frame_rec.size = ret - TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE;
+		frame_rec.timestamp.u64 = stream->timestamp.u64;
+
+		status = stream->rec_cb(stream->user_data, &frame_rec);
+		if (status != PJ_SUCCESS)
+			PJ_LOG(1, (THIS_FILE, "rec_cb() failed %d", status));
+
+		frame_play.type = PJMEDIA_FRAME_TYPE_AUDIO;
+		frame_play.buf = buf_play + TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE;
+		frame_play.size = TAPI_LL_DEV_ENC_BYTES_PER_FRAME;
+		frame_play.timestamp.u64 = stream->timestamp.u64;
+
+		status = (*stream->play_cb)(stream->user_data, &frame_play);
+		if (status != PJ_SUCCESS) {
+			PJ_LOG(1, (THIS_FILE, "play_cb() failed %d", status));
+		} else {
+			memcpy(buf_play, buf_rec, TAPI_LL_DEV_RTP_HEADER_SIZE_BYTE);
+
+			ret = write(dev_ctx->ch_fd[dev_idx], buf_play, sizeof(buf_play));
+
+			if (ret < 0) {
+				PJ_LOG(1, (THIS_FILE, "ERROR - device data writing failed!"));
+				return PJ_EUNKNOWN;
+			}
+
+			if (ret == 0) {
+				PJ_LOG(1, (THIS_FILE, "ERROR - no data written to device!"));
+				return PJ_EUNKNOWN;
+			}
+		}
+
+		stream->timestamp.u64 += TAPI_LL_DEV_ENC_SMPL_PER_FRAME;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static int
+PJ_THREAD_FUNC tapi_dev_thread(void *arg) {
+	tapi_ctx *dev_ctx = streams[0].dev_ctx;
+	pj_uint32_t sretval;
+	struct pollfd fds[3];
+
+	PJ_LOG(1,(THIS_FILE, "TAPI: thread starting..."));
+
+	streams[0].run_flag = 1;
+	streams[1].run_flag = 1;
+
+	fds[0].fd = dev_ctx->dev_fd;
+	fds[0].events = POLLIN;
+	fds[1].fd = dev_ctx->ch_fd[0];
+	fds[1].events = POLLIN;
+	fds[2].fd = dev_ctx->ch_fd[1];
+	fds[2].events = POLLIN;
+
+	while(1)
+	{
+		sretval = poll(fds, TAPI_AUDIO_PORT_NUM + 1, TAPI_LL_DEV_SELECT_TIMEOUT_MS);
+
+		if (!streams[0].run_flag && !streams[0].run_flag)
+			break;
+		if (sretval <= 0)
+			continue;
+
+		if (fds[0].revents == POLLIN) {
+			if (tapi_dev_event_handler(&streams[0]) != PJ_SUCCESS) {
+				PJ_LOG(1, (THIS_FILE, "TAPI: event hanldler failed"));
+				break;
+			}
+		}
+
+		if (fds[1].revents == POLLIN) {
+			if (tapi_dev_data_handler(&streams[0]) != PJ_SUCCESS) {
+				PJ_LOG(1, (THIS_FILE, "TAPI: data hanldler failed"));
+				break;
+			}
+		}
+
+		if (fds[2].revents == POLLIN) {
+			if (tapi_dev_data_handler(&streams[1]) != PJ_SUCCESS) {
+				PJ_LOG(1, (THIS_FILE, "TAPI: data hanldler failed"));
+				break;
+			}
+		}
+	}
+	PJ_LOG(1, (THIS_FILE, "TAPI: thread stopping..."));
+
+	return 0;
+}
+
+/* Factory operations */
+
+pjmedia_aud_dev_factory*
+pjmedia_tapi_factory(pj_pool_factory *pf) {
+	struct tapi_aud_factory *f;
+	pj_pool_t *pool;
+
+	TRACE_((THIS_FILE, "pjmedia_tapi_factory()"));
+
+	pool = pj_pool_create(pf, "tapi", 512, 512, NULL);
+	f = PJ_POOL_ZALLOC_T(pool, struct tapi_aud_factory);
+	f->pf = pf;
+	f->pool = pool;
+	f->base.op = &tapi_fact_op;
+
+	return &f->base;
+}
+
+static pj_status_t
+factory_init(pjmedia_aud_dev_factory *f)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+	pj_uint8_t i;
+
+	TRACE_((THIS_FILE, "factory_init()"));
+
+	af->dev_count = TAPI_AUDIO_PORT_NUM;
+	af->dev_info = (pjmedia_aud_dev_info*)
+	pj_pool_calloc(af->pool, af->dev_count, sizeof(pjmedia_aud_dev_info));
+	for (i = 0; i < TAPI_AUDIO_PORT_NUM; i++) {
+		pj_ansi_sprintf(af->dev_info[i].name,"%s_%02d", TAPI_BASE_NAME, i);
+		af->dev_info[i].input_count = af->dev_info[i].output_count = 1;
+		af->dev_info[i].default_samples_per_sec = TAPI_LL_DEV_ENC_SMPL_PER_SEC;
+		pj_ansi_strcpy(af->dev_info[i].driver, "/dev/vmmc");
+		af->dev_info[i].caps = PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
+			PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
+			PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+		af->dev_info[i].routes = PJMEDIA_AUD_DEV_ROUTE_DEFAULT ;
+	}
+	if (tapi_dev_start(af) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - TAPI device init failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+factory_destroy(pjmedia_aud_dev_factory *f)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+	pj_pool_t *pool;
+	pj_status_t status = PJ_SUCCESS;
+
+	TRACE_((THIS_FILE, "factory_destroy()"));
+
+	if (tapi_dev_stop(af) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - TAPI device stop failed!"));
+		status = PJ_EUNKNOWN;
+	}
+	pool = af->pool;
+	af->pool = NULL;
+	pj_pool_release(pool);
+
+	return status;
+}
+
+static unsigned
+factory_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+	TRACE_((THIS_FILE, "factory_get_dev_count()"));
+
+	return af->dev_count;
+}
+
+static pj_status_t
+factory_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+
+	TRACE_((THIS_FILE, "factory_get_dev_info()"));
+	PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EAUD_INVDEV);
+
+	pj_memcpy(info, &af->dev_info[index], sizeof(*info));
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+factory_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+	struct pjmedia_aud_dev_info *di = &af->dev_info[index];
+
+	TRACE_((THIS_FILE, "factory_default_param."));
+	PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EAUD_INVDEV);
+
+	pj_bzero(param, sizeof(*param));
+	if (di->input_count && di->output_count) {
+		param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+		param->rec_id = index;
+		param->play_id = index;
+	} else if (di->input_count) {
+		param->dir = PJMEDIA_DIR_CAPTURE;
+		param->rec_id = index;
+		param->play_id = PJMEDIA_AUD_INVALID_DEV;
+	} else if (di->output_count) {
+		param->dir = PJMEDIA_DIR_PLAYBACK;
+		param->play_id = index;
+		param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+	} else {
+		return PJMEDIA_EAUD_INVDEV;
+	}
+
+	param->clock_rate = TAPI_LL_DEV_ENC_SMPL_PER_SEC; //di->default_samples_per_sec;
+	param->channel_count = 1;
+	param->samples_per_frame = TAPI_LL_DEV_ENC_SMPL_PER_FRAME;
+	param->bits_per_sample = TAPI_LL_DEV_ENC_BITS_PER_SMPLS;
+	param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE | di->caps;
+	param->output_route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+
+	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)
+{
+	struct tapi_aud_factory *af = (struct tapi_aud_factory*)f;
+	pj_pool_t *pool;
+	pj_status_t status;
+	int id = param->rec_id;
+	struct tapi_aud_stream *strm = &streams[param->rec_id];
+	TRACE_((THIS_FILE, "factory_create_stream() rec_id:%d play_id:%d", param->rec_id, param->play_id));
+
+	/* Can only support 16bits per sample */
+	PJ_ASSERT_RETURN(param->bits_per_sample == TAPI_LL_DEV_ENC_BITS_PER_SMPLS, PJ_EINVAL);
+	PJ_ASSERT_RETURN(param->clock_rate == TAPI_LL_DEV_ENC_SMPL_PER_SEC, PJ_EINVAL);
+	PJ_ASSERT_RETURN(param->samples_per_frame == TAPI_LL_DEV_ENC_SMPL_PER_FRAME, PJ_EINVAL);
+
+	/* Can only support bidirectional stream */
+	PJ_ASSERT_RETURN(param->dir & PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EINVAL);
+
+	if (id == 0) {
+		/* Initialize our stream data */
+		pool = pj_pool_create(af->pf, "tapi-dev", 1000, 1000, NULL);
+		PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+		strm->pool = pool;
+	} else {
+		pool = strm->pool = streams[0].pool;
+	}
+
+	strm->rec_cb = rec_cb;
+	strm->play_cb = play_cb;
+	strm->user_data = user_data;
+
+	pj_memcpy(&strm->param, param, sizeof(*param));
+
+	if ((strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) == 0) {
+		strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
+	}
+
+	strm->timestamp.u64 = 0;
+	strm->dev_ctx = &(af->dev_ctx);
+
+	/* Create and start the thread */
+	if (id == 1) {
+		status = pj_thread_create(pool, "tapi", &tapi_dev_thread, strm, 0, 0, &streams[0].thread);
+		if (status != PJ_SUCCESS) {
+			stream_destroy(&strm->base);
+			return status;
+		}
+	}
+
+	/* Done */
+	strm->base.op = &tapi_strm_op;
+	*p_aud_strm = &strm->base;
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi)
+{
+	struct tapi_aud_stream *strm = (struct tapi_aud_stream*)s;
+
+	PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+	pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+	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;
+
+	if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+				&pi->output_latency_ms) == PJ_SUCCESS)
+		pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+	if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+				&pi->input_latency_ms) == PJ_SUCCESS)
+		pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_get_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, void *pval)
+{
+	// struct tapi_aud_stream *strm = (struct tapi_aud_stream*)s;
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_set_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, const void *pval)
+{
+	// struct tapi_aud_stream *strm = (struct tapi_aud_stream*)s;
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_start(pjmedia_aud_stream *s)
+{
+	struct tapi_aud_stream *strm = (struct tapi_aud_stream*)s;
+	pj_uint32_t dev_idx;
+
+	TRACE_((THIS_FILE, "stream_start()"));
+
+	dev_idx = strm->param.rec_id;
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_stop(pjmedia_aud_stream *s)
+{
+	struct tapi_aud_stream *strm = (struct tapi_aud_stream*)s;
+	tapi_ctx *dev_ctx = strm->dev_ctx;
+	pj_uint32_t dev_idx;
+
+	TRACE_((THIS_FILE, "stream_stop()"));
+	dev_idx = strm->param.rec_id;
+
+	if (tapi_dev_codec_control(dev_ctx->ch_fd[dev_idx], 0) != PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - codec start failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static pj_status_t
+stream_destroy(pjmedia_aud_stream *s)
+{
+	pj_status_t state = PJ_SUCCESS;
+	struct tapi_aud_stream *stream = (struct tapi_aud_stream*)s;
+	pj_pool_t *pool;
+
+	PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+	TRACE_((THIS_FILE, "stream_destroy()"));
+
+	stream_stop(&stream->base);
+	stream->run_flag = 0;
+
+	if (stream->thread)
+	{
+		pj_thread_join(stream->thread);
+		pj_thread_destroy(stream->thread);
+		stream->thread = NULL;
+	}
+
+	pool = stream->pool;
+	pj_bzero(stream, sizeof(stream));
+	pj_pool_release(pool);
+
+	return state;
+}
+
+pj_status_t
+tapi_hook_status(pj_uint8_t port, pj_int32_t *status)
+{
+	PJ_ASSERT_RETURN(port < TAPI_AUDIO_PORT_NUM, PJ_EINVAL);
+
+	if (ioctl(ch_fd[port], IFX_TAPI_LINE_HOOK_STATUS_GET, status)
+			!= PJ_SUCCESS) {
+		TRACE_((THIS_FILE, "ERROR - IFX_TAPI_LINE_HOOK_STATUS_GET ioctl failed!"));
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+pj_status_t
+tapi_ring(pj_uint8_t port, pj_uint8_t state, char *caller_number)
+{
+	PJ_ASSERT_RETURN(port < TAPI_AUDIO_PORT_NUM, PJ_EINVAL);
+
+	if (state) {
+		if (tapi_cid_type && caller_number) {
+			IFX_TAPI_CID_MSG_t cid_msg;
+			IFX_TAPI_CID_MSG_ELEMENT_t cid_msg_el[1];
+			memset(&cid_msg, 0, sizeof(cid_msg));
+			memset(&cid_msg_el, 0, sizeof(cid_msg_el));
+
+			cid_msg_el[0].string.elementType = IFX_TAPI_CID_ST_CLI;
+			cid_msg_el[0].string.len = strlen(caller_number);
+			strncpy(cid_msg_el[0].string.element, caller_number, sizeof(cid_msg_el[0].string.element));
+
+			cid_msg.txMode = IFX_TAPI_CID_HM_ONHOOK;
+			cid_msg.messageType = IFX_TAPI_CID_MT_CSUP;
+			cid_msg.nMsgElements = 1;
+			cid_msg.message = cid_msg_el;
+			ioctl(ch_fd[port], IFX_TAPI_CID_TX_SEQ_START, &cid_msg);
+		} else {
+			ioctl(ch_fd[port], IFX_TAPI_RING_START, 0);
+		}
+	} else {
+		ioctl(ch_fd[port], IFX_TAPI_RING_STOP, 0);
+	}
+
+	return PJ_SUCCESS;
+}
+
+pj_status_t
+tapi_tone(pj_uint8_t port, pj_uint8_t code)
+{
+	PJ_ASSERT_RETURN(port < TAPI_AUDIO_PORT_NUM, PJ_EINVAL);
+
+	if (tapi_locale && code)
+		ioctl(ch_fd[port], IFX_TAPI_TONE_LOCAL_PLAY, code);
+	else if (code)
+		ioctl(ch_fd[port], IFX_TAPI_TONE_LOCAL_PLAY, TAPI_TONE_LOCALE_NONE);
+	else
+		ioctl(ch_fd[port], IFX_TAPI_TONE_LOCAL_PLAY, 0);
+
+	return PJ_SUCCESS;
+}
+
+#endif /* PJMEDIA_AUDIO_DEV_HAS_TAPI_DEVICE */
