/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <pjmedia/stream.h>
#include <pjmedia/errno.h>
#include <pjmedia/rtp.h>
#include <pjmedia/rtcp.h>
#include <pjmedia/jbuf.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/compat/socket.h>
#include <pj/errno.h>
#include <pj/ioqueue.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/sock_select.h>
#include <pj/string.h>      /* memcpy() */


#define THIS_FILE                       "stream.c"
#define AUDIO_STREAM
#define ERRLEVEL                        1
#define LOGERR_(expr)                   PJ_PERROR(4,expr);
#define TRC_(expr)                      PJ_LOG(5,expr)

#define BYTES_PER_SAMPLE                2

/* Limit the number of synthetic audio samples that are generated by PLC.
 * Normally PLC should have it's own means to limit the number of
 * synthetic frames, so we need to set this to a reasonably large value
 * just as precaution
 */
#define MAX_PLC_MSEC                    PJMEDIA_MAX_PLC_DURATION_MSEC


/* Enable/disable trace. */
#define TRACE_JB                        PJMEDIA_STREAM_TRACE_JB
/* Optional path/prefix for the CSV filename. */
#define TRACE_JB_PATH_PREFIX            ""

#ifndef PJMEDIA_STREAM_SIZE
#   define PJMEDIA_STREAM_SIZE  4000
#endif

#ifndef PJMEDIA_STREAM_INC
#   define PJMEDIA_STREAM_INC   4000
#endif

/* Default number of DTMF E bit transmissions */
#define DTMF_EBIT_RETRANSMIT_CNT        3

/*  Number of send error before repeat the report. */
#define SEND_ERR_COUNT_TO_REPORT        50


struct dtmf
{
    int             event;
    pj_uint32_t     duration;
    pj_uint32_t     send_duration;
    unsigned        ebit_cnt;               /**< # of E bit transmissions   */
};


/**
 * This structure describes media stream.
 * A media stream is bidirectional media transmission between two endpoints.
 * It consists of two channels, i.e. encoding and decoding channels.
 * A media stream corresponds to a single "m=" line in a SDP session
 * description.
 */
struct pjmedia_stream
{
    pjmedia_stream_common    base;

    pjmedia_codec_mgr       *codec_mgr;     /**< Codec manager instance.    */
    pjmedia_stream_info      si;            /**< Creation parameter.        */

    pjmedia_codec           *codec;         /**< Codec instance being used. */
    pjmedia_codec_param      codec_param;   /**< Codec param.               */
    pj_int16_t              *enc_buf;       /**< Encoding buffer, when enc's
                                                 ptime is different than dec.
                                                 Otherwise it's NULL.       */

    unsigned                 enc_samples_per_pkt;
    unsigned                 enc_buf_size;  /**< Encoding buffer size, in
                                                 samples.                   */
    unsigned                 enc_buf_pos;   /**< First position in buf.     */
    unsigned                 enc_buf_count; /**< Number of samples in the
                                                 encoding buffer.           */

    pj_int16_t              *dec_buf;       /**< Decoding buffer.           */
    unsigned                 dec_buf_size;  /**< Decoding buffer size, in
                                                 samples.                   */
    unsigned                 dec_buf_pos;   /**< First position in buf.     */
    unsigned                 dec_buf_count; /**< Number of samples in the
                                                 decoding buffer.           */

    volatile pj_uint16_t     dec_ptime;     /**< Decoder frame ptime in ms. */
    volatile pj_uint8_t      dec_ptime_denum;/**< Decoder ptime denum.      */
    pj_bool_t                detect_ptime_change;
                                            /**< Detect decode ptime change */

    unsigned                 plc_cnt;       /**< # of consecutive PLC frames*/
    unsigned                 max_plc_cnt;   /**< Max # of PLC frames        */

    unsigned                 vad_enabled;   /**< VAD enabled in param.      */
    unsigned                 frame_size;    /**< Size of encoded base frame.*/
    pj_bool_t                is_streaming;  /**< Currently streaming?. This
                                                 is used to put RTP marker
                                                 bit.                       */
    pj_uint32_t              ts_vad_disabled;/**< TS when VAD was disabled. */
    pj_uint32_t              tx_duration;   /**< TX duration in timestamp.  */

    unsigned                 soft_start_cnt;/**< Stream soft start counter */

    pj_int16_t              *zero_frame;    /**< Zero frame buffer.         */

    /* RFC 2833 DTMF transmission queue: */
    unsigned                 dtmf_duration; /**< DTMF duration(in timestamp)*/
    int                      tx_event_pt;   /**< Outgoing pt for dtmf.      */
    int                      tx_dtmf_count; /**< # of digits in tx dtmf buf.*/
    struct dtmf              tx_dtmf_buf[32];/**< Outgoing dtmf queue.      */
    pj_uint32_t              tx_dtmf_pause_dur;
                                            /**< Outgoing DTMF full pause
                                                 (in timestamp).            */
    pj_int8_t                tx_dtmf_vol;   /**< Outgoing DTMF volume.      */
    pj_uint32_t              tx_dtmf_ebit_rep_cnt;
                                            /**< Outgoing DTMF end bit
                                                 packet repetition count.   */
    pj_uint32_t              tx_dtmf_pause_rem;
                                            /**< Outgoing DTMF rem. pause.  */

    /* Incoming DTMF: */
    int                      rx_event_pt;   /**< Incoming pt for dtmf.      */
    int                      last_dtmf;     /**< Current digit, or -1.      */
    pj_uint32_t              last_dtmf_dur; /**< Start ts for cur digit.    */
    pj_bool_t                last_dtmf_ended;
    unsigned                 rx_dtmf_count; /**< # of digits in dtmf rx buf.*/
    char                     rx_dtmf_buf[32];/**< Incoming DTMF buffer.     */

    /* DTMF callback */
    void                    (*dtmf_cb)(pjmedia_stream*, void*, int);
    void                     *dtmf_cb_user_data;

    void                    (*dtmf_event_cb)(pjmedia_stream*, void*,
                                             const pjmedia_stream_dtmf_event*);
    void                     *dtmf_event_cb_user_data;

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
    /* Enable support to handle codecs with inconsistent clock rate
     * between clock rate in SDP/RTP & the clock rate that is actually used.
     * This happens for example with G.722 and MPEG audio codecs.
     */
    pj_bool_t                has_g722_mpeg_bug;
                                            /**< Flag to specify whether
                                                 normalization process
                                                 is needed                  */
    unsigned                 rtp_tx_ts_len_per_pkt;
                                            /**< Normalized ts length per packet
                                                 transmitted according to
                                                 'erroneous' definition     */
    unsigned                 rtp_rx_ts_len_per_frame;
                                            /**< Normalized ts length per frame
                                                 received according to
                                                 'erroneous' definition     */
    unsigned                 rtp_rx_last_cnt;/**< Nb of frames in last pkt  */
    unsigned                 rtp_rx_check_cnt;
                                            /**< Counter of remote timestamp
                                                 checking */
#endif

};


/* RFC 2833 digit */
static const char digitmap[17] = { '0', '1', '2', '3',
                                   '4', '5', '6', '7',
                                   '8', '9', '*', '#',
                                   'A', 'B', 'C', 'D',
                                   'R'};

/* Zero audio frame samples */
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];


static void on_rx_rtcp( void *data,
                        void *pkt,
                        pj_ssize_t bytes_read);


#include "stream_imp_common.c"


/* Generate synthetic samples using PLC or zero-fill.
 * This function may leave samples in the decoder buffer.
 * Return 1 if PLC is invoked, otherwise return 0.
 */
static int synthesize_samples(pjmedia_stream *stream,
                              unsigned samples_required,
                              unsigned samples_per_decode,
                              pjmedia_frame* frame_out)
{
    pjmedia_frame frame_out_;
    unsigned samples_count = 0, out_buf_len;
    pj_status_t status;

    /* Verify the output frame size */
    out_buf_len = (unsigned)frame_out->size / 2;
    if (samples_required > out_buf_len) {
        PJ_LOG(5, (stream->base.port.info.name.ptr,
                   "Bad params in synthesize samples: "
                   "required=%u decode=%u, buf-size=%u",
                   samples_required, samples_per_decode, out_buf_len));
        pjmedia_zero_samples(frame_out->buf, out_buf_len);
        return 0;
    }

    /* Zero-fill when:
     * - PLC is not supported/active, or
     * - PLC limit has been reached.
     */
    if (!stream->codec->op->recover ||
        !stream->codec_param.setting.plc ||
        stream->plc_cnt >= stream->max_plc_cnt)
    {
        pjmedia_zero_samples(frame_out->buf, samples_required);
        return 0;
    }

    /* Decode to decoder buffer when samples_per_decode > samples_required */
    if (stream->dec_buf && samples_per_decode > samples_required) {
        ++stream->plc_cnt;
        frame_out_.buf  = stream->dec_buf;
        frame_out_.size = stream->dec_buf_size;
        status = pjmedia_codec_recover(stream->codec,
                                        (unsigned)frame_out_.size,
                                        &frame_out_);
        if (status != PJ_SUCCESS) {
            PJ_PERROR(5, (stream->base.port.info.name.ptr, status,
                          "Codec recover failed"));
            pjmedia_zero_samples(frame_out->buf, samples_required);
            return 1;
        }

        /* Copy samples from the decoder buffer */
        if (frame_out_.size / 2 > samples_required) {
            pjmedia_copy_samples(frame_out->buf, stream->dec_buf,
                                 samples_required);

            /* Leave the rest in the decoder buffer */
            stream->dec_buf_pos = samples_required;
            stream->dec_buf_count = (unsigned)frame_out_.size / 2;
        } else {
            /* Not enough samples generated, copy whatever we have */
            unsigned samples_avail = (unsigned)frame_out_.size / 2;
            pjmedia_copy_samples(frame_out->buf, stream->dec_buf,
                                 samples_avail);
            pjmedia_zero_samples((pj_int16_t*)frame_out->buf + samples_avail,
                                 samples_required - samples_avail);
        }

        return 1;
    }

    /* Decode directly to output frame */
    frame_out_ = *frame_out;
    do {
        ++stream->plc_cnt;
        status = pjmedia_codec_recover(stream->codec,
                                       (unsigned)frame_out_.size,
                                       &frame_out_);
        if (status != PJ_SUCCESS) {
            PJ_PERROR(5, (stream->base.port.info.name.ptr, status,
                          "Codec recover failed"));
            break;
        }

        samples_count += (unsigned)frame_out_.size / 2;
        frame_out_.buf = (pj_int16_t*)frame_out->buf + samples_count;
        frame_out_.size = frame_out->size - samples_count*2;
    } while (samples_count < samples_required &&
             stream->plc_cnt < stream->max_plc_cnt);

    /* Fill the rest with zeroes after PLC fails or over limit */
    if (samples_count < samples_required) {
        pjmedia_zero_samples((pj_int16_t*)frame_out->buf + samples_count,
                             samples_required - samples_count);
    }

    return 1;
}


/*
 * play_callback()
 *
 * This callback is called by sound device's player thread when it
 * needs to feed the player with some frames.
 */
