/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 *
 * 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 <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>

#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
#  error The PJSUA_MEDIA_HAS_PJMEDIA should be declared as zero
#endif


#define THIS_FILE               "alt_pjsua_aud.c"
#define UNIMPLEMENTED(func)     PJ_LOG(2,(THIS_FILE, "*** Call to unimplemented function %s ***", #func));


/*****************************************************************************
 * Our dummy codecs. Since we won't use any PJMEDIA codecs, we need to declare
 * our own codecs and register them to PJMEDIA's codec manager. We just need
 * the info so that they can be listed in SDP. The encoding and decoding will
 * happen in your third party media stream and will not use these codecs,
 * hence the "dummy" name.
 */
static struct alt_codec
{
    pj_str_t    encoding_name;
    pj_uint8_t  payload_type;
    unsigned    clock_rate;
    unsigned    channel_cnt;
    unsigned    frm_ptime;
    unsigned    avg_bps;
    unsigned    max_bps;
} codec_list[] =
{
    /* G.729 */
    { { "G729", 4 }, 18, 8000, 1, 10, 8000, 8000 },
    /* PCMU */
    { { "PCMU", 4 }, 0, 8000, 1, 10, 64000, 64000 },
    /* Our proprietary high end low bit rate (5kbps) codec, if you wish */
    { { "FOO", 3 }, PJMEDIA_RTP_PT_START+0, 16000, 1, 20, 5000, 5000 },
};

static struct alt_codec_factory
{
    pjmedia_codec_factory       base;
} alt_codec_factory;

static pj_status_t alt_codec_test_alloc( pjmedia_codec_factory *factory,
                                         const pjmedia_codec_info *id )
{
    unsigned i;
    for (i=0; i<PJ_ARRAY_SIZE(codec_list); ++i) {
        if (pj_stricmp(&id->encoding_name, &codec_list[i].encoding_name)==0)
            return PJ_SUCCESS;
    }
    return PJ_ENOTSUP;
}

static pj_status_t alt_codec_default_attr( pjmedia_codec_factory *factory,
                                           const pjmedia_codec_info *id,
                                           pjmedia_codec_param *attr )
{
    struct alt_codec *ac;
    unsigned i;

    PJ_UNUSED_ARG(factory);

    for (i=0; i<PJ_ARRAY_SIZE(codec_list); ++i) {
        if (pj_stricmp(&id->encoding_name, &codec_list[i].encoding_name)==0)
            break;
    }
    if (i == PJ_ARRAY_SIZE(codec_list))
        return PJ_ENOTFOUND;

    ac = &codec_list[i];

    pj_bzero(attr, sizeof(pjmedia_codec_param));
    attr->info.clock_rate = ac->clock_rate;
    attr->info.channel_cnt = ac->channel_cnt;
    attr->info.avg_bps = ac->avg_bps;
    attr->info.max_bps = ac->max_bps;
    attr->info.pcm_bits_per_sample = 16;
    attr->info.frm_ptime = ac->frm_ptime;
    attr->info.pt = ac->payload_type;

    attr->setting.frm_per_pkt = 1;
    attr->setting.vad = 1;
    attr->setting.plc = 1;

    return PJ_SUCCESS;
}

static pj_status_t alt_codec_enum_codecs(pjmedia_codec_factory *factory,
                                         unsigned *count,
                                         pjmedia_codec_info codecs[])
{
    unsigned i;

    for (i=0; i<*count && i<PJ_ARRAY_SIZE(codec_list); ++i) {
        struct alt_codec *ac = &codec_list[i];
        pj_bzero(&codecs[i], sizeof(pjmedia_codec_info));
        codecs[i].encoding_name = ac->encoding_name;
        codecs[i].pt = ac->payload_type;
        codecs[i].type = PJMEDIA_TYPE_AUDIO;
        codecs[i].clock_rate = ac->clock_rate;
        codecs[i].channel_cnt = ac->channel_cnt;
    }

    *count = i;

    return PJ_SUCCESS;
}

static pj_status_t alt_codec_alloc_codec(pjmedia_codec_factory *factory,
                                         const pjmedia_codec_info *id,
                                         pjmedia_codec **p_codec)
{
    /* This will never get called since we won't be using this codec */
    UNIMPLEMENTED(alt_codec_alloc_codec)
    return PJ_ENOTSUP;
}

