| /* $Id$ */ |
| /* |
| * 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 <pjmedia/stream_common.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 ERRLEVEL 1 |
| #define LOGERR_(expr) stream_perror 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 |
| |
| |
| /* Tracing jitter buffer operations in a stream session to a CSV file. |
| * The trace will contain JB operation timestamp, frame info, RTP info, and |
| * the JB state right after the operation. |
| */ |
| #define TRACE_JB 0 /* Enable/disable trace. */ |
| #define TRACE_JB_PATH_PREFIX "" /* Optional path/prefix |
| for the CSV filename. */ |
| #if TRACE_JB |
| # include <pj/file_io.h> |
| # define TRACE_JB_INVALID_FD ((pj_oshandle_t)-1) |
| # define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD) |
| #endif |
| |
| #ifndef PJMEDIA_STREAM_SIZE |
| # define PJMEDIA_STREAM_SIZE 1000 |
| #endif |
| |
| #ifndef PJMEDIA_STREAM_INC |
| # define PJMEDIA_STREAM_INC 1000 |
| #endif |
| |
| /* Number of DTMF E bit transmissions */ |
| #define DTMF_EBIT_RETRANSMIT_CNT 3 |
| |
| /** |
| * Media channel. |
| */ |
| struct pjmedia_channel |
| { |
| pjmedia_stream *stream; /**< Parent stream. */ |
| pjmedia_dir dir; /**< Channel direction. */ |
| unsigned pt; /**< Payload type. */ |
| pj_bool_t paused; /**< Paused?. */ |
| unsigned out_pkt_size; /**< Size of output buffer. */ |
| void *out_pkt; /**< Output buffer. */ |
| unsigned out_pkt_len; /**< Length of data in buffer. */ |
| pjmedia_rtp_session rtp; /**< RTP session. */ |
| }; |
| |
| |
| struct dtmf |
| { |
| int event; |
| pj_uint32_t duration; |
| int 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_endpt *endpt; /**< Media endpoint. */ |
| pjmedia_codec_mgr *codec_mgr; /**< Codec manager instance. */ |
| pjmedia_stream_info si; /**< Creation parameter. */ |
| pjmedia_port port; /**< Port interface. */ |
| pjmedia_channel *enc; /**< Encoding channel. */ |
| pjmedia_channel *dec; /**< Decoding channel. */ |
| |
| pj_pool_t *own_pool; /**< Only created if not given */ |
| |
| pjmedia_dir dir; /**< Stream direction. */ |
| void *user_data; /**< User data. */ |
| pj_str_t cname; /**< SDES CNAME */ |
| |
| pjmedia_transport *transport; /**< Stream transport. */ |
| |
| 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. */ |
| |
| 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. */ |
| |
| pj_mutex_t *jb_mutex; |
| pjmedia_jbuf *jb; /**< Jitter buffer. */ |
| char jb_last_frm; /**< Last frame type from jb */ |
| unsigned jb_last_frm_cnt;/**< Last JB frame type counter*/ |
| |
| pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */ |
| |
| pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */ |
| pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */ |
| pj_bool_t initial_rr; /**< Initial RTCP RR sent */ |
| pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/ |
| void *out_rtcp_pkt; /**< Outgoing RTCP packet. */ |
| unsigned out_rtcp_pkt_size; |
| /**< Outgoing RTCP packet size. */ |
| |
| /* RFC 2833 DTMF transmission queue: */ |
| 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. */ |
| |
| /* 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. */ |
| 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; |
| |
| #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 |
| |
| #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) |
| pj_uint32_t rtcp_xr_last_tx; /**< RTCP XR tx time |
| in timestamp. */ |
| pj_uint32_t rtcp_xr_interval; /**< Interval, in timestamp. */ |
| pj_sockaddr rtcp_xr_dest; /**< Additional remote RTCP XR |
| dest. If sin_family is |
| zero, it will be ignored*/ |
| unsigned rtcp_xr_dest_len; /**< Length of RTCP XR dest |
| address */ |
| #endif |
| |
| #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 |
| pj_bool_t use_ka; /**< Stream keep-alive with non- |
| codec-VAD mechanism is |
| enabled? */ |
| pj_timestamp last_frm_ts_sent; /**< Timestamp of last sending |
| packet */ |
| #endif |
| |
| #if TRACE_JB |
| pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/ |
| char *trace_jb_buf; /**< Jitter tracing buffer. */ |
| #endif |
| |
| pj_uint32_t rtp_rx_last_ts; /**< Last received RTP timestamp*/ |
| }; |
| |
| |
| /* RFC 2833 digit */ |
| static const char digitmap[16] = { '0', '1', '2', '3', |
| '4', '5', '6', '7', |
| '8', '9', '*', '#', |
| 'A', 'B', 'C', 'D'}; |
| |
| /* Zero audio frame samples */ |
| static pj_int16_t zero_frame[2 * 30 * 16000 / 1000]; |
| |
| /* |
| * Print error. |
| */ |
| static void stream_perror(const char *sender, const char *title, |
| pj_status_t status) |
| { |
| char errmsg[PJ_ERR_MSG_SIZE]; |
| |
| pj_strerror(status, errmsg, sizeof(errmsg)); |
| PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status)); |
| } |
| |
| |
| static pj_status_t send_rtcp(pjmedia_stream *stream, |
| pj_bool_t with_sdes, |
| pj_bool_t with_bye, |
| pj_bool_t with_xr); |
| |
| |
| #if TRACE_JB |
| |
| PJ_INLINE(int) trace_jb_print_timestamp(char **buf, pj_ssize_t len) |
| { |
| pj_time_val now; |
| pj_parsed_time ptime; |
| char *p = *buf; |
| |
| if (len < 14) |
| return -1; |
| |
| pj_gettimeofday(&now); |
| pj_time_decode(&now, &ptime); |
| p += pj_utoa_pad(ptime.hour, p, 2, '0'); |
| *p++ = ':'; |
| p += pj_utoa_pad(ptime.min, p, 2, '0'); |
| *p++ = ':'; |
| p += pj_utoa_pad(ptime.sec, p, 2, '0'); |
| *p++ = '.'; |
| p += pj_utoa_pad(ptime.msec, p, 3, '0'); |
| *p++ = ','; |
| |
| *buf = p; |
| |
| return 0; |
| } |
| |
| PJ_INLINE(int) trace_jb_print_state(pjmedia_stream *stream, |
| char **buf, pj_ssize_t len) |
| { |
| char *p = *buf; |
| char *endp = *buf + len; |
| pjmedia_jb_state state; |
| |
| pjmedia_jbuf_get_state(stream->jb, &state); |
| |
| len = pj_ansi_snprintf(p, endp-p, "%d, %d, %d", |
| state.size, state.burst, state.prefetch); |
| if ((len < 0) || (len >= endp-p)) |
| return -1; |
| |
| p += len; |
| *buf = p; |
| return 0; |
| } |
| |
| static void trace_jb_get(pjmedia_stream *stream, pjmedia_jb_frame_type ft, |
| pj_size_t fsize) |
| { |
| char *p = stream->trace_jb_buf; |
| char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; |
| pj_ssize_t len = 0; |
| const char* ft_st; |
| |
| if (!TRACE_JB_OPENED(stream)) |
| return; |
| |
| /* Print timestamp. */ |
| if (trace_jb_print_timestamp(&p, endp-p)) |
| goto on_insuff_buffer; |
| |
| /* Print frame type and size */ |
| switch(ft) { |
| case PJMEDIA_JB_MISSING_FRAME: |
| ft_st = "missing"; |
| break; |
| case PJMEDIA_JB_NORMAL_FRAME: |
| ft_st = "normal"; |
| break; |
| case PJMEDIA_JB_ZERO_PREFETCH_FRAME: |
| ft_st = "prefetch"; |
| break; |
| case PJMEDIA_JB_ZERO_EMPTY_FRAME: |
| ft_st = "empty"; |
| break; |
| default: |
| ft_st = "unknown"; |
| break; |
| } |
| |
| /* Print operation, size, frame count, frame type */ |
| len = pj_ansi_snprintf(p, endp-p, "GET,%d,1,%s,,,,", fsize, ft_st); |
| if ((len < 0) || (len >= endp-p)) |
| goto on_insuff_buffer; |
| p += len; |
| |
| /* Print JB state */ |
| if (trace_jb_print_state(stream, &p, endp-p)) |
| goto on_insuff_buffer; |
| |
| /* Print end of line */ |
| if (endp-p < 2) |
| goto on_insuff_buffer; |
| *p++ = '\n'; |
| |
| /* Write and flush */ |
| len = p - stream->trace_jb_buf; |
| pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); |
| pj_file_flush(stream->trace_jb_fd); |
| return; |
| |
| on_insuff_buffer: |
| pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); |
| } |
| |
| static void trace_jb_put(pjmedia_stream *stream, const pjmedia_rtp_hdr *hdr, |
| unsigned payloadlen, unsigned frame_cnt) |
| { |
| char *p = stream->trace_jb_buf; |
| char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; |
| pj_ssize_t len = 0; |
| |
| if (!TRACE_JB_OPENED(stream)) |
| return; |
| |
| /* Print timestamp. */ |
| if (trace_jb_print_timestamp(&p, endp-p)) |
| goto on_insuff_buffer; |
| |
| /* Print operation, size, frame count, RTP info */ |
| len = pj_ansi_snprintf(p, endp-p, |
| "PUT,%d,%d,,%d,%d,%d,", |
| payloadlen, frame_cnt, |
| pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), hdr->m); |
| if ((len < 0) || (len >= endp-p)) |
| goto on_insuff_buffer; |
| p += len; |
| |
| /* Print JB state */ |
| if (trace_jb_print_state(stream, &p, endp-p)) |
| goto on_insuff_buffer; |
| |
| /* Print end of line */ |
| if (endp-p < 2) |
| goto on_insuff_buffer; |
| *p++ = '\n'; |
| |
| /* Write and flush */ |
| len = p - stream->trace_jb_buf; |
| pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); |
| pj_file_flush(stream->trace_jb_fd); |
| return; |
| |
| on_insuff_buffer: |
| pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); |
| } |
| |
| #endif /* TRACE_JB */ |
| |
| |
| #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 |
| /* |
| * Send keep-alive packet using non-codec frame. |
| */ |
| static void send_keep_alive_packet(pjmedia_stream *stream) |
| { |
| #if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP |
| |
| /* Keep-alive packet is empty RTP */ |
| pj_status_t status; |
| void *pkt; |
| int pkt_len; |
| |
| TRC_((stream->port.info.name.ptr, |
| "Sending keep-alive (RTCP and empty RTP)")); |
| |
| /* Send RTP */ |
| status = pjmedia_rtp_encode_rtp( &stream->enc->rtp, |
| stream->enc->pt, 0, |
| 1, |
| 0, |
| (const void**)&pkt, |
| &pkt_len); |
| pj_assert(status == PJ_SUCCESS); |
| |
| pj_memcpy(stream->enc->out_pkt, pkt, pkt_len); |
| pjmedia_transport_send_rtp(stream->transport, stream->enc->out_pkt, |
| pkt_len); |
| |
| /* Send RTCP */ |
| send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE); |
| |
| #elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER |
| |
| /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */ |
| int pkt_len; |
| const pj_str_t str_ka = PJMEDIA_STREAM_KA_USER_PKT; |
| |
| TRC_((stream->port.info.name.ptr, |
| "Sending keep-alive (custom RTP/RTCP packets)")); |
| |
| /* Send to RTP port */ |
| pj_memcpy(stream->enc->out_pkt, str_ka.ptr, str_ka.slen); |
| pkt_len = str_ka.slen; |
| pjmedia_transport_send_rtp(stream->transport, stream->enc->out_pkt, |
| pkt_len); |
| |
| /* Send to RTCP port */ |
| pjmedia_transport_send_rtcp(stream->transport, stream->enc->out_pkt, |
| pkt_len); |
| |
| #else |
| |
| PJ_UNUSED_ARG(stream); |
| |
| #endif |
| } |
| #endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */ |
| |
| /* |
| * 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_channel *channel = stream->dec; |
| unsigned samples_count, samples_per_frame, samples_required; |
| pj_int16_t *p_out_samp; |
| pj_status_t status; |
| |
| |
| /* Return no frame is 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. |
| */ |
| |
| /* Lock jitter buffer mutex first */ |
| pj_mutex_lock( stream->jb_mutex ); |
| |
| samples_required = PJMEDIA_PIA_SPF(&stream->port.info); |
| samples_per_frame = stream->codec_param.info.frm_ptime * |
| stream->codec_param.info.clock_rate * |
| stream->codec_param.info.channel_cnt / |
| 1000; |
| p_out_samp = (pj_int16_t*) frame->buf; |
| |
| for (samples_count=0; samples_count < samples_required; |
| samples_count += samples_per_frame) |
| { |
| char frame_type; |
| pj_size_t frame_size; |
| pj_uint32_t bit_info; |
| |
| /* Get frame from jitter buffer. */ |
| pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size, |
| &frame_type, &bit_info); |
| |
| #if TRACE_JB |
| trace_jb_get(stream, frame_type, frame_size); |
| #endif |
| |
| if (frame_type == PJMEDIA_JB_MISSING_FRAME) { |
| |
| /* Activate PLC */ |
| if (stream->codec->op->recover && |
| stream->codec_param.setting.plc && |
| stream->plc_cnt < stream->max_plc_cnt) |
| { |
| pjmedia_frame frame_out; |
| |
| frame_out.buf = p_out_samp + samples_count; |
| frame_out.size = frame->size - samples_count*2; |
| status = pjmedia_codec_recover(stream->codec, |
| (unsigned)frame_out.size, |
| &frame_out); |
| |
| ++stream->plc_cnt; |
| |
| } else { |
| status = -1; |
| } |
| |
| if (status != PJ_SUCCESS) { |
| /* Either PLC failed or PLC not supported/enabled */ |
| pjmedia_zero_samples(p_out_samp + samples_count, |
| samples_required - samples_count); |
| } |
| |
| if (frame_type != stream->jb_last_frm) { |
| /* Report changing frame type event */ |
| PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost%s!", |
| (status == PJ_SUCCESS? ", recovered":""))); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->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 != stream->jb_last_frm) { |
| if (1) { |
| /* Activate PLC to smoothen the missing frame */ |
| if (stream->codec->op->recover && |
| stream->codec_param.setting.plc && |
| stream->plc_cnt < stream->max_plc_cnt) |
| { |
| pjmedia_frame frame_out; |
| |
| do { |
| frame_out.buf = p_out_samp + samples_count; |
| frame_out.size = frame->size - samples_count*2; |
| status = pjmedia_codec_recover(stream->codec, |
| (unsigned)frame_out.size, |
| &frame_out); |
| if (status != PJ_SUCCESS) |
| break; |
| |
| samples_count += samples_per_frame; |
| ++stream->plc_cnt; |
| |
| } while (samples_count < samples_required && |
| stream->plc_cnt < stream->max_plc_cnt); |
| |
| with_plc = ", plc invoked"; |
| } |
| } |
| |
| if (samples_count < samples_required) { |
| pjmedia_zero_samples(p_out_samp + samples_count, |
| samples_required - samples_count); |
| samples_count = samples_required; |
| } |
| |
| if (stream->jb_last_frm != frame_type) { |
| pjmedia_jb_state jb_state; |
| |
| /* Report changing frame type event */ |
| pjmedia_jbuf_get_state(stream->jb, &jb_state); |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer empty (prefetch=%d)%s", |
| jb_state.prefetch, with_plc)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| break; |
| |
| } else if (frame_type != PJMEDIA_JB_NORMAL_FRAME) { |
| |
| const char *with_plc = ""; |
| |
| /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */ |
| pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME); |
| |
| /* Always activate PLC when it's available.. */ |
| if (stream->codec->op->recover && |
| stream->codec_param.setting.plc && |
| stream->plc_cnt < stream->max_plc_cnt) |
| { |
| pjmedia_frame frame_out; |
| |
| do { |
| frame_out.buf = p_out_samp + samples_count; |
| frame_out.size = frame->size - samples_count*2; |
| status = pjmedia_codec_recover(stream->codec, |
| (unsigned)frame_out.size, |
| &frame_out); |
| if (status != PJ_SUCCESS) |
| break; |
| samples_count += samples_per_frame; |
| |
| ++stream->plc_cnt; |
| |
| } while (samples_count < samples_required && |
| stream->plc_cnt < stream->max_plc_cnt); |
| |
| with_plc = ", plc invoked"; |
| } |
| |
| if (samples_count < samples_required) { |
| pjmedia_zero_samples(p_out_samp + samples_count, |
| samples_required - samples_count); |
| samples_count = samples_required; |
| } |
| |
| if (stream->jb_last_frm != frame_type) { |
| pjmedia_jb_state jb_state; |
| |
| /* Report changing frame type event */ |
| pjmedia_jbuf_get_state(stream->jb, &jb_state); |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer is bufferring (prefetch=%d)%s", |
| jb_state.prefetch, with_plc)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| break; |
| |
| } else { |
| /* Got "NORMAL" frame from jitter buffer */ |
| pjmedia_frame frame_in, frame_out; |
| |
| stream->plc_cnt = 0; |
| |
| /* Decode */ |
| frame_in.buf = channel->out_pkt; |
| 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; |
| status = pjmedia_codec_decode( stream->codec, &frame_in, |
| (unsigned)frame_out.size, |
| &frame_out); |
| if (status != 0) { |
| LOGERR_((port->info.name.ptr, "codec decode() error", |
| status)); |
| |
| pjmedia_zero_samples(p_out_samp + samples_count, |
| samples_per_frame); |
| } |
| |
| if (stream->jb_last_frm != frame_type) { |
| /* Report changing frame type event */ |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer starts returning normal frames " |
| "(after %d empty/lost)", |
| stream->jb_last_frm_cnt, stream->jb_last_frm)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| } |
| } |
| |
| |
| /* Unlock jitter buffer mutex. */ |
| pj_mutex_unlock( stream->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_channel *channel = stream->dec; |
| pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame; |
| unsigned samples_per_frame, samples_required; |
| 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(&stream->port.info); |
| samples_per_frame = stream->codec_param.info.frm_ptime * |
| stream->codec_param.info.clock_rate * |
| stream->codec_param.info.channel_cnt / |
| 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; |
| pj_uint32_t bit_info; |
| |
| /* Lock jitter buffer mutex first */ |
| pj_mutex_lock( stream->jb_mutex ); |
| |
| /* Get frame from jitter buffer. */ |
| pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size, |
| &frame_type, &bit_info); |
| |
| #if TRACE_JB |
| trace_jb_get(stream, frame_type, frame_size); |
| #endif |
| |
| /* Unlock jitter buffer mutex. */ |
| pj_mutex_unlock( stream->jb_mutex ); |
| |
| if (frame_type == PJMEDIA_JB_NORMAL_FRAME) { |
| /* Got "NORMAL" frame from jitter buffer */ |
| pjmedia_frame frame_in; |
| |
| /* Decode */ |
| frame_in.buf = channel->out_pkt; |
| 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, "codec decode() error", |
| status)); |
| pjmedia_frame_ext_append_subframe(f, NULL, 0, |
| (pj_uint16_t)samples_per_frame); |
| } |
| |
| if (stream->jb_last_frm != frame_type) { |
| /* Report changing frame type event */ |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer starts returning normal frames " |
| "(after %d empty/lost)", |
| stream->jb_last_frm_cnt, stream->jb_last_frm)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| |
| } 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 != stream->jb_last_frm) { |
| /* Report changing frame type event */ |
| PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!")); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) { |
| if (frame_type != stream->jb_last_frm) { |
| pjmedia_jb_state jb_state; |
| |
| /* Report changing frame type event */ |
| pjmedia_jbuf_get_state(stream->jb, &jb_state); |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer empty (prefetch=%d)", |
| jb_state.prefetch)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->jb_last_frm_cnt++; |
| } |
| } else { |
| |
| /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */ |
| pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME); |
| |
| if (stream->jb_last_frm != frame_type) { |
| pjmedia_jb_state jb_state; |
| |
| /* Report changing frame type event */ |
| pjmedia_jbuf_get_state(stream->jb, &jb_state); |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Jitter buffer is bufferring (prefetch=%d)", |
| jb_state.prefetch)); |
| |
| stream->jb_last_frm = frame_type; |
| stream->jb_last_frm_cnt = 1; |
| } else { |
| stream->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_rtp_dtmf_event *event; |
| struct dtmf *digit = &stream->tx_dtmf_buf[0]; |
| pj_uint32_t cur_ts; |
| |
| pj_assert(sizeof(pjmedia_rtp_dtmf_event) == 4); |
| |
| *first = *last = 0; |
| |
| event = (pjmedia_rtp_dtmf_event*) frame_out->buf; |
| cur_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts); |
| |
| if (digit->duration == 0) { |
| PJ_LOG(5,(stream->port.info.name.ptr, "Sending DTMF digit id %c", |
| digitmap[digit->event])); |
| *first = 1; |
| } |
| |
| digit->duration += PJMEDIA_PIA_SPF(&stream->port.info); |
| if (digit->duration >= PJMEDIA_DTMF_DURATION) |
| digit->duration = PJMEDIA_DTMF_DURATION; |
| |
| event->event = (pj_uint8_t)digit->event; |
| event->e_vol = 10; |
| event->duration = pj_htons((pj_uint16_t)digit->duration); |
| |
| if (forced_last) { |
| digit->duration = PJMEDIA_DTMF_DURATION; |
| } |
| |
| if (digit->duration >= PJMEDIA_DTMF_DURATION) { |
| |
| event->e_vol |= 0x80; |
| |
| if (++digit->ebit_cnt >= DTMF_EBIT_RETRANSMIT_CNT) { |
| *last = 1; |
| |
| /* Prepare next digit. */ |
| pj_mutex_lock(stream->jb_mutex); |
| |
| pj_array_erase(stream->tx_dtmf_buf, sizeof(stream->tx_dtmf_buf[0]), |
| stream->tx_dtmf_count, 0); |
| --stream->tx_dtmf_count; |
| |
| pj_mutex_unlock(stream->jb_mutex); |
| } |
| } |
| |
| frame_out->size = 4; |
| } |
| |
| |
| static pj_status_t send_rtcp(pjmedia_stream *stream, |
| pj_bool_t with_sdes, |
| pj_bool_t with_bye, |
| pj_bool_t with_xr) |
| { |
| void *sr_rr_pkt; |
| pj_uint8_t *pkt; |
| int len, max_len; |
| pj_status_t status; |
| |
| /* Build RTCP RR/SR packet */ |
| pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); |
| |
| #if !defined(PJMEDIA_HAS_RTCP_XR) || (PJMEDIA_HAS_RTCP_XR == 0) |
| with_xr = PJ_FALSE; |
| #endif |
| |
| if (with_sdes || with_bye || with_xr) { |
| pkt = (pj_uint8_t*) stream->out_rtcp_pkt; |
| pj_memcpy(pkt, sr_rr_pkt, len); |
| max_len = stream->out_rtcp_pkt_size; |
| } else { |
| pkt = (pj_uint8_t*)sr_rr_pkt; |
| max_len = len; |
| } |
| |
| /* Build RTCP SDES packet */ |
| if (with_sdes) { |
| pjmedia_rtcp_sdes sdes; |
| pj_size_t sdes_len; |
| |
| pj_bzero(&sdes, sizeof(sdes)); |
| sdes.cname = stream->cname; |
| sdes_len = max_len - len; |
| status = pjmedia_rtcp_build_rtcp_sdes(&stream->rtcp, pkt+len, |
| &sdes_len, &sdes); |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error generating RTCP SDES")); |
| } else { |
| len += (int)sdes_len; |
| } |
| } |
| |
| /* Build RTCP XR packet */ |
| #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) |
| if (with_xr) { |
| int i; |
| pjmedia_jb_state jb_state; |
| void *xr_pkt; |
| int xr_len; |
| |
| /* Update RTCP XR with current JB states */ |
| pjmedia_jbuf_get_state(stream->jb, &jb_state); |
| |
| i = jb_state.avg_delay; |
| status = pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session, |
| PJMEDIA_RTCP_XR_INFO_JB_NOM, i); |
| pj_assert(status == PJ_SUCCESS); |
| |
| i = jb_state.max_delay; |
| status = pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session, |
| PJMEDIA_RTCP_XR_INFO_JB_MAX, i); |
| pj_assert(status == PJ_SUCCESS); |
| |
| pjmedia_rtcp_build_rtcp_xr(&stream->rtcp.xr_session, 0, |
| &xr_pkt, &xr_len); |
| |
| if (xr_len + len <= max_len) { |
| pj_memcpy(pkt+len, xr_pkt, xr_len); |
| len += xr_len; |
| |
| /* Send the RTCP XR to third-party destination if specified */ |
| if (stream->rtcp_xr_dest_len) { |
| pjmedia_transport_send_rtcp2(stream->transport, |
| &stream->rtcp_xr_dest, |
| stream->rtcp_xr_dest_len, |
| xr_pkt, xr_len); |
| } |
| |
| } else { |
| PJ_PERROR(4,(stream->port.info.name.ptr, PJ_ETOOBIG, |
| "Error generating RTCP-XR")); |
| } |
| } |
| #endif |
| |
| /* Build RTCP BYE packet */ |
| if (with_bye) { |
| pj_size_t bye_len; |
| |
| bye_len = max_len - len; |
| status = pjmedia_rtcp_build_rtcp_bye(&stream->rtcp, pkt+len, |
| &bye_len, NULL); |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error generating RTCP BYE")); |
| } else { |
| len += (int)bye_len; |
| } |
| } |
| |
| /* Send! */ |
| status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); |
| |
| return status; |
| } |
| |
| /** |
| * 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) |
| { |
| /* Note that timestamp may represent local or remote timestamp, |
| * depending on whether this function is called from put_frame() |
| * or get_frame(). |
| */ |
| |
| if (stream->rtcp_last_tx == 0) { |
| |
| stream->rtcp_last_tx = timestamp; |
| |
| } else if (timestamp - stream->rtcp_last_tx >= stream->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 (stream->rtcp.xr_enabled) { |
| if (stream->rtcp_xr_last_tx == 0) { |
| stream->rtcp_xr_last_tx = timestamp; |
| } else if (timestamp - stream->rtcp_xr_last_tx >= |
| stream->rtcp_xr_interval) |
| { |
| with_xr = PJ_TRUE; |
| |
| /* Update last tx RTCP XR */ |
| stream->rtcp_xr_last_tx = timestamp; |
| } |
| } |
| #endif |
| |
| status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE, |
| with_xr); |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error sending RTCP")); |
| } |
| |
| stream->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) |
| { |
| /* 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(stream->enc_buf, |
| stream->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(stream->enc_buf + stream->enc_buf_count, |
| frame->buf, frame->size); |
| } else { |
| pj_bzero(stream->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(&stream->port.info) / 1000; |
| |
| /* See if we have enough samples */ |
| if (stream->enc_buf_count >= count) { |
| |
| frame->type = PJMEDIA_FRAME_TYPE_AUDIO; |
| frame->buf = stream->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_channel *channel = stream->enc; |
| pj_status_t status = 0; |
| pjmedia_frame frame_out; |
| unsigned ts_len, rtp_ts_len, samples_per_frame; |
| 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 (stream->use_ka) |
| { |
| pj_uint32_t dtx_duration; |
| |
| dtx_duration = pj_timestamp_diff32(&stream->last_frm_ts_sent, |
| &frame->timestamp); |
| if (dtx_duration > |
| PJMEDIA_STREAM_KA_INTERVAL * PJMEDIA_PIA_SRATE(&stream->port.info)) |
| { |
| send_keep_alive_packet(stream); |
| stream->last_frm_ts_sent = frame->timestamp; |
| } |
| } |
| #endif |
| |
| /* Don't do anything if stream is paused */ |
| if (channel->paused) { |
| stream->enc_buf_pos = stream->enc_buf_count = 0; |
| return PJ_SUCCESS; |
| } |
| |
| /* 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(&stream->port.info) / |
| PJMEDIA_PIA_CCNT(&stream->port.info); |
| else |
| ts_len = 0; |
| |
| /* Increment transmit duration */ |
| stream->tx_duration += ts_len; |
| |
| #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 |
| |
| /* Init frame_out buffer. */ |
| frame_out.buf = ((char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr); |
| frame_out.size = 0; |
| |
| /* Calculate number of samples per frame */ |
| samples_per_frame = stream->enc_samples_per_pkt; |
| |
| |
| /* If we have DTMF digits in the queue, transmit the digits. |
| * Otherwise encode the PCM buffer. |
| */ |
| if (stream->tx_dtmf_count) { |
| 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 = PJMEDIA_DTMF_DURATION + |
| ((DTMF_EBIT_RETRANSMIT_CNT-1) * samples_per_frame) |
| - 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 http://trac.pjsip.org/repos/ticket/56, |
| * but then disabled in http://trac.pjsip.org/repos/ticket/439, but |
| * now it's enabled again. |
| */ |
| } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO && |
| frame->buf == NULL && |
| stream->port.info.fmt.id == PJMEDIA_FORMAT_L16 && |
| (stream->dir & PJMEDIA_DIR_ENCODING) && |
| stream->codec_param.info.frm_ptime * |
| stream->codec_param.info.channel_cnt * |
| stream->codec_param.info.clock_rate/1000 < |
| PJ_ARRAY_SIZE(zero_frame)) |
| { |
| pjmedia_frame silence_frame; |
| |
| pj_bzero(&silence_frame, sizeof(silence_frame)); |
| silence_frame.buf = zero_frame; |
| silence_frame.size = stream->codec_param.info.frm_ptime * 2 * |
| stream->codec_param.info.channel_cnt * |
| stream->codec_param.info.clock_rate / 1000; |
| silence_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
| silence_frame.timestamp.u32.lo = pj_ntohl(stream->enc->rtp.out_hdr.ts); |
| |
| /* Encode! */ |
| status = pjmedia_codec_encode( stream->codec, &silence_frame, |
| channel->out_pkt_size - |
| sizeof(pjmedia_rtp_hdr), |
| &frame_out); |
| if (status != PJ_SUCCESS) { |
| LOGERR_((stream->port.info.name.ptr, |
| "Codec encode() error", status)); |
| 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)) |
| { |
| /* Encode! */ |
| status = pjmedia_codec_encode( stream->codec, frame, |
| channel->out_pkt_size - |
| sizeof(pjmedia_rtp_hdr), |
| &frame_out); |
| if (status != PJ_SUCCESS) { |
| LOGERR_((stream->port.info.name.ptr, |
| "Codec encode() error", status)); |
| 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_((stream->port.info.name.ptr, |
| "RTP encode_rtp() error", status)); |
| 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 (stream->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,(stream->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->out_pkt, 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->out_pkt; |
| |
| rtp->m = 1; |
| PJ_LOG(5,(stream->port.info.name.ptr,"Start talksprut..")); |
| } |
| |
| stream->is_streaming = PJ_TRUE; |
| |
| /* Send the RTP packet to the transport. */ |
| status = pjmedia_transport_send_rtp(stream->transport, channel->out_pkt, |
| frame_out.size + |
| sizeof(pjmedia_rtp_hdr)); |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error sending RTP")); |
| } |
| |
| /* Update stat */ |
| pjmedia_rtcp_tx_rtp(&stream->rtcp, (unsigned)frame_out.size); |
| stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts); |
| stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq); |
| |
| #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 |
| /* Update timestamp of last sending packet. */ |
| stream->last_frm_ts_sent = frame->timestamp; |
| #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_frame tmp_zero_frame; |
| unsigned samples_per_frame; |
| |
| samples_per_frame = stream->enc_samples_per_pkt; |
| |
| /* http://www.pjsip.org/trac/ticket/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(&stream->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,(stream->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 (stream->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 void *payload, unsigned payloadlen) |
| { |
| pjmedia_rtp_dtmf_event *event = (pjmedia_rtp_dtmf_event*) payload; |
| |
| /* 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); |
| |
| /* Check if this is the same/current digit of the last packet. */ |
| if (stream->last_dtmf != -1 && |
| event->event == stream->last_dtmf && |
| pj_ntohs(event->duration) >= stream->last_dtmf_dur) |
| { |
| /* Yes, this is the same event. */ |
| stream->last_dtmf_dur = pj_ntohs(event->duration); |
| return; |
| } |
| |
| /* Ignore unknown event. */ |
| if (event->event > 15) { |
| PJ_LOG(5,(stream->port.info.name.ptr, |
| "Ignored RTP pkt with bad DTMF event %d", |
| event->event)); |
| return; |
| } |
| |
| /* New event! */ |
| PJ_LOG(5,(stream->port.info.name.ptr, "Received DTMF digit %c, vol=%d", |
| digitmap[event->event], |
| (event->e_vol & 0x3F))); |
| |
| stream->last_dtmf = event->event; |
| stream->last_dtmf_dur = pj_ntohs(event->duration); |
| |
| /* If DTMF callback is installed, call the callback, otherwise keep |
| * the DTMF digits in the buffer. |
| */ |
| 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(stream->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(stream->jb_mutex); |
| } |
| } |
| |
| |
| /* |
| * This callback is called by stream transport on receipt of packets |
| * in the RTP socket. |
| */ |
| static void on_rx_rtp( void *data, |
| void *pkt, |
| pj_ssize_t bytes_read) |
| |
| { |
| pjmedia_stream *stream = (pjmedia_stream*) data; |
| pjmedia_channel *channel = stream->dec; |
| const pjmedia_rtp_hdr *hdr; |
| const void *payload; |
| unsigned payloadlen; |
| pjmedia_rtp_status seq_st; |
| pj_status_t status; |
| pj_bool_t pkt_discarded = PJ_FALSE; |
| |
| /* Check for errors */ |
| if (bytes_read < 0) { |
| LOGERR_((stream->port.info.name.ptr, "RTP recv() error", |
| (pj_status_t)-bytes_read)); |
| return; |
| } |
| |
| /* Ignore keep-alive packets */ |
| if (bytes_read < (pj_ssize_t) sizeof(pjmedia_rtp_hdr)) |
| return; |
| |
| /* Update RTP and RTCP session. */ |
| status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, (int)bytes_read, |
| &hdr, &payload, &payloadlen); |
| if (status != PJ_SUCCESS) { |
| LOGERR_((stream->port.info.name.ptr, "RTP decode error", status)); |
| stream->rtcp.stat.rx.discard++; |
| return; |
| } |
| |
| /* Ignore the packet if decoder is paused */ |
| if (channel->paused) |
| goto on_return; |
| |
| /* Update RTP session (also checks if RTP session can accept |
| * the incoming packet. |
| */ |
| pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, |
| hdr->pt != stream->rx_event_pt); |
| if (seq_st.status.value) { |
| TRC_ ((stream->port.info.name.ptr, |
| "RTP status: badpt=%d, badssrc=%d, dup=%d, " |
| "outorder=%d, probation=%d, restart=%d", |
| seq_st.status.flag.badpt, |
| seq_st.status.flag.badssrc, |
| seq_st.status.flag.dup, |
| seq_st.status.flag.outorder, |
| seq_st.status.flag.probation, |
| seq_st.status.flag.restart)); |
| |
| if (seq_st.status.flag.badpt) { |
| PJ_LOG(4,(stream->port.info.name.ptr, |
| "Bad RTP pt %d (expecting %d)", |
| hdr->pt, channel->rtp.out_pt)); |
| } |
| |
| if (seq_st.status.flag.badssrc) { |
| PJ_LOG(4,(stream->port.info.name.ptr, |
| "Changed RTP peer SSRC %d (previously %d)", |
| channel->rtp.peer_ssrc, stream->rtcp.peer_ssrc)); |
| stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; |
| } |
| |
| |
| } |
| |
| /* Skip bad RTP packet */ |
| if (seq_st.status.flag.bad) { |
| pkt_discarded = PJ_TRUE; |
| goto on_return; |
| } |
| |
| /* Ignore if payloadlen is zero */ |
| if (payloadlen == 0) { |
| pkt_discarded = PJ_TRUE; |
| goto on_return; |
| } |
| |
| /* 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, payload, payloadlen); |
| goto on_return; |
| } |
| |
| /* Put "good" packet to jitter buffer, or reset the jitter buffer |
| * when RTP session is restarted. |
| */ |
| pj_mutex_lock( stream->jb_mutex ); |
| if (seq_st.status.flag.restart) { |
| status = pjmedia_jbuf_reset(stream->jb); |
| PJ_LOG(4,(stream->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 }; |
| pj_timestamp ts; |
| unsigned i, count = MAX; |
| unsigned ts_span; |
| pjmedia_frame frames[MAX]; |
| |
| /* Get the timestamp of the first sample */ |
| ts.u64 = pj_ntohl(hdr->ts); |
| |
| /* Parse the payload. */ |
| status = pjmedia_codec_parse(stream->codec, (void*)payload, |
| payloadlen, &ts, &count, frames); |
| if (status != PJ_SUCCESS) { |
| LOGERR_((stream->port.info.name.ptr, |
| "Codec parse() error", |
| status)); |
| count = 0; |
| } |
| |
| #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 && stream->rtp_rx_last_ts && |
| ts.u64 > stream->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(&stream->port.info) / |
| stream->codec_param.setting.frm_per_pkt/ |
| PJMEDIA_PIA_CCNT(&stream->port.info); |
| |
| /* Get remote frame timestamp span */ |
| peer_frm_ts_diff = |
| ((pj_uint32_t)ts.u64-stream->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(stream->jb); |
| } |
| } |
| } |
| |
| stream->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 + ts_span * i; |
| } |
| |
| } else { |
| ts_span = stream->codec_param.info.frm_ptime * |
| stream->codec_param.info.clock_rate / |
| 1000; |
| } |
| #else |
| ts_span = stream->codec_param.info.frm_ptime * |
| stream->codec_param.info.clock_rate / |
| 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_frame2(stream->jb, frames[i].buf, frames[i].size, |
| frames[i].bit_info, ext_seq, &discarded); |
| if (discarded) |
| pkt_discarded = PJ_TRUE; |
| } |
| |
| #if TRACE_JB |
| trace_jb_put(stream, hdr, payloadlen, count); |
| #endif |
| |
| } |
| pj_mutex_unlock( stream->jb_mutex ); |
| |
| |
| /* Check if now is the time to transmit RTCP SR/RR report. |
| * We only do this when stream direction is "decoding only", |
| * because otherwise check_tx_rtcp() will be handled by put_frame() |
| */ |
| if (stream->dir == PJMEDIA_DIR_DECODING) { |
| check_tx_rtcp(stream, pj_ntohl(hdr->ts)); |
| } |
| |
| if (status != 0) { |
| LOGERR_((stream->port.info.name.ptr, "Jitter buffer put() error", |
| status)); |
| pkt_discarded = PJ_TRUE; |
| goto on_return; |
| } |
| |
| on_return: |
| /* Update RTCP session */ |
| if (stream->rtcp.peer_ssrc == 0) |
| stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; |
| |
| pjmedia_rtcp_rx_rtp2(&stream->rtcp, pj_ntohs(hdr->seq), |
| pj_ntohl(hdr->ts), payloadlen, pkt_discarded); |
| |
| /* Send RTCP RR and SDES after we receive some RTP packets */ |
| if (stream->rtcp.received >= 10 && !stream->initial_rr) { |
| status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, |
| PJ_FALSE, PJ_FALSE); |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error sending initial RTCP RR")); |
| } else { |
| stream->initial_rr = PJ_TRUE; |
| } |
| } |
| } |
| |
| |
| /* |
| * This callback is called by stream transport on receipt of packets |
| * in the RTCP socket. |
| */ |
| static void on_rx_rtcp( void *data, |
| void *pkt, |
| pj_ssize_t bytes_read) |
| { |
| pjmedia_stream *stream = (pjmedia_stream*) data; |
| |
| /* Check for errors */ |
| if (bytes_read < 0) { |
| LOGERR_((stream->port.info.name.ptr, "RTCP recv() error", |
| (pj_status_t)-bytes_read)); |
| return; |
| } |
| |
| pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); |
| } |
| |
| |
| /* |
| * Create media channel. |
| */ |
| static pj_status_t create_channel( pj_pool_t *pool, |
| pjmedia_stream *stream, |
| pjmedia_dir dir, |
| unsigned pt, |
| const pjmedia_stream_info *param, |
| pjmedia_channel **p_channel) |
| { |
| pjmedia_channel *channel; |
| pj_status_t status; |
| |
| /* Allocate memory for channel descriptor */ |
| |
| channel = PJ_POOL_ZALLOC_T(pool, pjmedia_channel); |
| PJ_ASSERT_RETURN(channel != NULL, PJ_ENOMEM); |
| |
| /* Init channel info. */ |
| |
| channel->stream = stream; |
| channel->dir = dir; |
| channel->paused = 1; |
| channel->pt = pt; |
| |
| |
| /* Allocate buffer for outgoing packet. */ |
| |
| if (param->type == PJMEDIA_TYPE_AUDIO) { |
| channel->out_pkt_size = sizeof(pjmedia_rtp_hdr) + |
| stream->codec_param.info.max_bps * |
| PJMEDIA_MAX_FRAME_DURATION_MS / |
| 8 / 1000; |
| if (channel->out_pkt_size > PJMEDIA_MAX_MTU - |
| PJMEDIA_STREAM_RESV_PAYLOAD_LEN) |
| { |
| channel->out_pkt_size = PJMEDIA_MAX_MTU - |
| PJMEDIA_STREAM_RESV_PAYLOAD_LEN; |
| } |
| } else { |
| return PJ_ENOTSUP; |
| } |
| |
| channel->out_pkt = pj_pool_alloc(pool, channel->out_pkt_size); |
| PJ_ASSERT_RETURN(channel->out_pkt != NULL, PJ_ENOMEM); |
| |
| |
| |
| /* Create RTP and RTCP sessions: */ |
| |
| if (param->rtp_seq_ts_set == 0) { |
| status = pjmedia_rtp_session_init(&channel->rtp, pt, param->ssrc); |
| } else { |
| pjmedia_rtp_session_setting settings; |
| |
| settings.flags = (pj_uint8_t)((param->rtp_seq_ts_set << 2) | 3); |
| settings.default_pt = pt; |
| settings.sender_ssrc = param->ssrc; |
| settings.seq = param->rtp_seq; |
| settings.ts = param->rtp_ts; |
| status = pjmedia_rtp_session_init2(&channel->rtp, settings); |
| } |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Done. */ |
| *p_channel = channel; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * 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; |
| 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; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(endpt && info && p_stream, PJ_EINVAL); |
| |
| if (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); |
| stream->own_pool = own_pool; |
| pj_memcpy(&stream->si, info, sizeof(*info)); |
| stream->si.param = pjmedia_codec_param_clone(pool, info->param); |
| pj_strdup(pool, &stream->si.fmt.encoding_name, &info->fmt.encoding_name); |
| |
| /* 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(&stream->port.info, &name, |
| PJMEDIA_SIG_PORT_STREAM, |
| info->fmt.clock_rate, info->fmt.channel_cnt, |
| 16, 80); |
| afd = pjmedia_format_get_audio_format_detail(&stream->port.info.fmt, 1); |
| |
| /* Init port. */ |
| |
| //No longer there in 2.0 |
| //pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name); |
| afd->clock_rate = info->fmt.clock_rate; |
| afd->channel_count = info->fmt.channel_cnt; |
| stream->port.port_data.pdata = stream; |
| |
| /* Init stream: */ |
| stream->endpt = endpt; |
| stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); |
| stream->dir = info->dir; |
| stream->user_data = user_data; |
| stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) * |
| info->fmt.clock_rate / 1000; |
| stream->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; |
| stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME; |
| |
| #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 |
| stream->use_ka = info->use_ka; |
| #endif |
| |
| /* Build random RTCP CNAME. CNAME has user@host format */ |
| stream->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'; |
| stream->cname.slen = p - stream->cname.ptr; |
| |
| |
| /* Create mutex to protect jitter buffer: */ |
| |
| status = pj_mutex_create_simple(pool, NULL, &stream->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 = *info->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; |
| |
| /* Init the codec. */ |
| status = pjmedia_codec_init(stream->codec, pool); |
| if (status != PJ_SUCCESS) |
| goto err_cleanup; |
| |
| /* Open the codec. */ |
| status = pjmedia_codec_open(stream->codec, &stream->codec_param); |
| if (status != PJ_SUCCESS) |
| goto err_cleanup; |
| |
| /* Set additional info and callbacks. */ |
| afd->bits_per_sample = 16; |
| afd->frame_time_usec = stream->codec_param.info.frm_ptime * |
| stream->codec_param.setting.frm_per_pkt * 1000; |
| stream->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; |
| |
| stream->port.put_frame = &put_frame; |
| stream->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) |
| { |
| ++stream->port.info.bytes_per_frame; |
| } |
| stream->port.info.format.bitrate = stream->codec_param.info.avg_bps; |
| stream->port.info.format.vad = (stream->codec_param.setting.vad != 0); |
| */ |
| |
| stream->port.put_frame = &put_frame; |
| stream->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) |
| { |
| unsigned ptime; |
| |
| stream->enc_samples_per_pkt = stream->codec_param.info.enc_ptime * |
| stream->codec_param.info.channel_cnt * |
| afd->clock_rate / 1000; |
| |
| /* Set buffer size as twice the largest ptime value between |
| * stream's ptime, encoder ptime, or decoder ptime. |
| */ |
| |
| ptime = afd->frame_time_usec / 1000; |
| |
| if (stream->codec_param.info.enc_ptime > ptime) |
| ptime = stream->codec_param.info.enc_ptime; |
| |
| if (stream->codec_param.info.frm_ptime > ptime) |
| ptime = stream->codec_param.info.frm_ptime; |
| |
| ptime <<= 1; |
| |
| /* Allocate buffer */ |
| stream->enc_buf_size = afd->clock_rate * ptime / 1000; |
| stream->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,(stream->port.info.name.ptr,"VAD temporarily disabled")); |
| } |
| |
| /* Get the frame size */ |
| if (stream->codec_param.info.max_rx_frame_size > 0) { |
| stream->frame_size = stream->codec_param.info.max_rx_frame_size; |
| } else { |
| stream->frame_size = stream->codec_param.info.max_bps * |
| stream->codec_param.info.frm_ptime / 8 / 1000; |
| if ((stream->codec_param.info.max_bps * |
| stream->codec_param.info.frm_ptime) % 8000 != 0) |
| { |
| ++stream->frame_size; |
| } |
| } |
| |
| /* How many consecutive PLC frames can be generated */ |
| stream->max_plc_cnt = (MAX_PLC_MSEC+stream->codec_param.info.frm_ptime-1)/ |
| stream->codec_param.info.frm_ptime; |
| |
| #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; |
| stream->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; |
| } |
| #endif |
| |
| /* Init jitter buffer parameters: */ |
| if (info->jb_max >= stream->codec_param.info.frm_ptime) |
| jb_max = (info->jb_max + stream->codec_param.info.frm_ptime - 1) / |
| stream->codec_param.info.frm_ptime; |
| else |
| jb_max = 500 / stream->codec_param.info.frm_ptime; |
| |
| if (info->jb_min_pre >= stream->codec_param.info.frm_ptime) |
| jb_min_pre = info->jb_min_pre / 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) |
| jb_max_pre = info->jb_max_pre / stream->codec_param.info.frm_ptime; |
| else |
| //jb_max_pre = 240 / stream->codec_param.info.frm_ptime; |
| jb_max_pre = jb_max * 4 / 5; |
| |
| if (info->jb_init >= stream->codec_param.info.frm_ptime) |
| jb_init = info->jb_init / 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, &stream->port.info.name, |
| stream->frame_size, |
| stream->codec_param.info.frm_ptime, |
| jb_max, &stream->jb); |
| if (status != PJ_SUCCESS) |
| goto err_cleanup; |
| |
| |
| /* Set up jitter buffer */ |
| pjmedia_jbuf_set_adaptive( stream->jb, jb_init, jb_min_pre, jb_max_pre); |
| |
| /* Create decoder channel: */ |
| |
| status = create_channel( pool, stream, PJMEDIA_DIR_DECODING, |
| info->rx_pt, info, &stream->dec); |
| if (status != PJ_SUCCESS) |
| goto err_cleanup; |
| |
| |
| /* Create encoder channel: */ |
| |
| status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING, |
| info->tx_pt, info, &stream->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 = stream->port.info.name.ptr; |
| rtcp_setting.ssrc = info->ssrc; |
| rtcp_setting.rtp_ts_base = pj_ntohl(stream->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; |
| } |
| #endif |
| |
| pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting); |
| |
| if (info->rtp_seq_ts_set) { |
| stream->rtcp.stat.rtp_tx_last_seq = info->rtp_seq; |
| stream->rtcp.stat.rtp_tx_last_ts = info->rtp_ts; |
| } |
| } |
| |
| /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES, |
| * BYE, and XR. |
| */ |
| stream->out_rtcp_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) + |
| sizeof(pjmedia_rtcp_common) + |
| (4 + (unsigned)stream->cname.slen) + |
| 32; |
| #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) |
| if (info->rtcp_xr_enabled) { |
| stream->out_rtcp_pkt_size += sizeof(pjmedia_rtcp_xr_pkt); |
| } |
| #endif |
| |
| if (stream->out_rtcp_pkt_size > PJMEDIA_MAX_MTU) |
| stream->out_rtcp_pkt_size = PJMEDIA_MAX_MTU; |
| |
| stream->out_rtcp_pkt = pj_pool_alloc(pool, stream->out_rtcp_pkt_size); |
| |
| /* Only attach transport when stream is ready. */ |
| status = pjmedia_transport_attach(tp, stream, &info->rem_addr, |
| &info->rem_rtcp, |
| pj_sockaddr_get_len(&info->rem_addr), |
| &on_rx_rtp, &on_rx_rtcp); |
| if (status != PJ_SUCCESS) |
| goto err_cleanup; |
| |
| stream->transport = tp; |
| |
| #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(&stream->rtcp, PJ_TRUE); |
| |
| /* Set RTCP XR TX interval */ |
| if (info->rtcp_xr_interval != 0) |
| stream->rtcp_xr_interval = info->rtcp_xr_interval; |
| else |
| stream->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) { |
| stream->rtcp_xr_dest_len = pj_sockaddr_get_len(&info->rtcp_xr_dest); |
| pj_memcpy(&stream->rtcp_xr_dest, &info->rtcp_xr_dest, |
| stream->rtcp_xr_dest_len); |
| } |
| |
| /* jitter buffer adaptive info */ |
| i = PJMEDIA_RTCP_XR_JB_ADAPTIVE; |
| pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session, |
| PJMEDIA_RTCP_XR_INFO_CONF_JBA, |
| i); |
| |
| /* Jitter buffer aggressiveness info (estimated) */ |
| i = 7; |
| pjmedia_rtcp_xr_update_info(&stream->rtcp.xr_session, |
| PJMEDIA_RTCP_XR_INFO_CONF_JBR, |
| i); |
| |
| /* Jitter buffer absolute maximum delay */ |
| i = jb_max * stream->codec_param.info.frm_ptime; |
| pjmedia_rtcp_xr_update_info(&stream->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(&stream->rtcp.xr_session, |
| PJMEDIA_RTCP_XR_INFO_CONF_PLC, |
| i); |
| } |
| #endif |
| |
| /* Send RTCP SDES */ |
| if (!stream->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 (stream->use_ka) |
| send_keep_alive_packet(stream); |
| #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", |
| stream->port.info.name.ptr); |
| status = pj_file_open(pool, trace_name, PJ_O_WRONLY, &stream->trace_jb_fd); |
| if (status != PJ_SUCCESS) { |
| stream->trace_jb_fd = TRACE_JB_INVALID_FD; |
| PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'", |
| trace_name)); |
| } else { |
| stream->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE); |
| |
| /* Print column header */ |
| len = pj_ansi_snprintf(stream->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"); |
| pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); |
| pj_file_flush(stream->trace_jb_fd); |
| } |
| } |
| #endif |
| |
| /* Success! */ |
| *p_stream = stream; |
| |
| PJ_LOG(5,(THIS_FILE, "Stream %s created", stream->port.info.name.ptr)); |
| |
| return PJ_SUCCESS; |
| |
| |
| err_cleanup: |
| pjmedia_stream_destroy(stream); |
| return status; |
| } |
| |
| |
| /* |
| * Destroy stream. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream ) |
| { |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); |
| |
| /* Send RTCP BYE (also SDES & XR) */ |
| if (!stream->rtcp_sdes_bye_disabled) { |
| send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_TRUE); |
| } |
| |
| /* 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) { |
| pjmedia_frame frame_out; |
| pjmedia_channel *channel = stream->enc; |
| int first=0, last=0; |
| void *rtphdr; |
| int rtphdrlen; |
| |
| pj_bzero(&frame_out, sizeof(frame_out)); |
| frame_out.buf = ((char*)channel->out_pkt) + 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->out_pkt, rtphdr, sizeof(pjmedia_rtp_hdr)); |
| |
| /* Send the RTP packet to the transport. */ |
| status = pjmedia_transport_send_rtp(stream->transport, |
| channel->out_pkt, |
| frame_out.size + |
| sizeof(pjmedia_rtp_hdr)); |
| } |
| |
| if (status != PJ_SUCCESS) { |
| PJ_PERROR(4,(stream->port.info.name.ptr, status, |
| "Error sending RTP/DTMF end packet")); |
| } |
| } |
| |
| /* Detach from transport |
| * MUST NOT hold stream mutex while detaching from transport, as |
| * it may cause deadlock. See ticket #460 for the details. |
| */ |
| if (stream->transport) { |
| pjmedia_transport_detach(stream->transport, stream); |
| stream->transport = NULL; |
| } |
| |
| /* This function may be called when stream is partly initialized. */ |
| if (stream->jb_mutex) |
| pj_mutex_lock(stream->jb_mutex); |
| |
| |
| /* Free codec. */ |
| |
| if (stream->codec) { |
| pjmedia_codec_close(stream->codec); |
| pjmedia_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec); |
| stream->codec = NULL; |
| } |
| |
| /* Free mutex */ |
| |
| if (stream->jb_mutex) { |
| pj_mutex_destroy(stream->jb_mutex); |
| stream->jb_mutex = NULL; |
| } |
| |
| /* Destroy jitter buffer */ |
| if (stream->jb) |
| pjmedia_jbuf_destroy(stream->jb); |
| |
| #if TRACE_JB |
| if (TRACE_JB_OPENED(stream)) { |
| pj_file_close(stream->trace_jb_fd); |
| stream->trace_jb_fd = TRACE_JB_INVALID_FD; |
| } |
| #endif |
| |
| if (stream->own_pool) { |
| pj_pool_t *pool = stream->own_pool; |
| stream->own_pool = NULL; |
| pj_pool_release(pool); |
| } |
| 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) |
| { |
| return stream->jb_last_frm; |
| } |
| |
| |
| /* |
| * Get the port interface. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_get_port( pjmedia_stream *stream, |
| pjmedia_port **p_port ) |
| { |
| *p_port = &stream->port; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Get the transport object |
| */ |
| PJ_DEF(pjmedia_transport*) pjmedia_stream_get_transport(pjmedia_stream *st) |
| { |
| return st->transport; |
| } |
| |
| |
| /* |
| * Start stream. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream) |
| { |
| |
| PJ_ASSERT_RETURN(stream && stream->enc && stream->dec, PJ_EINVALIDOP); |
| |
| if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) { |
| stream->enc->paused = 0; |
| //pjmedia_snd_stream_start(stream->enc->snd_stream); |
| PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream started")); |
| } else { |
| PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream paused")); |
| } |
| |
| if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) { |
| stream->dec->paused = 0; |
| //pjmedia_snd_stream_start(stream->dec->snd_stream); |
| PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream started")); |
| } else { |
| PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream paused")); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| 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) |
| { |
| PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL); |
| |
| pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat)); |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Reset the stream statistics in the middle of a stream session. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_reset_stat(pjmedia_stream *stream) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| pjmedia_rtcp_init_stat(&stream->rtcp.stat); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| #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) |
| { |
| PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL); |
| |
| if (stream->rtcp.xr_enabled) { |
| pj_memcpy(stat, &stream->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) |
| { |
| PJ_ASSERT_RETURN(stream && state, PJ_EINVAL); |
| return pjmedia_jbuf_get_state(stream->jb, state); |
| } |
| |
| /* |
| * Pause stream. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_pause( pjmedia_stream *stream, |
| pjmedia_dir dir) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { |
| stream->enc->paused = 1; |
| PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream paused")); |
| } |
| |
| if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { |
| stream->dec->paused = 1; |
| |
| /* Also reset jitter buffer */ |
| pj_mutex_lock( stream->jb_mutex ); |
| pjmedia_jbuf_reset(stream->jb); |
| pj_mutex_unlock( stream->jb_mutex ); |
| |
| PJ_LOG(4,(stream->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) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { |
| stream->enc->paused = 0; |
| PJ_LOG(4,(stream->port.info.name.ptr, "Encoder stream resumed")); |
| } |
| |
| if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { |
| stream->dec->paused = 0; |
| PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream resumed")); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* |
| * Dial DTMF |
| */ |
| PJ_DEF(pj_status_t) pjmedia_stream_dial_dtmf( pjmedia_stream *stream, |
| const pj_str_t *digit_char) |
| { |
| 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(stream->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; |
| } |
| 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].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(stream->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) |
| { |
| PJ_ASSERT_RETURN(stream && digits && size, PJ_EINVAL); |
| |
| pj_assert(sizeof(stream->rx_dtmf_buf[0]) == 0); |
| |
| /* By convention, we use jitter buffer's mutex to access DTMF |
| * digits resources. |
| */ |
| pj_mutex_lock(stream->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(stream->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) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| /* By convention, we use jitter buffer's mutex to access DTMF |
| * digits resources. |
| */ |
| pj_mutex_lock(stream->jb_mutex); |
| |
| stream->dtmf_cb = cb; |
| stream->dtmf_cb_user_data = user_data; |
| |
| pj_mutex_unlock(stream->jb_mutex); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* |
| * Send RTCP SDES. |
| */ |
| PJ_DEF(pj_status_t) |
| pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream ) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| return send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE); |
| } |
| |
| /* |
| * Send RTCP BYE. |
| */ |
| PJ_DEF(pj_status_t) |
| pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream ) |
| { |
| PJ_ASSERT_RETURN(stream, PJ_EINVAL); |
| |
| if (stream->enc && stream->transport) { |
| return send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_FALSE); |
| } |
| |
| return PJ_SUCCESS; |
| } |