static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame)
{
    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_channel *channel = c_strm->dec;
    unsigned samples_count, samples_per_frame, samples_required;
    pj_int16_t *p_out_samp;
    pj_uint32_t rtp_ts = 0;
    pj_status_t status;


    /* Return no frame is channel is paused */
    if (channel->paused) {
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
        return PJ_SUCCESS;
    }

    if (stream->soft_start_cnt) {
        if (stream->soft_start_cnt == PJMEDIA_STREAM_SOFT_START) {
            PJ_LOG(4,(c_strm->port.info.name.ptr,
                      "Resetting jitter buffer in stream playback start"));
            pj_mutex_lock( c_strm->jb_mutex );
            pjmedia_jbuf_reset(c_strm->jb);
            pj_mutex_unlock( c_strm->jb_mutex );
        }
        --stream->soft_start_cnt;
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
        return PJ_SUCCESS;
    }

    /* Repeat get frame from the jitter buffer and decode the frame
     * until we have enough frames according to codec's ptime.
     */

    /* Lock jitter buffer mutex first */
    pj_mutex_lock( c_strm->jb_mutex );

    samples_required = PJMEDIA_PIA_SPF(&c_strm->port.info);
    samples_per_frame = stream->dec_ptime *
                        stream->codec_param.info.clock_rate *
                        stream->codec_param.info.channel_cnt /
                        stream->dec_ptime_denum /
                        1000;
    p_out_samp = (pj_int16_t*) frame->buf;

    for (samples_count=0; samples_count < samples_required;) {
        char frame_type;
        pj_size_t frame_size = channel->buf_size;
        pj_uint32_t bit_info;

        /* Get samples from the decoder buffer first, if any.
         * The decoder buffer is currently only used by Opus when
         * the stream frame is smaller than the decoder frame,
         * e.g: stream frame is 20ms while decoder frame is 60ms.
         */
        if (stream->dec_buf && stream->dec_buf_pos < stream->dec_buf_count) {
            unsigned nsamples_req = samples_required - samples_count;
            unsigned nsamples_avail = stream->dec_buf_count -
                                      stream->dec_buf_pos;
            unsigned nsamples_copy = PJ_MIN(nsamples_req, nsamples_avail);

            pjmedia_copy_samples(p_out_samp + samples_count,
                                 stream->dec_buf + stream->dec_buf_pos,
                                 nsamples_copy);
            samples_count += nsamples_copy;
            stream->dec_buf_pos += nsamples_copy;
            continue;
        }

        /* Get frame from jitter buffer. */
        pjmedia_jbuf_get_frame3(c_strm->jb, channel->buf, &frame_size,
                                &frame_type, &bit_info, &rtp_ts, NULL);

#if TRACE_JB
        trace_jb_get(c_strm, frame_type, frame_size);
#endif

        if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
            pjmedia_frame frame_out = {0};
            unsigned samples_needed;
            pj_bool_t plc_invoked;

            frame_out.buf = p_out_samp + samples_count;
            frame_out.size = frame->size - samples_count*2;

            /* Generate only a frame (samples_per_frame) */
            samples_needed = samples_required - samples_count;
            if (samples_needed > samples_per_frame)
                samples_needed = samples_per_frame;
            plc_invoked = synthesize_samples(stream, samples_needed,
                                             samples_per_frame, &frame_out);
            samples_count += samples_needed;

            if (frame_type != c_strm->jb_last_frm) {
                /* Report changing frame type event */
                PJ_LOG(5,(c_strm->port.info.name.ptr, "Frame lost%s!",
                          (plc_invoked? ", recovered":"")));

                c_strm->jb_last_frm = frame_type;
                c_strm->jb_last_frm_cnt = 1;
            } else {
                c_strm->jb_last_frm_cnt++;
            }

        } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {

            const char *with_plc = "";

            /* Jitter buffer is empty. If this is the first "empty" state,
             * activate PLC to smoothen the fade-out, otherwise zero
             * the frame.
             */
            //Using this "if" will only invoke PLC for the first packet
            //lost and not the subsequent ones.
            //if (frame_type != c_strm->jb_last_frm) {
            if (1) {
                pjmedia_frame frame_out = {0};
                unsigned samples_needed;

                frame_out.buf = p_out_samp + samples_count;
                frame_out.size = frame->size - samples_count*2;

                /* Generate all required (may be multiple frames) */
                samples_needed = samples_required - samples_count;
                if (synthesize_samples(stream, samples_needed,
                                       samples_per_frame, &frame_out))
                {
                    with_plc = ", plc invoked";
                }
                samples_count += samples_needed;
            }

            if (c_strm->jb_last_frm != frame_type) {
                pjmedia_jb_state jb_state;

                /* Report changing frame type event */
                pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                PJ_LOG(5,(c_strm->port.info.name.ptr,
                          "Jitter buffer empty (prefetch=%d)%s",
                          jb_state.prefetch, with_plc));

                c_strm->jb_last_frm = frame_type;
                c_strm->jb_last_frm_cnt = 1;
            } else {
                c_strm->jb_last_frm_cnt++;
            }
            break;

        } else if (frame_type != PJMEDIA_JB_NORMAL_FRAME) {

            const char *with_plc = "";
            pjmedia_frame frame_out = {0};
            unsigned samples_needed;

            /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
            pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);

            frame_out.buf = p_out_samp + samples_count;
            frame_out.size = frame->size - samples_count*2;

            /* Generate all required (may be multiple frames) */
            samples_needed = samples_required - samples_count;
            if (synthesize_samples(stream, samples_needed,
                                    samples_per_frame, &frame_out))
            {
                with_plc = ", plc invoked";
            }
            samples_count += samples_needed;

            if (c_strm->jb_last_frm != frame_type) {
                pjmedia_jb_state jb_state;

                /* Report changing frame type event */
                pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                PJ_LOG(5,(c_strm->port.info.name.ptr,
                          "Jitter buffer is bufferring (prefetch=%d)%s",
                          jb_state.prefetch, with_plc));

                c_strm->jb_last_frm = frame_type;
                c_strm->jb_last_frm_cnt = 1;
            } else {
                c_strm->jb_last_frm_cnt++;
            }
            break;

        } else {
            /* Got "NORMAL" frame from jitter buffer */
            pjmedia_frame frame_in, frame_out;
            pj_bool_t use_dec_buf = PJ_FALSE;

            stream->plc_cnt = 0;

            /* Decode */
            frame_in.buf = channel->buf;
            frame_in.size = frame_size;
            frame_in.bit_info = bit_info;
            frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;  /* ignored */

            frame_out.buf = p_out_samp + samples_count;
            frame_out.size = frame->size - samples_count*BYTES_PER_SAMPLE;

            /* Check if we need to use the decode buffer, i.e: codec is opus
             * and the decoded frame is larger than the stream frame.
             */
            if (stream->dec_buf &&
                bit_info * sizeof(pj_int16_t) > frame_out.size)
            {
                stream->dec_buf_pos = 0;
                stream->dec_buf_count = bit_info;

                use_dec_buf = PJ_TRUE;
                frame_out.buf = stream->dec_buf;
                frame_out.size = stream->dec_buf_size;
            }

            status = pjmedia_codec_decode( stream->codec, &frame_in,
                                           (unsigned)frame_out.size,
                                           &frame_out);
            if (status != 0) {
                LOGERR_((port->info.name.ptr, status,
                         "codec decode() error"));

                if (use_dec_buf) {
                    pjmedia_zero_samples(stream->dec_buf,
                                         stream->dec_buf_count);
                    stream->dec_buf_count = 0;
                } else {
                    unsigned samples_needed = samples_required - samples_count;
                    if (samples_needed > frame_out.size / 2)
                        samples_needed = (unsigned)frame_out.size / 2;
                    pjmedia_zero_samples(p_out_samp + samples_count,
                                         samples_needed);
                }
            } else if (use_dec_buf) {
                stream->dec_buf_count = (unsigned)frame_out.size /
                                        sizeof(pj_int16_t);
            }

            if (c_strm->jb_last_frm != frame_type) {
                /* Report changing frame type event */
                PJ_LOG(5,(c_strm->port.info.name.ptr,
                          "Jitter buffer starts returning normal frames "
                          "(after %d empty/lost)",
                          c_strm->jb_last_frm_cnt));

                c_strm->jb_last_frm = frame_type;
                c_strm->jb_last_frm_cnt = 1;
            } else {
                c_strm->jb_last_frm_cnt++;
            }
            if (!use_dec_buf)
                samples_count += samples_per_frame;

            /* Update synchronizer with presentation time and check if the
             * synchronizer requests for delay adjustment.
             */
            if (c_strm->av_sync_media) {
                pj_timestamp pts = { 0 };
                pj_int32_t delay_req_ms;

                pts.u32.lo = rtp_ts;
                status = pjmedia_av_sync_update_pts(c_strm->av_sync_media,
                                                    &pts, &delay_req_ms);
                if (status == PJ_SUCCESS && delay_req_ms) {
                    /* Delay adjustment is requested */
                    pjmedia_jb_state jb_state;
                    int target_delay_ms, cur_delay_ms;

                    /* Apply delay request to jitter buffer */
                    pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                    cur_delay_ms = jb_state.min_delay_set * stream->dec_ptime/
                                   stream->dec_ptime_denum;
                    target_delay_ms = cur_delay_ms + delay_req_ms;
                    if (target_delay_ms < 0)
                        target_delay_ms = 0;

                    /* Just for safety (never see in tests), target delay
                     * should not exceed 5 seconds.
                     */
                    if (target_delay_ms > 5000) {
                        PJ_LOG(5,(c_strm->port.info.name.ptr,
                                  "Ignored avsync request for excessive delay"
                                  " (current=%dms, target=%dms)!",
                                  cur_delay_ms, target_delay_ms));
                    } else if (cur_delay_ms != target_delay_ms) {
                        pjmedia_jbuf_set_min_delay(c_strm->jb,
                                                   target_delay_ms);
                        PJ_LOG(5,(c_strm->port.info.name.ptr,
                                  "Adjust audio minimal delay to %dms",
                                  target_delay_ms));
                    }
                }
            }
        }
    }


    /* Unlock jitter buffer mutex. */
    pj_mutex_unlock( c_strm->jb_mutex );

    /* Return PJMEDIA_FRAME_TYPE_NONE if we have no frames at all
     * (it can happen when jitter buffer returns PJMEDIA_JB_ZERO_EMPTY_FRAME).
     */
    if (samples_count == 0) {
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
        frame->size = 0;
    } else {
        frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
        frame->size = samples_count * BYTES_PER_SAMPLE;
        frame->timestamp.u64 = 0;
    }

    return PJ_SUCCESS;
}


/* The other version of get_frame callback used when stream port format
 * is non linear PCM.
 */
static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame)
{
    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_channel *channel = c_strm->dec;
    pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame;
    unsigned samples_per_frame, samples_required;
    pj_uint32_t rtp_ts = 0;
    pj_status_t status;

    /* Return no frame if channel is paused */
    if (channel->paused) {
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
        return PJ_SUCCESS;
    }

    /* Repeat get frame from the jitter buffer and decode the frame
     * until we have enough frames according to codec's ptime.
     */

    samples_required = PJMEDIA_PIA_SPF(&c_strm->port.info);
    samples_per_frame = stream->codec_param.info.frm_ptime *
                        stream->codec_param.info.clock_rate *
                        stream->codec_param.info.channel_cnt /
                        stream->codec_param.info.frm_ptime_denum /
                        1000;

    pj_bzero(f, sizeof(pjmedia_frame_ext));
    f->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;

    while (f->samples_cnt < samples_required) {
        char frame_type;
        pj_size_t frame_size = channel->buf_size;
        pj_uint32_t bit_info;

        /* Lock jitter buffer mutex first */
        pj_mutex_lock( c_strm->jb_mutex );

        /* Get frame from jitter buffer. */
        pjmedia_jbuf_get_frame3(c_strm->jb, channel->buf, &frame_size,
                                &frame_type, &bit_info, &rtp_ts, NULL);

#if TRACE_JB
        trace_jb_get(c_strm, frame_type, frame_size);
#endif

        /* Unlock jitter buffer mutex. */
        pj_mutex_unlock( c_strm->jb_mutex );

        if (frame_type == PJMEDIA_JB_NORMAL_FRAME) {
            /* Got "NORMAL" frame from jitter buffer */
            pjmedia_frame frame_in;

            /* Decode */
            frame_in.buf = channel->buf;
            frame_in.size = frame_size;
            frame_in.bit_info = bit_info;
            frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;

            status = pjmedia_codec_decode( stream->codec, &frame_in,
                                           0, frame);
            if (status != PJ_SUCCESS) {
                LOGERR_((port->info.name.ptr, status,
                         "codec decode() error"));
                pjmedia_frame_ext_append_subframe(f, NULL, 0,
                                            (pj_uint16_t)samples_per_frame);
            }

            if (c_strm->jb_last_frm != frame_type) {
                /* Report changing frame type event */
                PJ_LOG(5,(c_strm->port.info.name.ptr,
                          "Jitter buffer starts returning normal frames "
                          "(after %d empty/lost)",
                          c_strm->jb_last_frm_cnt));

                c_strm->jb_last_frm = frame_type;
                c_strm->jb_last_frm_cnt = 1;
            } else {
                c_strm->jb_last_frm_cnt++;
            }

            /* Update synchronizer with presentation time */
            if (c_strm->av_sync_media) {
                pj_timestamp pts = { 0 };
                pj_int32_t delay_req_ms;

                pts.u32.lo = rtp_ts;
                status = pjmedia_av_sync_update_pts(c_strm->av_sync_media,
                                                    &pts, &delay_req_ms);
                if (status == PJ_SUCCESS && delay_req_ms) {
                    /* Delay adjustment is requested */
                    pjmedia_jb_state jb_state;
                    int target_delay_ms, cur_delay_ms;

                    /* Increase delay slowly, but decrease delay quickly */
                    if (delay_req_ms > 0)
                        delay_req_ms = delay_req_ms * 3 / 4;
                    else 
                        delay_req_ms = delay_req_ms * 4 / 3;

                    /* Apply delay request to jitter buffer */
                    pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                    cur_delay_ms = jb_state.min_delay_set * stream->dec_ptime/
                                   stream->dec_ptime_denum;
                    target_delay_ms = cur_delay_ms + delay_req_ms;
                    if (target_delay_ms < 0)
                        target_delay_ms = 0;
                    pjmedia_jbuf_set_min_delay(c_strm->jb, target_delay_ms);

                    PJ_LOG(5,(c_strm->port.info.name.ptr,
                              "Adjust minimal delay to %dms",
                              target_delay_ms));
                }
            }

        } else {

            /* Try to generate frame by invoking PLC (when any) */
            status = PJ_SUCCESS;
            if (stream->codec->op->recover) {
                status = pjmedia_codec_recover(stream->codec, 0, frame);
            }

            /* No PLC or PLC failed */
            if (!stream->codec->op->recover || status != PJ_SUCCESS) {
                pjmedia_frame_ext_append_subframe(f, NULL, 0,
                                            (pj_uint16_t)samples_per_frame);
            }

            if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
                if (frame_type != c_strm->jb_last_frm) {
                    /* Report changing frame type event */
                    PJ_LOG(5,(c_strm->port.info.name.ptr, "Frame lost!"));

                    c_strm->jb_last_frm = frame_type;
                    c_strm->jb_last_frm_cnt = 1;
                } else {
                    c_strm->jb_last_frm_cnt++;
                }
            } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
                if (frame_type != c_strm->jb_last_frm) {
                    pjmedia_jb_state jb_state;

                    /* Report changing frame type event */
                    pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                    PJ_LOG(5,(c_strm->port.info.name.ptr,
                              "Jitter buffer empty (prefetch=%d)",
                              jb_state.prefetch));

                    c_strm->jb_last_frm = frame_type;
                    c_strm->jb_last_frm_cnt = 1;
                } else {
                    c_strm->jb_last_frm_cnt++;
                }
            } else {

                /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
                pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);

                if (c_strm->jb_last_frm != frame_type) {
                    pjmedia_jb_state jb_state;

                    /* Report changing frame type event */
                    pjmedia_jbuf_get_state(c_strm->jb, &jb_state);
                    PJ_LOG(5,(c_strm->port.info.name.ptr,
                              "Jitter buffer is bufferring (prefetch=%d)",
                              jb_state.prefetch));

                    c_strm->jb_last_frm = frame_type;
                    c_strm->jb_last_frm_cnt = 1;
                } else {
                    c_strm->jb_last_frm_cnt++;
                }
            }
        }
    }

    return PJ_SUCCESS;
}