static pj_status_t alt_codec_dealloc_codec( pjmedia_codec_factory *factory,
                                            pjmedia_codec *codec )
{
    /* This will never get called */
    UNIMPLEMENTED(alt_codec_dealloc_codec)
    return PJ_ENOTSUP;
}

static pj_status_t alt_codec_deinit(void)
{
    pjmedia_codec_mgr *codec_mgr;
    codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
    return pjmedia_codec_mgr_unregister_factory(codec_mgr,
                                                &alt_codec_factory.base);

}

static pjmedia_codec_factory_op alt_codec_factory_op =
{
    &alt_codec_test_alloc,
    &alt_codec_default_attr,
    &alt_codec_enum_codecs,
    &alt_codec_alloc_codec,
    &alt_codec_dealloc_codec,
    &alt_codec_deinit
};


/*****************************************************************************
 * API
 */

/* Initialize third party media library. */
pj_status_t pjsua_aud_subsys_init()
{
    pjmedia_codec_mgr *codec_mgr;
    pj_status_t status;

    /* Register our "dummy" codecs */
    alt_codec_factory.base.op = &alt_codec_factory_op;
    codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
    status = pjmedia_codec_mgr_register_factory(codec_mgr,
                                                &alt_codec_factory.base);
    if (status != PJ_SUCCESS)
        return status;

    /* TODO: initialize your evil library here */
    return PJ_SUCCESS;
}

/* Start (audio) media library. */
pj_status_t pjsua_aud_subsys_start(void)
{
    /* TODO: */
    return PJ_SUCCESS;
}

/* Cleanup and deinitialize third party media library. */
pj_status_t pjsua_aud_subsys_destroy()
{
    /* TODO: */
    return PJ_SUCCESS;
}

/* Our callback to receive incoming RTP packets */
static void aud_rtp_cb(void *user_data, void *pkt, pj_ssize_t size)
{
    pjsua_call_media *call_med = (pjsua_call_media*) user_data;

    /* TODO: Do something with the packet */
    PJ_LOG(4,(THIS_FILE, "RX %d bytes audio RTP packet", (int)size));
}

/* Our callback to receive RTCP packets */
static void aud_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size)
{
    pjsua_call_media *call_med = (pjsua_call_media*) user_data;

    /* TODO: Do something with the packet here */
    PJ_LOG(4,(THIS_FILE, "RX %d bytes audio RTCP packet", (int)size));
}

/* A demo function to send dummy "RTP" packets periodically. You would not
 * need to have this function in the real app!
 */
static void timer_to_send_aud_rtp(void *user_data)
{
    pjsua_call_media *call_med = (pjsua_call_media*) user_data;
    const char *pkt = "Not RTP packet";

    if (!call_med->call || !call_med->call->inv || !call_med->tp) {
        /* Call has been disconnected. There is race condition here as
         * this cb may be called sometime after call has been disconnected */
        return;
    }

    pjmedia_transport_send_rtp(call_med->tp, pkt, strlen(pkt));

    pjsua_schedule_timer2(&timer_to_send_aud_rtp, call_med, 2000);
}

static void timer_to_send_aud_rtcp(void *user_data)
{
    pjsua_call_media *call_med = (pjsua_call_media*) user_data;
    const char *pkt = "Not RTCP packet";

    if (!call_med->call || !call_med->call->inv || !call_med->tp) {
        /* Call has been disconnected. There is race condition here as
         * this cb may be called sometime after call has been disconnected */
        return;
    }

    pjmedia_transport_send_rtcp(call_med->tp, pkt, strlen(pkt));

    pjsua_schedule_timer2(&timer_to_send_aud_rtcp, call_med, 5000);
}

/* Stop the audio stream of a call. */
void pjsua_aud_stop_stream(pjsua_call_media *call_med)
{
    /* Detach our RTP/RTCP callbacks from transport */
    if (call_med->tp) {
        pjmedia_transport_detach(call_med->tp, call_med);
    }

    /* TODO: destroy your audio stream here */
}

/*
 * This function is called whenever SDP negotiation has completed
 * successfully. Here you'd want to start your audio stream
 * based on the info in the SDPs.
 */
pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med,
                                     pj_pool_t *tmp_pool,
                                     pjmedia_stream_info *si,
                                     const pjmedia_sdp_session *local_sdp,
                                     const pjmedia_sdp_session *remote_sdp)
{
    pj_status_t status = PJ_SUCCESS;

    PJ_LOG(4,(THIS_FILE,"Alt audio channel update.."));
    pj_log_push_indent();

    /* Check if no media is active */
    if (si->dir != PJMEDIA_DIR_NONE) {
        /* Attach our RTP and RTCP callbacks to the media transport */
        status = pjmedia_transport_attach(call_med->tp, call_med,
                                          &si->rem_addr, &si->rem_rtcp,
                                          pj_sockaddr_get_len(&si->rem_addr),
                                          &aud_rtp_cb, &aud_rtcp_cb);

        /* For a demonstration, let's use a timer to send "RTP" packet
         * periodically.
         */
        pjsua_schedule_timer2(&timer_to_send_aud_rtp, call_med, 0);
        pjsua_schedule_timer2(&timer_to_send_aud_rtcp, call_med, 2500);

        /* TODO:
         *   - Create and start your media stream based on the parameters
         *     in si
         */
    }

on_return:
    pj_log_pop_indent();
    return status;
}

void pjsua_check_snd_dev_idle()
{
}

/*****************************************************************************
 *
 * Call API which MAY need to be re-implemented if different backend is used.
 */

/* Check if call has an active media session. */
PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
{
    UNIMPLEMENTED(pjsua_call_has_media)
    return PJ_TRUE;
}


/* Get the conference port identification associated with the call. */
PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
{
    UNIMPLEMENTED(pjsua_call_get_conf_port)
    return PJSUA_INVALID_ID;
}

/* Modify audio stream's codec parameters. */
PJ_DEF(pj_status_t)
pjsua_call_aud_stream_modify_codec_param(pjsua_call_id call_id,
                                         int med_idx,
                                         const pjmedia_codec_param *param)
{
    UNIMPLEMENTED(pjsua_call_aud_stream_modify_codec_param)
    return PJ_ENOTSUP;
}

/* Get media stream info for the specified media index. */
PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id,
                                                unsigned med_idx,
                                                pjsua_stream_info *psi)
{
    pj_bzero(psi, sizeof(*psi));
    UNIMPLEMENTED(pjsua_call_get_stream_info)
    return PJ_ENOTSUP;
}

/* Get media stream statistic for the specified media index.  */
PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
                                                unsigned med_idx,
                                                pjsua_stream_stat *stat)
{
    pj_bzero(stat, sizeof(*stat));
    UNIMPLEMENTED(pjsua_call_get_stream_stat)
    return PJ_ENOTSUP;
}

/*
 * Send DTMF digits to remote using RFC 2833 payload formats.
 */
PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id,
                                          const pj_str_t *digits)
{
    UNIMPLEMENTED(pjsua_call_dial_dtmf)
    return PJ_ENOTSUP;
}

PJ_DEF(pj_status_t) pjsua_call_dial_dtmf2( pjsua_call_id call_id,
                                          const pj_str_t *digits,
                                          unsigned duration)
{
    UNIMPLEMENTED(pjsua_call_dial_dtmf2)
    return PJ_ENOTSUP;
}

/*****************************************************************************
 * Below are auxiliary API that we don't support (feel free to implement them
 * with the other media stack)
 */

PJ_DEF(void) pjsua_conf_connect_param_default(pjsua_conf_connect_param *prm)
{
    UNIMPLEMENTED(pjsua_conf_connect_param_default)
}

/* Get maximum number of conference ports. */
PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
{
    UNIMPLEMENTED(pjsua_conf_get_max_ports)
    return 0xFF;
}

/* Get current number of active ports in the bridge. */
PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
{
    UNIMPLEMENTED(pjsua_conf_get_active_ports)
    return 0;
}

/* Enumerate all conference ports. */
PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
                                          unsigned *count)
{
    *count = 0;
    UNIMPLEMENTED(pjsua_enum_conf_ports)
    return PJ_ENOTSUP;
}

/* Get information about the specified conference port */
PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
                                              pjsua_conf_port_info *info)
{
    UNIMPLEMENTED(pjsua_conf_get_port_info)
    return PJ_ENOTSUP;
}

/* Add arbitrary media port to PJSUA's conference bridge. */
PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
                                         pjmedia_port *port,
                                         pjsua_conf_port_id *p_id)
{
    *p_id = PJSUA_INVALID_ID;
    UNIMPLEMENTED(pjsua_conf_add_port)
    /* We should return PJ_ENOTSUP here, but this API is needed by pjsua
     * application or otherwise it will refuse to start.
     */
    return PJ_SUCCESS;
}