/*
 * Transmit DTMF
 */
static void create_dtmf_payload(pjmedia_stream *stream,
                                struct pjmedia_frame *frame_out,
                                int forced_last, int *first, int *last)
{
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_rtp_dtmf_event *event;
    struct dtmf *digit = &stream->tx_dtmf_buf[0];
    unsigned duration = 0;

    pj_assert(sizeof(pjmedia_rtp_dtmf_event) == 4);

    if (digit->send_duration)
    {
        float ts_modifier = 1.0;
#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
        if (stream->codec_param.info.pt == PJMEDIA_RTP_PT_G722) {
            ts_modifier = 0.5;
        }
#endif
        if (!pj_stricmp2(&stream->si.fmt.encoding_name, "opus")) {
            ts_modifier = (float)48000 / stream->codec_param.info.clock_rate;
        }
        duration = (unsigned)(ts_modifier * digit->send_duration *
                              stream->codec_param.info.clock_rate / 1000);
    }
    else
    {
        duration = stream->dtmf_duration;
    }
    *first = *last = 0;

    event = (pjmedia_rtp_dtmf_event*) frame_out->buf;

    if (digit->duration == 0) {
        PJ_LOG(4,(c_strm->port.info.name.ptr, "Sending DTMF digit id %c",
                  digitmap[digit->event]));
        *first = 1;
    }

    digit->duration += stream->rtp_tx_ts_len_per_pkt;
    if (digit->duration >= duration)
        digit->duration = duration;

    event->event = (pj_uint8_t)digit->event;
    event->e_vol = PJ_ABS(stream->tx_dtmf_vol);
    event->duration = pj_htons((pj_uint16_t)digit->duration);

    if (forced_last) {
        digit->duration = duration;
    }

    if (digit->duration >= duration) {
        event->e_vol |= 0x80;

        if (++digit->ebit_cnt >= (1U + stream->tx_dtmf_ebit_rep_cnt)) {
            *last = 1;

            /* Prepare next digit. */
            pj_mutex_lock(c_strm->jb_mutex);

            pj_array_erase(stream->tx_dtmf_buf, sizeof(stream->tx_dtmf_buf[0]),
                           stream->tx_dtmf_count, 0);
            --stream->tx_dtmf_count;

            /* add pause after each completed digit */
            stream->tx_dtmf_pause_rem = stream->tx_dtmf_pause_dur;

            pj_mutex_unlock(c_strm->jb_mutex);
        }
    }

    frame_out->size = 4;
}


/*
 * Process (count down) DTMF pause
 */
static void process_dtmf_pause(pjmedia_stream *stream)
{
    if (stream->tx_dtmf_pause_rem > stream->rtp_tx_ts_len_per_pkt) {
        stream->tx_dtmf_pause_rem -= stream->rtp_tx_ts_len_per_pkt;
    }
    else {
        stream->tx_dtmf_pause_rem = 0U;
    }
}


/**
 * check_tx_rtcp()
 *
 * This function is can be called by either put_frame() or get_frame(),
 * to transmit periodic RTCP SR/RR report.
 */
static void check_tx_rtcp(pjmedia_stream *stream, pj_uint32_t timestamp)
{
    pjmedia_stream_common *c_strm = &stream->base;

    /* Note that timestamp may represent local or remote timestamp,
     * depending on whether this function is called from put_frame()
     * or get_frame().
     */

    if (c_strm->rtcp_last_tx == 0) {

        c_strm->rtcp_last_tx = timestamp;

    } else if (timestamp - c_strm->rtcp_last_tx >= c_strm->rtcp_interval) {
        pj_bool_t with_xr = PJ_FALSE;
        pj_status_t status;

#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
        if (c_strm->rtcp.xr_enabled) {
            if (c_strm->rtcp_xr_last_tx == 0) {
                c_strm->rtcp_xr_last_tx = timestamp;
            } else if (timestamp - c_strm->rtcp_xr_last_tx >=
                       c_strm->rtcp_xr_interval)
            {
                with_xr = PJ_TRUE;

                /* Update last tx RTCP XR */
                c_strm->rtcp_xr_last_tx = timestamp;
            }
        }
#endif

        status = send_rtcp(c_strm, !c_strm->rtcp_sdes_bye_disabled, PJ_FALSE,
                           with_xr, PJ_FALSE, PJ_FALSE, PJ_FALSE);
        if (status == PJ_SUCCESS) {
            c_strm->rtcp_last_tx = timestamp;
        }
    }
}


/**
 * Rebuffer the frame when encoder and decoder has different ptime
 * (such as when different iLBC modes are used by local and remote)
 */
static void rebuffer(pjmedia_stream *stream,
                     pjmedia_frame *frame)
{
    pjmedia_stream_common *c_strm = &stream->base;

    /* How many samples are needed */
    unsigned count;

    /* Normalize frame */
    if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
        frame->size = 0;

    /* Remove used frame from the buffer. */
    if (stream->enc_buf_pos) {
        if (stream->enc_buf_count) {
            pj_memmove(c_strm->enc_buf,
                       c_strm->enc_buf + stream->enc_buf_pos,
                       (stream->enc_buf_count << 1));
        }
        stream->enc_buf_pos = 0;
    }

    /* Make sure we have space to store the new frame */
    pj_assert(stream->enc_buf_count + (frame->size >> 1) <
                stream->enc_buf_size);

    /* Append new frame to the buffer */
    if (frame->size) {
        /* Handle case when there is no port transmitting to this port */
        if (frame->buf) {
            pj_memcpy(c_strm->enc_buf + stream->enc_buf_count,
                      frame->buf, frame->size);
        } else {
            pj_bzero(c_strm->enc_buf + stream->enc_buf_count, frame->size);
        }
        stream->enc_buf_count += ((unsigned)frame->size >> 1);
    }

    /* How many samples are needed */
    count = stream->codec_param.info.enc_ptime *
            PJMEDIA_PIA_SRATE(&c_strm->port.info) /
            stream->codec_param.info.enc_ptime_denum /
            1000;

    /* See if we have enough samples */
    if (stream->enc_buf_count >= count) {

        frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
        frame->buf = c_strm->enc_buf;
        frame->size = (count << 1);

        stream->enc_buf_pos = count;
        stream->enc_buf_count -= count;

    } else {
        /* We don't have enough samples */
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
    }
}


/**
 * put_frame_imp()
 */
static pj_status_t put_frame_imp( pjmedia_port *port,
                                  pjmedia_frame *frame )
{
    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_channel *channel = c_strm->enc;
    pj_status_t status = 0;
    pjmedia_frame frame_out;
    unsigned ts_len, rtp_ts_len;
    void *rtphdr;
    int rtphdrlen;
    int inc_timestamp = 0;


#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
    /* If the interval since last sending packet is greater than
     * PJMEDIA_STREAM_KA_INTERVAL, send keep-alive packet.
     */
    if (c_strm->use_ka)
    {
        pj_uint32_t dtx_duration, ka_interval;
        pj_time_val now, tmp;

        pj_gettimeofday(&now);
        tmp = now;
        PJ_TIME_VAL_SUB(tmp, c_strm->last_frm_ts_sent);
        dtx_duration = PJ_TIME_VAL_MSEC(tmp);
        if (c_strm->start_ka_count) {
            ka_interval = c_strm->start_ka_interval;
        }  else {
            ka_interval = c_strm->ka_interval * 1000;
        }
        if (dtx_duration > ka_interval) {
            send_keep_alive_packet(c_strm);
            c_strm->last_frm_ts_sent = now;

            if (c_strm->start_ka_count)
                c_strm->start_ka_count--;
        }
    }
#endif

    /* Number of samples in the frame */
    if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO)
        ts_len = ((unsigned)frame->size >> 1) /
                 stream->codec_param.info.channel_cnt;
    else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)
        ts_len = PJMEDIA_PIA_SPF(&c_strm->port.info) /
                 PJMEDIA_PIA_CCNT(&c_strm->port.info);
    else
        ts_len = 0;

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
    /* Handle special case for audio codec with RTP timestamp inconsistence
     * e.g: G722, MPEG audio.
     */
    if (stream->has_g722_mpeg_bug)
        rtp_ts_len = stream->rtp_tx_ts_len_per_pkt;
    else
        rtp_ts_len = ts_len;
#else
    rtp_ts_len = ts_len;
#endif

    /* Don't do anything if stream is paused, except updating RTP timestamp */
    if (channel->paused) {
        stream->enc_buf_pos = stream->enc_buf_count = 0;

        /* Update RTP session's timestamp. */
        status = pjmedia_rtp_encode_rtp( &channel->rtp, 0, 0, 0, rtp_ts_len,
                                         NULL, NULL);

        /* Update RTCP stats with last RTP timestamp. */
        c_strm->rtcp.stat.rtp_tx_last_ts = pj_ntohl(channel->rtp.out_hdr.ts);

        /* Check if now is the time to transmit RTCP SR/RR report.
         * We only do this when the decoder is paused,
         * because otherwise check_tx_rtcp() will be handled by on_rx_rtp().
         */
        if (c_strm->dec->paused) {
            check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
        }

        return PJ_SUCCESS;
    }

    /* Increment transmit duration */
    stream->tx_duration += ts_len;

    /* Init frame_out buffer. */
    frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr);
    frame_out.size = 0;

    /* If we have DTMF digits in the queue (and no pause is remaining), transmit
     * the digits.
     * Otherwise encode the PCM buffer.
     */
    if (stream->tx_dtmf_count && (stream->tx_dtmf_pause_rem == 0U)) {
        int first=0, last=0;

        create_dtmf_payload(stream, &frame_out, 0, &first, &last);

        /* Encapsulate into RTP packet. Note that:
         *  - RTP marker should be set on the beginning of a new event
         *  - RTP timestamp is constant for the same packet.
         */
        status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                         stream->tx_event_pt, first,
                                         (int)frame_out.size,
                                         (first ? rtp_ts_len : 0),
                                         (const void**)&rtphdr,
                                         &rtphdrlen);

        if (last) {
            /* This is the last packet for the event.
             * Increment the RTP timestamp of the RTP session, for next
             * RTP packets.
             */
            inc_timestamp = stream->dtmf_duration +
                            ((stream->tx_dtmf_ebit_rep_cnt) *
                             stream->rtp_tx_ts_len_per_pkt)
                            - rtp_ts_len;
        }


    /*
     * Special treatment for FRAME_TYPE_AUDIO but with frame->buf==NULL.
     * This happens when stream input is disconnected from the bridge.
     * In this case we periodically transmit RTP frame to keep NAT binding
     * open, by giving zero PCM frame to the codec.
     *
     * This was originally done in https://github.com/pjsip/pjproject/issues/56,
     * but then disabled in https://github.com/pjsip/pjproject/issues/439, but
     * now it's enabled again.
     */
    } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
               frame->buf == NULL &&
               c_strm->port.info.fmt.id == PJMEDIA_FORMAT_L16 &&
               (c_strm->dir & PJMEDIA_DIR_ENCODING))
    {
        pjmedia_frame silence_frame;

        /* process a possibly ongoing DTMF pause */
        process_dtmf_pause(stream);

        pj_bzero(&silence_frame, sizeof(silence_frame));
        silence_frame.buf = stream->zero_frame;
        silence_frame.size = stream->enc_samples_per_pkt * 2;
        silence_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
        silence_frame.timestamp.u32.lo = pj_ntohl(c_strm->enc->rtp.out_hdr.ts);

        /* Encode! */
        status = pjmedia_codec_encode( stream->codec, &silence_frame,
                                       channel->buf_size -
                                       sizeof(pjmedia_rtp_hdr),
                                       &frame_out);
        if (status != PJ_SUCCESS) {
            LOGERR_((c_strm->port.info.name.ptr, status,
                    "Codec encode() error"));
            return status;
        }

        /* Encapsulate. */
        status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                         channel->pt, 0,
                                         (int)frame_out.size, rtp_ts_len,
                                         (const void**)&rtphdr,
                                         &rtphdrlen);

    /* Encode audio frame */
    } else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
                frame->buf != NULL) ||
               (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED))
    {
        /* process a possibly ongoing DTMF pause */
        process_dtmf_pause(stream);

        /* Encode! */
        status = pjmedia_codec_encode( stream->codec, frame,
                                       channel->buf_size -
                                       sizeof(pjmedia_rtp_hdr),
                                       &frame_out);
        if (status != PJ_SUCCESS) {
            LOGERR_((c_strm->port.info.name.ptr, status,
                    "Codec encode() error"));
            return status;
        }

        /* Encapsulate. */
        status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                         channel->pt, 0,
                                         (int)frame_out.size, rtp_ts_len,
                                         (const void**)&rtphdr,
                                         &rtphdrlen);

    } else {

        /* Just update RTP session's timestamp. */
        status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                         0, 0,
                                         0, rtp_ts_len,
                                         (const void**)&rtphdr,
                                         &rtphdrlen);

    }

    if (status != PJ_SUCCESS) {
        LOGERR_((c_strm->port.info.name.ptr, status,
                "RTP encode_rtp() error"));
        return status;
    }

    /* Check if now is the time to transmit RTCP SR/RR report.
     * We only do this when stream direction is not "decoding only", because
     * when it is, check_tx_rtcp() will be handled by get_frame().
     */
    if (c_strm->dir != PJMEDIA_DIR_DECODING) {
        check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
    }

    /* Do nothing if we have nothing to transmit */
    if (frame_out.size == 0) {
        if (stream->is_streaming) {
            PJ_LOG(5,(c_strm->port.info.name.ptr,"Starting silence"));
            stream->is_streaming = PJ_FALSE;
        }

        return PJ_SUCCESS;
    }


    /* Copy RTP header to the beginning of packet */
    pj_memcpy(channel->buf, rtphdr, sizeof(pjmedia_rtp_hdr));

    /* Special case for DTMF: timestamp remains constant for
     * the same event, and is only updated after a complete event
     * has been transmitted.
     */
    if (inc_timestamp) {
        pjmedia_rtp_encode_rtp( &channel->rtp, stream->tx_event_pt, 0,
                                0, inc_timestamp, NULL, NULL);
    }

    /* Set RTP marker bit if currently not streaming */
    if (stream->is_streaming == PJ_FALSE) {
        pjmedia_rtp_hdr *rtp = (pjmedia_rtp_hdr*) channel->buf;

        rtp->m = 1;
        PJ_LOG(5,(c_strm->port.info.name.ptr,"Starting talksprut.."));
    }

    stream->is_streaming = PJ_TRUE;

    /* Send the RTP packet to the transport. */
    status = pjmedia_transport_send_rtp(c_strm->transport, channel->buf,
                                        frame_out.size +
                                            sizeof(pjmedia_rtp_hdr));

    if (status != PJ_SUCCESS) {
        if (c_strm->rtp_tx_err_cnt++ == 0) {
            LOGERR_((c_strm->port.info.name.ptr, status, "Error sending RTP"));
        }
        if (c_strm->rtp_tx_err_cnt > SEND_ERR_COUNT_TO_REPORT) {
            c_strm->rtp_tx_err_cnt = 0;
        }
        return PJ_SUCCESS;
    }

    /* Update stat */
    pjmedia_rtcp_tx_rtp(&c_strm->rtcp, (unsigned)frame_out.size);
    c_strm->rtcp.stat.rtp_tx_last_ts = pj_ntohl(c_strm->enc->rtp.out_hdr.ts);
    c_strm->rtcp.stat.rtp_tx_last_seq = pj_ntohs(c_strm->enc->rtp.out_hdr.seq);

#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
    /* Update time of last sending packet. */
    pj_gettimeofday(&c_strm->last_frm_ts_sent);
#endif

    return PJ_SUCCESS;
}


/**
 * put_frame()
 *
 * This callback is called by upstream component when it has PCM frame
 * to transmit. This function encodes the PCM frame, pack it into
 * RTP packet, and transmit to peer.
 */
static pj_status_t put_frame( pjmedia_port *port,
                              pjmedia_frame *frame )
{
    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_frame tmp_zero_frame;
    unsigned samples_per_frame;

    samples_per_frame = stream->enc_samples_per_pkt;

    /* https://github.com/pjsip/pjproject/issues/56:
     *  when input is PJMEDIA_FRAME_TYPE_NONE, feed zero PCM frame
     *  instead so that encoder can decide whether or not to transmit
     *  silence frame.
     */
    if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
        pj_memcpy(&tmp_zero_frame, frame, sizeof(pjmedia_frame));
        frame = &tmp_zero_frame;

        tmp_zero_frame.buf = NULL;
        tmp_zero_frame.size = samples_per_frame * 2;
        tmp_zero_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
    }

#if 0
    // This is no longer needed because each TYPE_NONE frame will
    // be converted into zero frame above

    /* If VAD is temporarily disabled during creation, feed zero PCM frame
     * to the codec.
     */
    if (stream->vad_enabled != stream->codec_param.setting.vad &&
        stream->vad_enabled != 0 &&
        frame->type == PJMEDIA_FRAME_TYPE_NONE &&
        samples_per_frame <= ZERO_PCM_MAX_SIZE)
    {
        pj_memcpy(&tmp_in_frame, frame, sizeof(pjmedia_frame));
        frame = &tmp_in_frame;

        tmp_in_frame.buf = NULL;
        tmp_in_frame.size = samples_per_frame * 2;
        tmp_in_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
    }
#endif

    /* If VAD is temporarily disabled during creation, enable it
     * after transmitting for VAD_SUSPEND_SEC seconds.
     */
    if (stream->vad_enabled != stream->codec_param.setting.vad &&
        (stream->tx_duration - stream->ts_vad_disabled) >
           PJMEDIA_PIA_SRATE(&c_strm->port.info) *
          PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000)
    {
        stream->codec_param.setting.vad = stream->vad_enabled;
        pjmedia_codec_modify(stream->codec, &stream->codec_param);
        PJ_LOG(4,(c_strm->port.info.name.ptr,"VAD re-enabled"));
    }


    /* If encoder has different ptime than decoder, then the frame must
     * be passed through the encoding buffer via rebuffer() function.
     */
    if (c_strm->enc_buf != NULL) {
        pjmedia_frame tmp_rebuffer_frame;
        pj_status_t status = PJ_SUCCESS;

        /* Copy original frame to temporary frame since we need
         * to modify it.
         */
        pj_memcpy(&tmp_rebuffer_frame, frame, sizeof(pjmedia_frame));

        /* Loop while we have full frame in enc_buffer */
        for (;;) {
            pj_status_t st;

            /* Run rebuffer() */
            rebuffer(stream, &tmp_rebuffer_frame);

            /* Process this frame */
            st = put_frame_imp(port, &tmp_rebuffer_frame);
            if (st != PJ_SUCCESS)
                status = st;

            /* If we still have full frame in the buffer, re-run
             * rebuffer() with NULL frame.
             */
            if (stream->enc_buf_count >= stream->enc_samples_per_pkt) {

                tmp_rebuffer_frame.type = PJMEDIA_FRAME_TYPE_NONE;

            } else {

                /* Otherwise break */
                break;
            }
        }

        return status;

    } else {
        return put_frame_imp(port, frame);
    }
}


#if 0
static void dump_bin(const char *buf, unsigned len)
{
    unsigned i;

    PJ_LOG(3,(THIS_FILE, "begin dump"));
    for (i=0; i<len; ++i) {
        int j;
        char bits[9];
        unsigned val = buf[i] & 0xFF;

        bits[8] = '\0';
        for (j=0; j<8; ++j) {
            if (val & (1 << (7-j)))
                bits[j] = '1';
            else
                bits[j] = '0';
        }

        PJ_LOG(3,(THIS_FILE, "%2d %s [%d]", i, bits, val));
    }
    PJ_LOG(3,(THIS_FILE, "end dump"));
}
#endif

/*
 * Handle incoming DTMF digits.
 */