/* Remove arbitrary slot from the conference bridge. */
PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
{
    UNIMPLEMENTED(pjsua_conf_remove_port)
    return PJ_ENOTSUP;
}

/* Establish unidirectional media flow from souce to sink. */
PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
                                        pjsua_conf_port_id sink)
{
    UNIMPLEMENTED(pjsua_conf_connect)
    return PJ_ENOTSUP;
}

/*
 * Establish unidirectional media flow from souce to sink, with signal
 * level adjustment.
 */
PJ_DEF(pj_status_t) pjsua_conf_connect2( pjsua_conf_port_id source,
                                         pjsua_conf_port_id sink,
                                         const pjsua_conf_connect_param *prm)
{
    UNIMPLEMENTED(pjsua_conf_connect2)
    return PJ_ENOTSUP;
}

/* Disconnect media flow from the source to destination port. */
PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
                                           pjsua_conf_port_id sink)
{
    UNIMPLEMENTED(pjsua_conf_disconnect)
    return PJ_ENOTSUP;
}

/* Adjust the signal level to be transmitted from the bridge to the
 * specified port by making it louder or quieter.
 */
PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
                                               float level)
{
    UNIMPLEMENTED(pjsua_conf_adjust_tx_level)
    return PJ_ENOTSUP;
}

/* Adjust the signal level to be received from the specified port (to
 * the bridge) by making it louder or quieter.
 */
PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
                                               float level)
{
    UNIMPLEMENTED(pjsua_conf_adjust_rx_level)
    return PJ_ENOTSUP;
}


/* Get last signal level transmitted to or received from the specified port. */
PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
                                                unsigned *tx_level,
                                                unsigned *rx_level)
{
    UNIMPLEMENTED(pjsua_conf_get_signal_level)
    return PJ_ENOTSUP;
}

/* Create a file player, and automatically connect this player to
 * the conference bridge.
 */
PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
                                         unsigned options,
                                         pjsua_player_id *p_id)
{
    UNIMPLEMENTED(pjsua_player_create)
    return PJ_ENOTSUP;
}

/* Create a file playlist media port, and automatically add the port
 * to the conference bridge.
 */
PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
                                           unsigned file_count,
                                           const pj_str_t *label,
                                           unsigned options,
                                           pjsua_player_id *p_id)
{
    UNIMPLEMENTED(pjsua_playlist_create)
    return PJ_ENOTSUP;
}

/* Get conference port ID associated with player. */
PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
{
    UNIMPLEMENTED(pjsua_player_get_conf_port)
    return -1;
}

/* Get the media port for the player. */
PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
                                           pjmedia_port **p_port)
{
    UNIMPLEMENTED(pjsua_player_get_port)
    return PJ_ENOTSUP;
}

/* Get number of bits per sample of the WAV payload */
PJ_DEF(pj_status_t) pjsua_player_get_info(pjsua_player_id id,
                                          pjmedia_wav_player_info *info)
{
    UNIMPLEMENTED(pjsua_player_get_info)
    return PJ_ENOTSUP;
}

/* Get position in samples */
PJ_DEF(pj_ssize_t) pjsua_player_get_pos(pjsua_player_id id)
{
    UNIMPLEMENTED(pjsua_player_get_pos)
    return -PJ_ENOTSUP;
}

/* Set playback position. */
PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
                                          pj_uint32_t samples)
{
    UNIMPLEMENTED(pjsua_player_set_pos)
    return PJ_ENOTSUP;
}

/* Close the file, remove the player from the bridge, and free
 * resources associated with the file player.
 */
PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
{
    UNIMPLEMENTED(pjsua_player_destroy)
    return PJ_ENOTSUP;
}

/* Create a file recorder, and automatically connect this recorder to
 * the conference bridge.
 */
PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
                                           unsigned enc_type,
                                           void *enc_param,
                                           pj_ssize_t max_size,
                                           unsigned options,
                                           pjsua_recorder_id *p_id)
{
    UNIMPLEMENTED(pjsua_recorder_create)
    return PJ_ENOTSUP;
}


/* Get conference port associated with recorder. */
PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
{
    UNIMPLEMENTED(pjsua_recorder_get_conf_port)
    return -1;
}