static void handle_incoming_dtmf( pjmedia_stream *stream,
                                  const pj_timestamp *timestamp,
                                  const void *payload, unsigned payloadlen)
{
    pjmedia_stream_common *c_strm = &stream->base;
    pjmedia_rtp_dtmf_event *event = (pjmedia_rtp_dtmf_event*) payload;
    pj_uint16_t event_duration;
    pjmedia_stream_dtmf_event dtmf_event;
    pj_bool_t is_event_end;
    pj_bool_t emit_event;
    float ts_modifier = 1.0;

    /* Check compiler packing. */
    pj_assert(sizeof(pjmedia_rtp_dtmf_event)==4);

    /* Must have sufficient length before we proceed. */
    if (payloadlen < sizeof(pjmedia_rtp_dtmf_event))
        return;

    //dump_bin(payload, payloadlen);

    /* Ignore unknown event. */
#if defined(PJMEDIA_HAS_DTMF_FLASH) && PJMEDIA_HAS_DTMF_FLASH!= 0
    if (event->event > 16) {
#else
    if (event->event > 15) {
#endif
        PJ_LOG(5,(c_strm->port.info.name.ptr,
                  "Ignored RTP pkt with bad DTMF event %d",
                  event->event));
        return;
    }

    /* Extract event data. */
    event_duration = pj_ntohs(event->duration);
    is_event_end = (event->e_vol & PJMEDIA_RTP_DTMF_EVENT_END_MASK) != 0;

    /* Correct duration and timestamp for codecs where clock_rate
     * differs from sample rate
     */
#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
    if (stream->has_g722_mpeg_bug == PJ_TRUE) {
        ts_modifier = 2;
    }
#endif
    if (!pj_stricmp2(&stream->si.fmt.encoding_name, "opus")) {
        ts_modifier = (float)stream->codec_param.info.clock_rate / 48000;
    }

    /* Check if this is the same/current digit of the last packet. */
    if (stream->last_dtmf != -1 &&
        event->event == stream->last_dtmf &&
        event_duration >= stream->last_dtmf_dur)
    {
        /* Emit all updates but hide duplicate end frames. */
        emit_event = !is_event_end || stream->last_dtmf_ended != is_event_end;

        /* Yes, this is the same event. */
        stream->last_dtmf_dur = event_duration;
        stream->last_dtmf_ended = is_event_end;

        /* If DTMF callback is installed and end of event hasn't been reported
         * already, call it.
         */
        if (stream->dtmf_event_cb && emit_event) {
            dtmf_event.digit = digitmap[event->event];
            dtmf_event.timestamp = (pj_uint32_t)(ts_modifier * timestamp->u64 /
                (stream->codec_param.info.clock_rate / 1000));
            dtmf_event.duration = (pj_uint16_t)(ts_modifier * event_duration /
                (stream->codec_param.info.clock_rate / 1000));
            dtmf_event.flags = PJMEDIA_STREAM_DTMF_IS_UPDATE;
            if (is_event_end) {
                dtmf_event.flags |= PJMEDIA_STREAM_DTMF_IS_END;
            }
            stream->dtmf_event_cb(stream, stream->dtmf_event_cb_user_data,
                                  &dtmf_event);
        }
        return;
    }

    /* New event! */
    PJ_LOG(5,(c_strm->port.info.name.ptr, "Received DTMF digit %c, vol=%d",
              digitmap[event->event],
              (event->e_vol & PJMEDIA_RTP_DTMF_EVENT_VOLUME_MASK)));

    stream->last_dtmf = event->event;
    stream->last_dtmf_dur = event_duration;
    stream->last_dtmf_ended = is_event_end;

    /* If DTMF callback is installed, call the callback, otherwise keep
     * the DTMF digits in the buffer.
     */
    if (stream->dtmf_event_cb) {
        dtmf_event.digit = digitmap[event->event];
        dtmf_event.timestamp = (pj_uint32_t)(ts_modifier * timestamp->u64 /
            (stream->codec_param.info.clock_rate / 1000));
        dtmf_event.duration = (pj_uint16_t)(ts_modifier * event_duration /
            (stream->codec_param.info.clock_rate / 1000));
        dtmf_event.flags = 0;
        if (is_event_end) {
            dtmf_event.flags |= PJMEDIA_STREAM_DTMF_IS_END;
        }
        stream->dtmf_event_cb(stream, stream->dtmf_event_cb_user_data,
                              &dtmf_event);
    } else if (stream->dtmf_cb) {
        stream->dtmf_cb(stream, stream->dtmf_cb_user_data,
                        digitmap[event->event]);
    } else {
        /* By convention, we use jitter buffer's mutex to access shared
         * DTMF variables.
         */
        pj_mutex_lock(c_strm->jb_mutex);
        if (stream->rx_dtmf_count >= PJ_ARRAY_SIZE(stream->rx_dtmf_buf)) {
            /* DTMF digits overflow.  Discard the oldest digit. */
            pj_array_erase(stream->rx_dtmf_buf,
                           sizeof(stream->rx_dtmf_buf[0]),
                           stream->rx_dtmf_count, 0);
            --stream->rx_dtmf_count;
        }
        stream->rx_dtmf_buf[stream->rx_dtmf_count++] = digitmap[event->event];
        pj_mutex_unlock(c_strm->jb_mutex);
    }
}


/*
 * This callback is called by common stream processing on receipt of
 * packets in the RTP socket (i.e. called by on_rx_rtp() in
 * stream_imp_common.c)
 */
static pj_status_t on_stream_rx_rtp(pjmedia_stream_common *c_strm,
                                    const pjmedia_rtp_hdr *hdr,
                                    const void *payload,
                                    unsigned payloadlen,
                                    pjmedia_rtp_status seq_st,
                                    pj_bool_t *pkt_discarded)
{
    pjmedia_stream *stream = (pjmedia_stream*) c_strm;
    pj_timestamp ts;
    pj_status_t status = PJ_SUCCESS;

    /* Get the timestamp from the RTP header */
    ts.u64 = pj_ntohl(hdr->ts);

    /* Handle incoming DTMF. */
    if (hdr->pt == stream->rx_event_pt) {
        /* Ignore out-of-order packet as it will be detected as new
         * digit. Also ignore duplicate packet as it serves no use.
         */
        if (seq_st.status.flag.outorder || seq_st.status.flag.dup) {
            goto on_return;
        }

        handle_incoming_dtmf(stream, &ts, payload, payloadlen);
        goto on_return;
    }

    /* Put "good" packet to jitter buffer, or reset the jitter buffer
     * when RTP session is restarted.
     */
    pj_mutex_lock( c_strm->jb_mutex );
    if (seq_st.status.flag.restart) {
        status = pjmedia_jbuf_reset(c_strm->jb);
        PJ_LOG(4,(c_strm->port.info.name.ptr, "Jitter buffer reset"));
    } else {
        /*
         * Packets may contain more than one frames, while the jitter
         * buffer can only take one frame per "put" operation. So we need
         * to ask the codec to "parse" the payload into multiple frames.
         */
        enum { MAX = 16 };
        unsigned i, count = MAX;
        unsigned ts_span;
        pjmedia_frame frames[MAX];
        pj_bzero(frames, sizeof(frames[0]) * MAX);

        /* Parse the payload. */
        status = pjmedia_codec_parse(stream->codec, (void*)payload,
                                     payloadlen, &ts, &count, frames);
        if (status != PJ_SUCCESS) {
            LOGERR_((c_strm->port.info.name.ptr, status,
                     "Codec parse() error"));
            count = 0;
        } else if (count == 0) {
                PJ_LOG(2, (c_strm->port.info.name.ptr, "codec parsed 0 frames"));
        } else if (stream->detect_ptime_change &&
                   frames[0].bit_info > 0xFFFF)
        {
            unsigned dec_ptime, dec_ptime_denum = 1;
            pj_uint16_t old_ptime, old_ptime_denum;
            pjmedia_rtcp_session_setting setting;

            old_ptime = stream->dec_ptime;
            old_ptime_denum = stream->dec_ptime_denum;

            frames[0].bit_info &= 0xFFFF;
            if ((frames[0].bit_info * 1000) %
                stream->codec_param.info.clock_rate != 0)
            {
                dec_ptime_denum = 2;
            }
            dec_ptime = frames[0].bit_info * 1000 * dec_ptime_denum /
                        stream->codec_param.info.clock_rate;
            stream->rtp_rx_ts_len_per_frame= stream->rtp_rx_ts_len_per_frame *
                                             dec_ptime *
                                             stream->dec_ptime_denum /
                                             stream->dec_ptime /
                                             dec_ptime_denum;
            stream->dec_ptime = (pj_uint16_t)dec_ptime;
            stream->dec_ptime_denum = (pj_uint8_t)dec_ptime_denum;
            pjmedia_jbuf_set_ptime2(c_strm->jb, stream->dec_ptime,
                                    stream->dec_ptime_denum);

            pjmedia_rtcp_session_setting_default(&setting);
            setting.dec_samples_per_frame =
                PJMEDIA_SPF(stream->codec_param.info.clock_rate,
                            stream->dec_ptime * 1000 /
                            stream->dec_ptime_denum,
                            stream->codec_param.info.channel_cnt);
            pjmedia_rtcp_update(&c_strm->rtcp, &setting);

            PJ_LOG(4, (c_strm->port.info.name.ptr, "codec decode "
                       "ptime change detected: %d/%d -> %d/%d",
                       old_ptime, old_ptime_denum,
                       dec_ptime, dec_ptime_denum));

            /* Reset jitter buffer after ptime changed */
            pjmedia_jbuf_reset(c_strm->jb);
        }

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
        /* This code is used to learn the samples per frame value that is put
         * by remote endpoint, for codecs with inconsistent clock rate such
         * as G.722 or MPEG audio. We need to learn the samples per frame
         * value as it is used as divider when inserting frames into the
         * jitter buffer.
         */
        if (stream->has_g722_mpeg_bug) {
            if (stream->rtp_rx_check_cnt) {
                /* Make sure the detection performed only on two consecutive
                 * packets with valid RTP sequence and no wrapped timestamp.
                 */
                if (seq_st.diff == 1 && c_strm->rtp_rx_last_ts &&
                    ts.u64 > c_strm->rtp_rx_last_ts &&
                    stream->rtp_rx_last_cnt > 0)
                {
                    unsigned peer_frm_ts_diff;
                    unsigned frm_ts_span;

                    /* Calculate actual frame timestamp span */
                    frm_ts_span = PJMEDIA_PIA_SPF(&c_strm->port.info) /
                                  stream->codec_param.setting.frm_per_pkt/
                                  PJMEDIA_PIA_CCNT(&c_strm->port.info);

                    /* Get remote frame timestamp span */
                    peer_frm_ts_diff =
                        ((pj_uint32_t)ts.u64-c_strm->rtp_rx_last_ts) /
                        stream->rtp_rx_last_cnt;

                    /* Possibilities remote's samples per frame for G.722
                     * are only (frm_ts_span) and (frm_ts_span/2), this
                     * validation is needed to avoid wrong decision because
                     * of silence frames.
                     */
                    if (stream->codec_param.info.pt == PJMEDIA_RTP_PT_G722 &&
                        (peer_frm_ts_diff == frm_ts_span ||
                         peer_frm_ts_diff == (frm_ts_span>>1)))
                    {
                        if (peer_frm_ts_diff < stream->rtp_rx_ts_len_per_frame)
                        {
                            stream->rtp_rx_ts_len_per_frame = peer_frm_ts_diff;
                            /* Done, stop the check immediately */
                            stream->rtp_rx_check_cnt = 1;
                        }

                        if (--stream->rtp_rx_check_cnt == 0) {
                            PJ_LOG(4, (THIS_FILE, "G722 codec used, remote"
                                       " samples per frame detected = %d",
                                       stream->rtp_rx_ts_len_per_frame));

                            /* Reset jitter buffer once detection done */
                            pjmedia_jbuf_reset(c_strm->jb);
                        }
                    }
                }

                c_strm->rtp_rx_last_ts = (pj_uint32_t)ts.u64;
                stream->rtp_rx_last_cnt = count;
            }

            ts_span = stream->rtp_rx_ts_len_per_frame;

            /* Adjust the timestamp of the parsed frames */
            for (i=0; i<count; ++i) {
                frames[i].timestamp.u64 = ts.u64 + (pj_uint64_t)ts_span * i;
            }

        } else {
            ts_span = stream->dec_ptime *
                      stream->codec_param.info.clock_rate /
                      stream->dec_ptime_denum /
                      1000;
        }
#else
        ts_span = stream->dec_ptime *
                  stream->codec_param.info.clock_rate /
                  stream->dec_ptime_denum /
                  1000;
#endif

        /* Put each frame to jitter buffer. */
        for (i=0; i<count; ++i) {
            unsigned ext_seq;
            pj_bool_t discarded;

            ext_seq = (unsigned)(frames[i].timestamp.u64 / ts_span);
            pjmedia_jbuf_put_frame3(c_strm->jb, frames[i].buf, frames[i].size,
                                    frames[i].bit_info, ext_seq, ts.u32.lo,
                                    &discarded);
            if (discarded)
                *pkt_discarded = PJ_TRUE;
        }

#if TRACE_JB
        trace_jb_put(c_strm, hdr, payloadlen, count);
#endif

    }
    pj_mutex_unlock( c_strm->jb_mutex );


    /* Check if now is the time to transmit RTCP SR/RR report.
     * We only do this when stream direction is "decoding only" or
     * if the encoder is paused,
     * because otherwise check_tx_rtcp() will be handled by put_frame()
     */
    if (c_strm->dir == PJMEDIA_DIR_DECODING || c_strm->enc->paused) {
        check_tx_rtcp(stream, pj_ntohl(hdr->ts));
    }

    if (status != 0) {
        LOGERR_((c_strm->port.info.name.ptr, status,
                 "Jitter buffer put() error"));
        *pkt_discarded = PJ_TRUE;
        goto on_return;
    }

on_return:
    return status;
}

/*
 * Handle events.
 */
static pj_status_t stream_event_cb(pjmedia_event *event,
                                   void *user_data)
{
    pjmedia_stream *stream = (pjmedia_stream*)user_data;
    pjmedia_stream_common *c_strm = &stream->base;

    /* Set RTCP FB capability in the event */
    if (event->type==PJMEDIA_EVENT_RX_RTCP_FB &&
        event->epub==&c_strm->rtcp)
    {
        pjmedia_event_rx_rtcp_fb_data *data = (pjmedia_event_rx_rtcp_fb_data*)
                                              &event->data.rx_rtcp_fb;

        /* Application not configured to listen to NACK, discard this event */
        if (c_strm->rtcp_fb_nack_cap_idx < 0)
            return PJ_SUCCESS;

        data->cap = stream->si.loc_rtcp_fb.caps[c_strm->rtcp_fb_nack_cap_idx];
    }

    /* Republish events */
    return pjmedia_event_publish(NULL, stream, event,
                                 PJMEDIA_EVENT_PUBLISH_POST_EVENT);
}


/*
 * Create media stream.
 */
PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
                                           pj_pool_t *pool,
                                           const pjmedia_stream_info *info,
                                           pjmedia_transport *tp,
                                           void *user_data,
                                           pjmedia_stream **p_stream)

{
    enum { M = 32 };
    pjmedia_stream *stream;
    pjmedia_stream_common *c_strm;
    pj_str_t name;
    unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
    pjmedia_audio_format_detail *afd;
    pj_pool_t *own_pool = NULL;
    char *p;
    unsigned max_rx_based_size;
    unsigned max_bps_based_size;
    unsigned buf_size;
    pj_status_t status;
    pjmedia_transport_attach_param att_param;

    PJ_ASSERT_RETURN(endpt && info && p_stream, PJ_EINVAL);

    /* Must create own pool to avoid premature destroy */
    if (1 /* || pool == NULL */) {
        own_pool = pjmedia_endpt_create_pool( endpt, "strm%p",
                                              PJMEDIA_STREAM_SIZE,
                                              PJMEDIA_STREAM_INC);
        PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM);
        pool = own_pool;
    }

    /* Allocate the media stream: */

    stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);
    PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM);
    c_strm = &stream->base;
    c_strm->own_pool = own_pool;

    /* Duplicate stream info */
    pj_memcpy(&stream->si, info, sizeof(*info));
    c_strm->si = (pjmedia_stream_info_common *)&stream->si;
    pj_strdup(pool, &stream->si.fmt.encoding_name, &info->fmt.encoding_name);
    if (info->param)
        stream->si.param = pjmedia_codec_param_clone(pool, info->param);
    pjmedia_rtcp_fb_info_dup(pool, &stream->si.loc_rtcp_fb,
                             &info->loc_rtcp_fb);
    pjmedia_rtcp_fb_info_dup(pool, &stream->si.rem_rtcp_fb,
                             &info->rem_rtcp_fb);

    /* Init stream/port name */
    name.ptr = (char*) pj_pool_alloc(pool, M);
    name.slen = pj_ansi_snprintf(name.ptr, M, "strm%p", stream);

    /* Init some port-info. Some parts of the info will be set later
     * once we have more info about the codec.
     */
    pjmedia_port_info_init(&c_strm->port.info, &name,
                           PJMEDIA_SIG_PORT_STREAM,
                           info->fmt.clock_rate, info->fmt.channel_cnt,
                           16, 80);
    afd = pjmedia_format_get_audio_format_detail(&c_strm->port.info.fmt, 1);

    //No longer there in 2.0
    //pj_strdup(pool, &c_strm->port.info.encoding_name, &info->fmt.encoding_name);
    afd->clock_rate = info->fmt.clock_rate;
    afd->channel_count = info->fmt.channel_cnt;
    c_strm->port.port_data.pdata = stream;

    /* Init stream: */
    c_strm->endpt = endpt;
    stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
    c_strm->dir = info->dir;
    c_strm->user_data = user_data;
    c_strm->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
                            info->fmt.clock_rate / 1000;
    c_strm->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;

    stream->tx_event_pt = info->tx_event_pt ? info->tx_event_pt : -1;
    stream->rx_event_pt = info->rx_event_pt ? info->rx_event_pt : -1;
    stream->last_dtmf = -1;
    c_strm->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME;
    c_strm->rtcp_fb_nack.pid = -1;
    stream->soft_start_cnt = PJMEDIA_STREAM_SOFT_START;

#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
    c_strm->use_ka = info->use_ka;
    c_strm->ka_interval = info->ka_cfg.ka_interval;
    c_strm->start_ka_count = info->ka_cfg.start_count;
    c_strm->start_ka_interval = info->ka_cfg.start_interval;
#endif

    c_strm->cname = info->cname;
    if (c_strm->cname.slen == 0) {
        /* Build random RTCP CNAME. CNAME has user@host format */
        c_strm->cname.ptr = p = (char*) pj_pool_alloc(pool, 20);
        pj_create_random_string(p, 5);
        p += 5;
        *p++ = '@'; *p++ = 'p'; *p++ = 'j';
        pj_create_random_string(p, 6);
        p += 6;
        *p++ = '.'; *p++ = 'o'; *p++ = 'r'; *p++ = 'g';
        c_strm->cname.slen = p - c_strm->cname.ptr;
    }

    /* Create mutex to protect jitter buffer: */

    status = pj_mutex_create_simple(pool, NULL, &c_strm->jb_mutex);
    if (status != PJ_SUCCESS)
        goto err_cleanup;


    /* Create and initialize codec: */

    status = pjmedia_codec_mgr_alloc_codec( stream->codec_mgr,
                                            &info->fmt, &stream->codec);
    if (status != PJ_SUCCESS)
        goto err_cleanup;


    /* Get codec param: */
    if (info->param)
        stream->codec_param = *stream->si.param;
    else {
        status = pjmedia_codec_mgr_get_default_param(stream->codec_mgr,
                                                     &info->fmt,
                                                     &stream->codec_param);
        if (status != PJ_SUCCESS)
            goto err_cleanup;
    }

    /* Check for invalid max_bps. */
    if (stream->codec_param.info.max_bps < stream->codec_param.info.avg_bps)
        stream->codec_param.info.max_bps = stream->codec_param.info.avg_bps;

    /* Check for invalid frame per packet. */
    if (stream->codec_param.setting.frm_per_pkt < 1)
        stream->codec_param.setting.frm_per_pkt = 1;

    if (stream->codec_param.info.frm_ptime_denum < 1)
        stream->codec_param.info.frm_ptime_denum = 1;

    if (stream->codec_param.info.enc_ptime_denum < 1)
        stream->codec_param.info.enc_ptime_denum = 1;

    /* Init the codec. */
    status = pjmedia_codec_init(stream->codec, pool);
    if (status != PJ_SUCCESS)
        goto err_cleanup;

    /* Open the codec. */

    /* The clock rate for Opus codec is not static,
     * it's negotiated in the SDP.
     */
    if (!pj_stricmp2(&info->fmt.encoding_name, "opus")) {
        stream->codec_param.info.clock_rate = info->fmt.clock_rate;
        stream->codec_param.info.channel_cnt = info->fmt.channel_cnt;

        /* Allocate decoding buffer as Opus can send a packet duration of
         * up to 120 ms.
         */
        stream->dec_buf_size = stream->codec_param.info.clock_rate * 120 / 1000;
        stream->dec_buf = (pj_int16_t*)pj_pool_alloc(pool,
                                                     stream->dec_buf_size *
                                                     sizeof(pj_int16_t));
    }

    status = pjmedia_codec_open(stream->codec, &stream->codec_param);
    if (status != PJ_SUCCESS)
        goto err_cleanup;

    /* Set additional info and callbacks. */
    stream->dec_ptime = stream->codec_param.info.frm_ptime;
    stream->dec_ptime_denum = PJ_MAX(stream->codec_param.info.frm_ptime_denum,
                                     1);
    afd->bits_per_sample = 16;
    afd->frame_time_usec = stream->codec_param.info.frm_ptime *
                           stream->codec_param.setting.frm_per_pkt * 1000 /
                           stream->codec_param.info.frm_ptime_denum;
    c_strm->port.info.fmt.id = stream->codec_param.info.fmt_id;
    if (stream->codec_param.info.fmt_id == PJMEDIA_FORMAT_L16) {
        /* Raw format */
        afd->avg_bps = afd->max_bps = afd->clock_rate * afd->channel_count *
                                      afd->bits_per_sample;

        c_strm->port.put_frame = &put_frame;
        c_strm->port.get_frame = &get_frame;
    } else {
        /* Encoded format */
        afd->avg_bps = stream->codec_param.info.avg_bps;
        afd->max_bps = stream->codec_param.info.max_bps;

        /* Not applicable for 2.0
        if ((stream->codec_param.info.max_bps *
             stream->codec_param.info.frm_ptime *
             stream->codec_param.setting.frm_per_pkt) % 8000 != 0)
        {
            ++c_strm->port.info.bytes_per_frame;
        }
        c_strm->port.info.format.bitrate = stream->codec_param.info.avg_bps;
        c_strm->port.info.format.vad = (stream->codec_param.setting.vad != 0);
        */

        c_strm->port.put_frame = &put_frame;
        c_strm->port.get_frame = &get_frame_ext;
    }

    /* If encoder and decoder's ptime are asymmetric, then we need to
     * create buffer on the encoder side. This could happen for example
     * with iLBC
     */
    if (stream->codec_param.info.enc_ptime!=0 &&
        stream->codec_param.info.enc_ptime *
        stream->codec_param.info.frm_ptime_denum !=
        stream->codec_param.info.frm_ptime *
        stream->codec_param.info.enc_ptime_denum)
    {
        unsigned ptime;

        stream->enc_samples_per_pkt = stream->codec_param.info.enc_ptime *
                                      stream->codec_param.info.channel_cnt *
                                      afd->clock_rate /
                                      stream->codec_param.info.enc_ptime_denum
                                      / 1000;

        /* Set buffer size as twice the largest ptime value between
         * stream's ptime, encoder ptime, or decoder ptime.
         */

        ptime = afd->frame_time_usec;

        if (stream->codec_param.info.enc_ptime * (unsigned)1000 >
            ptime * stream->codec_param.info.enc_ptime_denum)
        {
            ptime = stream->codec_param.info.enc_ptime * 1000 /
                    stream->codec_param.info.enc_ptime_denum;
        }

        if (stream->codec_param.info.frm_ptime * (unsigned)1000 >
            ptime * stream->codec_param.info.frm_ptime_denum)
        {
            ptime = stream->codec_param.info.frm_ptime * 1000 /
                    stream->codec_param.info.frm_ptime_denum;
        }

        ptime <<= 1;

        /* Allocate buffer */
        stream->enc_buf_size = afd->clock_rate * ptime / 1000 / 1000;
        c_strm->enc_buf = (pj_int16_t*)
                          pj_pool_alloc(pool, stream->enc_buf_size * 2);

    } else {
        stream->enc_samples_per_pkt = PJMEDIA_AFD_SPF(afd);
    }


    /* Initially disable the VAD in the stream, to help traverse NAT better */
    stream->vad_enabled = stream->codec_param.setting.vad;
    if (PJMEDIA_STREAM_VAD_SUSPEND_MSEC > 0 && stream->vad_enabled) {
        stream->codec_param.setting.vad = 0;
        stream->ts_vad_disabled = 0;
        pjmedia_codec_modify(stream->codec, &stream->codec_param);
        PJ_LOG(4,(c_strm->port.info.name.ptr,"VAD temporarily disabled"));
    }

    /* Get the frame size */
    if (stream->codec_param.info.max_rx_frame_size > 0) {
        c_strm->frame_size = stream->codec_param.info.max_rx_frame_size;
    } else {
        c_strm->frame_size = stream->codec_param.info.max_bps *
                             stream->codec_param.info.frm_ptime /
                             stream->codec_param.info.frm_ptime_denum /
                             8 / 1000;
        if ((stream->codec_param.info.max_bps *
             stream->codec_param.info.frm_ptime /
             stream->codec_param.info.frm_ptime_denum) % 8000 != 0)
        {
            ++c_strm->frame_size;
        }
    }

    /* How many consecutive PLC frames can be generated */
    stream->max_plc_cnt = (MAX_PLC_MSEC+stream->codec_param.info.frm_ptime/
                           stream->codec_param.info.frm_ptime_denum-1) *
                          stream->codec_param.info.frm_ptime_denum /
                          stream->codec_param.info.frm_ptime;
    /* Disable PLC until a "NORMAL" frame is gotten from the jitter buffer. */
    stream->plc_cnt = stream->max_plc_cnt;

    /* TX DTMF is just initialized with default values here */
#if defined(PJMEDIA_DTMF_DURATION_MSEC) && (PJMEDIA_DTMF_DURATION_MSEC > 0)
    stream->dtmf_duration = PJMEDIA_DTMF_DURATION_MSEC *
                            afd->clock_rate / 1000;
#else
    stream->dtmf_duration = PJMEDIA_DTMF_DURATION;
#endif
    stream->tx_dtmf_pause_dur = 0U;
    stream->tx_dtmf_vol = -10;
    stream->tx_dtmf_ebit_rep_cnt = (DTMF_EBIT_RETRANSMIT_CNT - 1U);
    stream->tx_dtmf_pause_rem = 0U;

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
    stream->rtp_rx_check_cnt = 50;
    stream->has_g722_mpeg_bug = PJ_FALSE;
    c_strm->rtp_rx_last_ts = 0;
    stream->rtp_rx_last_cnt = 0;
    stream->rtp_tx_ts_len_per_pkt = stream->enc_samples_per_pkt /
                                     stream->codec_param.info.channel_cnt;
    stream->rtp_rx_ts_len_per_frame = PJMEDIA_AFD_SPF(afd) /
                                      stream->codec_param.setting.frm_per_pkt /
                                      stream->codec_param.info.channel_cnt;

    if (info->fmt.pt == PJMEDIA_RTP_PT_G722) {
        stream->has_g722_mpeg_bug = PJ_TRUE;
        /* RTP clock rate = 1/2 real clock rate */
        stream->rtp_tx_ts_len_per_pkt >>= 1;
#if defined(PJMEDIA_DTMF_DURATION_MSEC) && (PJMEDIA_DTMF_DURATION_MSEC > 0)
        stream->dtmf_duration >>= 1;
#endif
    } else if (!pj_stricmp2(&info->fmt.encoding_name, "opus")) {
        unsigned opus_ts_modifier = 48000 / afd->clock_rate;
        stream->rtp_rx_check_cnt = 0;
        stream->has_g722_mpeg_bug = PJ_TRUE;
        stream->rtp_tx_ts_len_per_pkt *= opus_ts_modifier;
        stream->rtp_rx_ts_len_per_frame *= opus_ts_modifier;
        stream->detect_ptime_change = PJ_TRUE;
#if defined(PJMEDIA_DTMF_DURATION_MSEC) && (PJMEDIA_DTMF_DURATION_MSEC > 0)
        stream->dtmf_duration *= opus_ts_modifier;
#endif
    }
#endif

    /* Init jitter buffer parameters: */
    if (info->jb_max * stream->codec_param.info.frm_ptime_denum >=
        stream->codec_param.info.frm_ptime)
    {
        jb_max = (info->jb_max + stream->codec_param.info.frm_ptime /
                  stream->codec_param.info.frm_ptime_denum - 1) *
                 stream->codec_param.info.frm_ptime_denum /
                 stream->codec_param.info.frm_ptime;
    } else {
        jb_max = 500 * stream->codec_param.info.frm_ptime_denum /
                 stream->codec_param.info.frm_ptime;
    }

    if (info->jb_min_pre * stream->codec_param.info.frm_ptime_denum >=
        stream->codec_param.info.frm_ptime)
    {
        jb_min_pre = info->jb_min_pre *
                     stream->codec_param.info.frm_ptime_denum /
                     stream->codec_param.info.frm_ptime;
    } else {
        //jb_min_pre = 60 / stream->codec_param.info.frm_ptime;
        jb_min_pre = 1;
    }

    if (info->jb_max_pre * stream->codec_param.info.frm_ptime_denum >=
        stream->codec_param.info.frm_ptime)
    {
        jb_max_pre = info->jb_max_pre *
                     stream->codec_param.info.frm_ptime_denum /
                     stream->codec_param.info.frm_ptime;
    } else {
        //jb_max_pre = 240 / stream->codec_param.info.frm_ptime;
        jb_max_pre = PJ_MAX(1, jb_max * 4 / 5);
    }

    if (info->jb_init * stream->codec_param.info.frm_ptime_denum >=
        stream->codec_param.info.frm_ptime)
    {
        jb_init = info->jb_init *
                  stream->codec_param.info.frm_ptime_denum /
                  stream->codec_param.info.frm_ptime;
    } else {
        //jb_init = (jb_min_pre + jb_max_pre) / 2;
        jb_init = 0;
    }

    /* Create jitter buffer */
    status = pjmedia_jbuf_create(pool, &c_strm->port.info.name,
                                 c_strm->frame_size,
                                 stream->codec_param.info.frm_ptime,
                                 jb_max, &c_strm->jb);
    if (status != PJ_SUCCESS)
        goto err_cleanup;


    /* Set up jitter buffer */
    pjmedia_jbuf_set_ptime2(c_strm->jb, stream->codec_param.info.frm_ptime,
                            stream->codec_param.info.frm_ptime_denum);
    pjmedia_jbuf_set_adaptive( c_strm->jb, jb_init, jb_min_pre, jb_max_pre);
    pjmedia_jbuf_set_discard(c_strm->jb, info->jb_discard_algo);

    /* buf buffer is used for sending and receiving, so lets calculate
     * its size based on both. For receiving, we have c_strm->frame_size,
     * which is used in configuring jitter buffer frame length.
     * For sending, it is based on codec max_bps info.
     */
    max_rx_based_size = c_strm->frame_size;
    max_bps_based_size = stream->codec_param.info.max_bps *
                         PJMEDIA_MAX_FRAME_DURATION_MS / 8 / 1000;
    buf_size = PJ_MAX(max_rx_based_size, max_bps_based_size);

    /* Create decoder channel: */
    status = create_channel( pool, c_strm, PJMEDIA_DIR_DECODING,
                             info->rx_pt, buf_size, c_strm->si, &c_strm->dec);
    if (status != PJ_SUCCESS)
        goto err_cleanup;


    /* Create encoder channel: */
    status = create_channel( pool, c_strm, PJMEDIA_DIR_ENCODING,
                             info->tx_pt, buf_size, c_strm->si, &c_strm->enc);
    if (status != PJ_SUCCESS)
        goto err_cleanup;


    /* Init RTCP session: */

    {
        pjmedia_rtcp_session_setting rtcp_setting;

        pjmedia_rtcp_session_setting_default(&rtcp_setting);
        rtcp_setting.name = c_strm->port.info.name.ptr;
        rtcp_setting.ssrc = info->ssrc;
        rtcp_setting.rtp_ts_base = pj_ntohl(c_strm->enc->rtp.out_hdr.ts);
        rtcp_setting.clock_rate = info->fmt.clock_rate;
        rtcp_setting.samples_per_frame = PJMEDIA_AFD_SPF(afd);

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
        /* Special case for G.722 */
        if (info->fmt.pt == PJMEDIA_RTP_PT_G722) {
            rtcp_setting.clock_rate = 8000;
            rtcp_setting.samples_per_frame = 160 *
                stream->codec_param.setting.frm_per_pkt;
        }
#endif

        pjmedia_rtcp_init2(&c_strm->rtcp, &rtcp_setting);

        if (info->rtp_seq_ts_set) {
            c_strm->rtcp.stat.rtp_tx_last_seq = info->rtp_seq;
            c_strm->rtcp.stat.rtp_tx_last_ts = info->rtp_ts;
        }

        /* Subscribe to RTCP events */
        pjmedia_event_subscribe(NULL, &stream_event_cb, stream,
                                &c_strm->rtcp);
    }

    /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES,
     * BYE, and XR.
     */
    c_strm->out_rtcp_pkt_size =  sizeof(pjmedia_rtcp_sr_pkt) +
                                 sizeof(pjmedia_rtcp_common) +
                                 (4 + (unsigned)c_strm->cname.slen) +
                                 32;
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    if (info->rtcp_xr_enabled) {
        c_strm->out_rtcp_pkt_size += sizeof(pjmedia_rtcp_xr_pkt);
    }