/* Get the media port for the recorder. */
PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
                                             pjmedia_port **p_port)
{
    UNIMPLEMENTED(pjsua_recorder_get_port)
    return PJ_ENOTSUP;
}

/* Destroy recorder (this will complete recording). */
PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
{
    UNIMPLEMENTED(pjsua_recorder_destroy)
    return PJ_ENOTSUP;
}

/* Enum sound devices. */
PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
                                         unsigned *count)
{
    UNIMPLEMENTED(pjsua_enum_aud_devs)
    return PJ_ENOTSUP;
}

PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
                                         unsigned *count)
{
    UNIMPLEMENTED(pjsua_enum_snd_devs)
    return PJ_ENOTSUP;
}

/* Select or change sound device. */
PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev, int playback_dev)
{
    UNIMPLEMENTED(pjsua_set_snd_dev)
    return PJ_SUCCESS;
}

/* Get currently active sound devices. */
PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev, int *playback_dev)
{
    *capture_dev = *playback_dev = PJSUA_INVALID_ID;
    UNIMPLEMENTED(pjsua_get_snd_dev)
    return PJ_ENOTSUP;
}

/* Get sound dev parameters such as playback & capture device IDs and mode. */
PJ_DEF(pj_status_t) pjsua_get_snd_dev2(pjsua_snd_dev_param *snd_param)
{
    UNIMPLEMENTED(pjsua_get_snd_dev2)
    return PJ_ENOTSUP;
}

/*
 * Select or change sound device. Application may call this function at
 * any time to replace current sound device.
 */
PJ_DEF(pj_status_t) pjsua_set_snd_dev2(const pjsua_snd_dev_param *snd_param)
{
    UNIMPLEMENTED(pjsua_set_snd_dev2)
    return PJ_SUCCESS;
}

/* Use null sound device. Alt media backend has no real sound device,
 * so this is a no-op that always succeeds. */
PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
{
    return PJ_SUCCESS;
}

/* Use no device! */
PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
{
    UNIMPLEMENTED(pjsua_set_no_snd_dev)
    return NULL;
}

/* Configure the AEC settings of the sound port. */
PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
{
    UNIMPLEMENTED(pjsua_set_ec)
    return PJ_ENOTSUP;
}

/* Get current AEC tail length. */
PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
{
    UNIMPLEMENTED(pjsua_get_ec_tail)
    return PJ_ENOTSUP;
}

/* Check whether the sound device is currently active. */
PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
{
    UNIMPLEMENTED(pjsua_snd_is_active)
    return PJ_FALSE;
}

/* Configure sound device setting to the sound device being used. */
PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
                                           const void *pval, pj_bool_t keep)
{
    UNIMPLEMENTED(pjsua_snd_set_setting)
    return PJ_ENOTSUP;
}

/* Retrieve a sound device setting. */
PJ_DEF(pj_status_t) pjsua_snd_get_setting(pjmedia_aud_dev_cap cap, void *pval)
{
    UNIMPLEMENTED(pjsua_snd_get_setting)
    return PJ_ENOTSUP;
}

/* Create an extra sound device and register it to conference bridge. */
PJ_DEF(pj_status_t) pjsua_ext_snd_dev_create( pjmedia_snd_port_param *param,
                                              pjsua_ext_snd_dev **p_snd)
{
    UNIMPLEMENTED(pjsua_ext_snd_dev_create)
    return PJ_ENOTSUP;
}

/* Destroy an extra sound device and unregister it from conference bridge. */
PJ_DEF(pj_status_t) pjsua_ext_snd_dev_destroy(pjsua_ext_snd_dev *snd)
{
    UNIMPLEMENTED(pjsua_ext_snd_dev_destroy)
    return PJ_ENOTSUP;
}

/* Get sound port instance of an extra sound device. */
PJ_DEF(pjmedia_snd_port*) pjsua_ext_snd_dev_get_snd_port(
                                            pjsua_ext_snd_dev *snd)
{
    UNIMPLEMENTED(pjsua_ext_snd_dev_get_snd_port)
    return NULL;
}

/* Get conference port ID of an extra sound device. */
PJ_DEF(pjsua_conf_port_id) pjsua_ext_snd_dev_get_conf_port(
                                            pjsua_ext_snd_dev *snd)
{
    UNIMPLEMENTED(pjsua_ext_snd_dev_get_conf_port)
    return PJSUA_INVALID_ID;
}