#endif

    if (c_strm->out_rtcp_pkt_size > PJMEDIA_MAX_MTU)
        c_strm->out_rtcp_pkt_size = PJMEDIA_MAX_MTU;

    c_strm->out_rtcp_pkt = pj_pool_alloc(pool, c_strm->out_rtcp_pkt_size);
    pj_bzero(&att_param, sizeof(att_param));
    att_param.stream = stream;
    att_param.media_type = PJMEDIA_TYPE_AUDIO;
    att_param.user_data = stream;
    pj_sockaddr_cp(&att_param.rem_addr, &info->rem_addr);
    pj_sockaddr_cp(&c_strm->rem_rtp_addr, &info->rem_addr);
    if (stream->si.rtcp_mux) {
        pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_addr);
    } else if (pj_sockaddr_has_addr(&info->rem_rtcp)) {
        pj_sockaddr_cp(&att_param.rem_rtcp, &info->rem_rtcp);
    }
    att_param.addr_len = pj_sockaddr_get_len(&info->rem_addr);
    att_param.rtp_cb2 = &on_rx_rtp;
    att_param.rtcp_cb = &on_rx_rtcp;

    /* Create group lock & attach handler */
    status = pj_grp_lock_create_w_handler(pool, NULL, stream,
                                          &on_destroy,
                                          &c_strm->grp_lock);
    if (status != PJ_SUCCESS)
        goto err_cleanup;

    /* Add ref */
    pj_grp_lock_add_ref(c_strm->grp_lock);
    c_strm->port.grp_lock = c_strm->grp_lock;

    /* Only attach transport when stream is ready. */
    c_strm->transport = tp;
    status = pjmedia_transport_attach2(tp, &att_param);
    if (status != PJ_SUCCESS)
        goto err_cleanup;

    /* Also add ref the transport group lock */
    if (c_strm->transport->grp_lock)
        pj_grp_lock_add_ref(c_strm->transport->grp_lock);


#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    /* Enable RTCP XR and update stream info/config to RTCP XR */
    if (info->rtcp_xr_enabled) {
        int i;

        pjmedia_rtcp_enable_xr(&c_strm->rtcp, PJ_TRUE);

        /* Set RTCP XR TX interval */
        if (info->rtcp_xr_interval != 0)
            c_strm->rtcp_xr_interval = info->rtcp_xr_interval;
        else
            c_strm->rtcp_xr_interval = (PJMEDIA_RTCP_INTERVAL +
                                       (pj_rand() % 8000)) *
                                       info->fmt.clock_rate / 1000;

        /* Additional third-party RTCP XR destination */
        if (info->rtcp_xr_dest.addr.sa_family != 0) {
            c_strm->rtcp_xr_dest_len = pj_sockaddr_get_len(&info->rtcp_xr_dest);
            pj_memcpy(&c_strm->rtcp_xr_dest, &info->rtcp_xr_dest,
                      c_strm->rtcp_xr_dest_len);
        }

        /* jitter buffer adaptive info */
        i = PJMEDIA_RTCP_XR_JB_ADAPTIVE;
        pjmedia_rtcp_xr_update_info(&c_strm->rtcp.xr_session,
                                    PJMEDIA_RTCP_XR_INFO_CONF_JBA,
                                    i);

        /* Jitter buffer aggressiveness info (estimated) */
        i = 7;
        pjmedia_rtcp_xr_update_info(&c_strm->rtcp.xr_session,
                                    PJMEDIA_RTCP_XR_INFO_CONF_JBR,
                                    i);

        /* Jitter buffer absolute maximum delay */
        i = jb_max * stream->codec_param.info.frm_ptime /
            stream->codec_param.info.frm_ptime_denum;
        pjmedia_rtcp_xr_update_info(&c_strm->rtcp.xr_session,
                                    PJMEDIA_RTCP_XR_INFO_JB_ABS_MAX,
                                    i);

        /* PLC info */
        if (stream->codec_param.setting.plc == 0)
            i = PJMEDIA_RTCP_XR_PLC_DIS;
        else
#if PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA
            i = PJMEDIA_RTCP_XR_PLC_ENH;
#else
            i = PJMEDIA_RTCP_XR_PLC_DIS;
#endif
        pjmedia_rtcp_xr_update_info(&c_strm->rtcp.xr_session,
                                    PJMEDIA_RTCP_XR_INFO_CONF_PLC,
                                    i);
    }
#endif

    /* Check if we should send RTCP-FB */
    if (stream->si.rem_rtcp_fb.cap_count) {
        pjmedia_rtcp_fb_info *rfi = &stream->si.rem_rtcp_fb;
        unsigned i;

        for (i = 0; i < rfi->cap_count; ++i) {
            if (rfi->caps[i].type == PJMEDIA_RTCP_FB_NACK &&
                rfi->caps[i].param.slen == 0)
            {
                c_strm->send_rtcp_fb_nack = PJ_TRUE;
                PJ_LOG(4,(c_strm->port.info.name.ptr,
                          "Send RTCP-FB generic NACK"));
                break;
            }
        }
    }

    /* Check if we should process incoming RTCP-FB */
    c_strm->rtcp_fb_nack_cap_idx = -1;
    if (stream->si.loc_rtcp_fb.cap_count) {
        pjmedia_rtcp_fb_info *lfi = &stream->si.loc_rtcp_fb;
        unsigned i;

        for (i = 0; i < lfi->cap_count; ++i) {
            if (lfi->caps[i].type == PJMEDIA_RTCP_FB_NACK &&
                lfi->caps[i].param.slen == 0)
            {
                c_strm->rtcp_fb_nack_cap_idx = i;
                PJ_LOG(4,(c_strm->port.info.name.ptr,
                          "Receive RTCP-FB generic NACK"));
                break;
            }
        }
    }

    /* Update the stream info's codec param */
    stream->si.param = &stream->codec_param;

    /* Check the zero frame buffer. */
    if (stream->enc_samples_per_pkt > PJ_ARRAY_SIZE(zero_frame)) {
        stream->zero_frame = (pj_int16_t*)pj_pool_zalloc(pool, 
                               sizeof(pj_int16_t)*stream->enc_samples_per_pkt); 
    } else {
        stream->zero_frame = zero_frame;
    }

    /* Send RTCP SDES */
    if (!c_strm->rtcp_sdes_bye_disabled) {
        pjmedia_stream_send_rtcp_sdes(stream);
    }

#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
    /* NAT hole punching by sending KA packet via RTP transport. */
    if (c_strm->use_ka)
        send_keep_alive_packet(c_strm);
#endif

#if TRACE_JB
    {
        char trace_name[PJ_MAXPATH];
        pj_ssize_t len;

        pj_ansi_snprintf(trace_name, sizeof(trace_name),
                         TRACE_JB_PATH_PREFIX "%s.csv",
                         c_strm->port.info.name.ptr);
        status = pj_file_open(pool, trace_name, PJ_O_WRONLY,
                              &c_strm->trace_jb_fd);
        if (status != PJ_SUCCESS) {
            c_strm->trace_jb_fd = TRACE_JB_INVALID_FD;
            PJ_PERROR(3,(THIS_FILE, status,
                         "Failed creating RTP trace file '%s'", trace_name));
        } else {
            c_strm->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE);

            /* Print column header */
            len = pj_ansi_snprintf(c_strm->trace_jb_buf, PJ_LOG_MAX_SIZE,
                                   "Time, Operation, Size, Frame Count, "
                                   "Frame type, RTP Seq, RTP TS, RTP M, "
                                   "JB size, JB burst level, JB prefetch\n");
            if (len < 1 || len >= PJ_LOG_MAX_SIZE)
                len = PJ_LOG_MAX_SIZE-1;
            pj_file_write(c_strm->trace_jb_fd, c_strm->trace_jb_buf, &len);
            pj_file_flush(c_strm->trace_jb_fd);
        }
    }
#endif

    /* Success! */
    *p_stream = stream;

    PJ_LOG(5,(THIS_FILE, "Stream %s created", c_strm->port.info.name.ptr));

    return PJ_SUCCESS;


err_cleanup:
    pjmedia_stream_destroy(stream);
    return status;
}


static void on_stream_destroy(void *arg)
{
    pjmedia_stream* stream = (pjmedia_stream*)arg;

    /* Free codec. */
    if (stream->codec) {
        pjmedia_codec_close(stream->codec);
        pjmedia_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec);
        stream->codec = NULL;
    }
}


/*
 * Destroy stream.
 */
PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream )
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;
    pj_status_t status;

    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);

    PJ_LOG(4,(c_strm->port.info.name.ptr, "Stream destroying"));

    /* Stop the streaming */
    if (c_strm->enc)
        c_strm->port.put_frame = NULL;
    if (c_strm->dec)
        c_strm->port.get_frame = NULL;

    /* Send RTCP BYE (also SDES & XR) */
    if (c_strm->transport && !c_strm->rtcp_sdes_bye_disabled) {
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
        send_rtcp(c_strm, PJ_TRUE, PJ_TRUE, c_strm->rtcp.xr_enabled,
                  PJ_FALSE, PJ_FALSE, PJ_FALSE);
#else
        send_rtcp(c_strm, PJ_TRUE, PJ_TRUE, PJ_FALSE, PJ_FALSE,
                  PJ_FALSE, PJ_FALSE);
#endif
    }

    /* If we're in the middle of transmitting DTMF digit, send one last
     * RFC 2833 RTP packet with 'End' flag set.
     */
    if (stream->tx_dtmf_count && stream->tx_dtmf_buf[0].duration != 0 &&
        c_strm->transport && c_strm->jb_mutex)
    {
        pjmedia_frame frame_out;
        pjmedia_channel *channel = c_strm->enc;
        int first=0, last=0;
        void *rtphdr;
        int rtphdrlen;

        pj_bzero(&frame_out, sizeof(frame_out));
        frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr);
        frame_out.size = 0;

        create_dtmf_payload(stream, &frame_out, 1, &first, &last);

        /* Encapsulate into RTP packet. Note that:
         *  - RTP marker should be set on the beginning of a new event
         *  - RTP timestamp is constant for the same packet.
         */
        status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                         stream->tx_event_pt, first,
                                         (int)frame_out.size, 0,
                                         (const void**)&rtphdr,
                                         &rtphdrlen);
        if (status == PJ_SUCCESS) {
            /* Copy RTP header to the beginning of packet */
            pj_memcpy(channel->buf, rtphdr, sizeof(pjmedia_rtp_hdr));

            /* Send the RTP packet to the transport. */
            status = pjmedia_transport_send_rtp(c_strm->transport,
                                                channel->buf,
                                                frame_out.size +
                                                    sizeof(pjmedia_rtp_hdr));
        }

        if (status != PJ_SUCCESS) {
            PJ_PERROR(4,(c_strm->port.info.name.ptr, status,
                         "Error sending RTP/DTMF end packet"));
        }
    }

    /* Unsubscribe from RTCP session events */
    pjmedia_event_unsubscribe(NULL, &stream_event_cb, stream,
                              &c_strm->rtcp);

    /* Detach from transport
     * MUST NOT hold stream mutex while detaching from transport, as
     * it may cause deadlock. See ticket #460 for the details.
     */
    if (c_strm->transport) {
        pjmedia_transport_detach(c_strm->transport, stream);
        //c_strm->transport = NULL;
    }

    if (c_strm->grp_lock) {
        pj_grp_lock_dec_ref(c_strm->grp_lock);
    } else {
        on_destroy(stream);
    }

    return PJ_SUCCESS;
}


/*
 * Get the last frame frame type retreived from the jitter buffer.
 */
PJ_DEF(char) pjmedia_stream_get_last_jb_frame_type(pjmedia_stream *stream)
{
    pjmedia_stream_common *c_strm = &stream->base;
    return c_strm->jb_last_frm;
}


/*
 * Get the port interface.
 */
PJ_DEF(pj_status_t) pjmedia_stream_get_port( pjmedia_stream *stream,
                                             pjmedia_port **p_port )
{
    pjmedia_stream_common *c_strm = &stream->base;
    *p_port = &c_strm->port;
    return PJ_SUCCESS;
}


/*
 * Get the transport object
 */
PJ_DEF(pjmedia_transport*) pjmedia_stream_get_transport(pjmedia_stream *st)
{
    return st->base.transport;
}


/*
 * Start stream.
 */
PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream)
{
    return pjmedia_stream_common_start((pjmedia_stream_common *) stream);
}

/*
 * Modify codec parameter.
 */
PJ_DEF(pj_status_t)
pjmedia_stream_modify_codec_param(pjmedia_stream *stream,
                                  const pjmedia_codec_param *param)
{
    PJ_ASSERT_RETURN(stream && param, PJ_EINVAL);

    return pjmedia_codec_modify(stream->codec, param);
}


PJ_DEF(pj_status_t) pjmedia_stream_get_info( const pjmedia_stream *stream,
                                             pjmedia_stream_info *info)
{
    PJ_ASSERT_RETURN(stream && info, PJ_EINVAL);

    pj_memcpy(info, &stream->si, sizeof(pjmedia_stream_info));
    return PJ_SUCCESS;
}

/*
 * Get stream statistics.
 */
PJ_DEF(pj_status_t) pjmedia_stream_get_stat( const pjmedia_stream *stream,
                                             pjmedia_rtcp_stat *stat)
{
    return pjmedia_stream_common_get_stat((pjmedia_stream_common *)stream,
                                          stat);
}


/*
 * Reset the stream statistics in the middle of a stream session.
 */
PJ_DEF(pj_status_t) pjmedia_stream_reset_stat(pjmedia_stream *stream)
{
    return pjmedia_stream_common_reset_stat((pjmedia_stream_common *)stream);
}


#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
/*
 * Get stream extended statistics.
 */
PJ_DEF(pj_status_t) pjmedia_stream_get_stat_xr( const pjmedia_stream *stream,
                                                pjmedia_rtcp_xr_stat *stat)
{
    const pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL);

    if (c_strm->rtcp.xr_enabled) {
        pj_memcpy(stat, &c_strm->rtcp.xr_session.stat, sizeof(pjmedia_rtcp_xr_stat));
        return PJ_SUCCESS;
    }
    return PJ_ENOTFOUND;
}
#endif

/*
 * Get jitter buffer state.
 */
PJ_DEF(pj_status_t) pjmedia_stream_get_stat_jbuf(const pjmedia_stream *stream,
                                                 pjmedia_jb_state *state)
{
    return pjmedia_stream_common_get_stat_jbuf((pjmedia_stream_common *)stream,
                                               state);
}

/*
 * Pause stream.
 */
PJ_DEF(pj_status_t) pjmedia_stream_pause( pjmedia_stream *stream,
                                          pjmedia_dir dir)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream, PJ_EINVAL);

    if ((dir & PJMEDIA_DIR_ENCODING) && c_strm->enc) {
        c_strm->enc->paused = 1;
        PJ_LOG(4,(c_strm->port.info.name.ptr, "Encoder stream paused"));
    }

    if ((dir & PJMEDIA_DIR_DECODING) && c_strm->dec) {
        c_strm->dec->paused = 1;

        /* Also reset jitter buffer */
        pj_mutex_lock( c_strm->jb_mutex );
        pjmedia_jbuf_reset(c_strm->jb);
        pj_mutex_unlock( c_strm->jb_mutex );

        PJ_LOG(4,(c_strm->port.info.name.ptr, "Decoder stream paused"));
    }

    return PJ_SUCCESS;
}


/*
 * Resume stream
 */
PJ_DEF(pj_status_t) pjmedia_stream_resume( pjmedia_stream *stream,
                                           pjmedia_dir dir)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream, PJ_EINVAL);

    if ((dir & PJMEDIA_DIR_ENCODING) && c_strm->enc) {
        c_strm->enc->paused = 0;
        PJ_LOG(4,(c_strm->port.info.name.ptr, "Encoder stream resumed"));
    }

    if ((dir & PJMEDIA_DIR_DECODING) && c_strm->dec) {
        c_strm->dec->paused = 0;
        stream->soft_start_cnt = PJMEDIA_STREAM_SOFT_START;
        PJ_LOG(4,(c_strm->port.info.name.ptr, "Decoder stream resumed"));
    }

    return PJ_SUCCESS;
}


/*
 * Set DTMF transmission options of this stream
 */
PJ_DEF(pj_status_t)
pjmedia_stream_set_tx_dtmf_options(pjmedia_stream *stream,
                                   pj_uint32_t duration_ms,
                                   pj_uint32_t pause_ms,
                                   pj_uint8_t pt,
                                   pj_int8_t vol,
                                   pj_uint32_t ebit_rep_cnt)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;
    pjmedia_audio_format_detail *afd;

    PJ_ASSERT_RETURN(stream &&
                     (duration_ms <= 1000U) &&
                     (pause_ms <= 1000U) &&
                     (vol >= -63) && (vol <= 0) &&
                     (ebit_rep_cnt <= 7U), PJ_EINVAL);

    /* By convention we use jitter buffer mutex to access DTMF queue. */
    pj_mutex_lock(c_strm->jb_mutex);

    afd = pjmedia_format_get_audio_format_detail(&c_strm->port.info.fmt, 1);

    /* convert durations to timestamps */
    stream->dtmf_duration = duration_ms * afd->clock_rate / 1000U;
    stream->tx_dtmf_pause_dur = pause_ms * afd->clock_rate / 1000U;

    /* take other options as they are */
    stream->tx_event_pt = pt;
    stream->tx_dtmf_vol = vol;
    stream->tx_dtmf_ebit_rep_cnt = ebit_rep_cnt;

    /* Unlock jitter buffer mutex. */
    pj_mutex_unlock(c_strm->jb_mutex);

    return PJ_SUCCESS;
}


/*
 * Dial DTMF
 */
PJ_DEF(pj_status_t) pjmedia_stream_dial_dtmf( pjmedia_stream *stream,
                                              const pj_str_t *digit_char)
{
    return pjmedia_stream_dial_dtmf2(stream, digit_char, 0);
}

PJ_DEF(pj_status_t) pjmedia_stream_dial_dtmf2( pjmedia_stream *stream,
                                              const pj_str_t *digit_char,
                                              unsigned duration)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;
    pj_status_t status = PJ_SUCCESS;

    /* By convention we use jitter buffer mutex to access DTMF
     * queue.
     */
    PJ_ASSERT_RETURN(stream && digit_char, PJ_EINVAL);

    /* Check that remote can receive DTMF events. */
    if (stream->tx_event_pt < 0) {
        return PJMEDIA_RTP_EREMNORFC2833;
    }

    pj_mutex_lock(c_strm->jb_mutex);

    if (stream->tx_dtmf_count+digit_char->slen >
        (long)PJ_ARRAY_SIZE(stream->tx_dtmf_buf))
    {
        status = PJ_ETOOMANY;
    } else {
        int i;

        /* convert ASCII digits into payload type first, to make sure
         * that all digits are valid.
         */
        for (i=0; i<digit_char->slen; ++i) {
            unsigned pt;
            int dig = pj_tolower(digit_char->ptr[i]);

            if (dig >= '0' && dig <= '9')
            {
                pt = dig - '0';
            }
            else if (dig >= 'a' && dig <= 'd')
            {
                pt = dig - 'a' + 12;
            }
            else if (dig == '*')
            {
                pt = 10;
            }
            else if (dig == '#')
            {
                pt = 11;
            }
#if defined(PJMEDIA_HAS_DTMF_FLASH) && PJMEDIA_HAS_DTMF_FLASH!= 0
            else if (dig == 'r')
            {
                pt = 16;
            }
#endif
            else
            {
                status = PJMEDIA_RTP_EINDTMF;
                break;
            }

            stream->tx_dtmf_buf[stream->tx_dtmf_count+i].event = pt;
            stream->tx_dtmf_buf[stream->tx_dtmf_count+i].duration = 0;
            stream->tx_dtmf_buf[stream->tx_dtmf_count+i].send_duration = duration;
            stream->tx_dtmf_buf[stream->tx_dtmf_count+i].ebit_cnt = 0;
        }

        if (status != PJ_SUCCESS)
            goto on_return;

        /* Increment digit count only if all digits are valid. */
        stream->tx_dtmf_count += (int)digit_char->slen;
    }

on_return:
    pj_mutex_unlock(c_strm->jb_mutex);

    return status;
}


/*
 * See if we have DTMF digits in the rx buffer.
 */
PJ_DEF(pj_bool_t) pjmedia_stream_check_dtmf(pjmedia_stream *stream)
{
    return stream->rx_dtmf_count != 0;
}


/*
 * Retrieve incoming DTMF digits from the stream's DTMF buffer.
 */
PJ_DEF(pj_status_t) pjmedia_stream_get_dtmf( pjmedia_stream *stream,
                                             char *digits,
                                             unsigned *size)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream && digits && size, PJ_EINVAL);

    /* By convention, we use jitter buffer's mutex to access DTMF
     * digits resources.
     */
    pj_mutex_lock(c_strm->jb_mutex);

    if (stream->rx_dtmf_count < *size)
        *size = stream->rx_dtmf_count;

    if (*size) {
        pj_memcpy(digits, stream->rx_dtmf_buf, *size);
        stream->rx_dtmf_count -= *size;
        if (stream->rx_dtmf_count) {
            pj_memmove(stream->rx_dtmf_buf,
                       &stream->rx_dtmf_buf[*size],
                       stream->rx_dtmf_count);
        }
    }

    pj_mutex_unlock(c_strm->jb_mutex);

    return PJ_SUCCESS;
}


/*
 * Set callback to be called upon receiving DTMF digits.
 */
PJ_DEF(pj_status_t) pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream,
                                 void (*cb)(pjmedia_stream*,
                                            void *user_data,
                                            int digit),
                                 void *user_data)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream, PJ_EINVAL);

    /* By convention, we use jitter buffer's mutex to access DTMF
     * digits resources.
     */
    pj_mutex_lock(c_strm->jb_mutex);

    stream->dtmf_cb = cb;
    stream->dtmf_cb_user_data = user_data;

    pj_mutex_unlock(c_strm->jb_mutex);

    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjmedia_stream_set_dtmf_event_callback(pjmedia_stream *stream,
                                                           void (*cb)(pjmedia_stream*,
                                                                      void *user_data,
                                                                      const pjmedia_stream_dtmf_event *event),
                                                           void *user_data)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;

    PJ_ASSERT_RETURN(stream, PJ_EINVAL);

    /* By convention, we use jitter buffer's mutex to access DTMF
     * digits resources.
     */
    pj_mutex_lock(c_strm->jb_mutex);

    stream->dtmf_event_cb = cb;
    stream->dtmf_event_cb_user_data = user_data;

    pj_mutex_unlock(c_strm->jb_mutex);

    return PJ_SUCCESS;
}

/*
 * Get the number of queued DTMF digits for transmission.
 */
PJ_DEF(unsigned) pjmedia_get_queued_dtmf_digits(pjmedia_stream *stream)
{
    pjmedia_stream_common *c_strm = (pjmedia_stream_common *)stream;
    unsigned count;

    PJ_ASSERT_RETURN(stream, 0);

    /* By convention, we use jitter buffer's mutex to access DTMF
     * digits resources.
     */
    pj_mutex_lock(c_strm->jb_mutex);
    count = stream->tx_dtmf_count;
    pj_mutex_unlock(c_strm->jb_mutex);

    return count;
}

/*
 * Send RTCP SDES.
 */
PJ_DEF(pj_status_t)
pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream )
{
    return pjmedia_stream_common_send_rtcp_sdes(
               (pjmedia_stream_common *) stream);
}

/*
 * Send RTCP BYE.
 */
PJ_DEF(pj_status_t)
pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream )
{
    return pjmedia_stream_common_send_rtcp_bye(
               (pjmedia_stream_common *) stream);
}


/**
 * Get RTP session information from stream.
 */
PJ_DEF(pj_status_t)
pjmedia_stream_get_rtp_session_info(pjmedia_stream *stream,
                                    pjmedia_stream_rtp_sess_info *session_info)
{
    return pjmedia_stream_common_get_rtp_session_info(
               (pjmedia_stream_common *)stream, session_info);
}
