Compare commits
10 Commits
Author | SHA1 | Date |
---|---|---|
Nanang Izzuddin | f923e2d7f5 | |
Nanang Izzuddin | 5785471d14 | |
Nanang Izzuddin | 3f502e8b42 | |
Nanang Izzuddin | 2e29528c22 | |
Nanang Izzuddin | 10a4714ff5 | |
Nanang Izzuddin | 5112c49cea | |
Nanang Izzuddin | 77e3f0693c | |
Nanang Izzuddin | 3101589616 | |
Nanang Izzuddin | 2082fc1626 | |
Benny Prijono | eac4062c0d |
|
@ -509,6 +509,10 @@
|
|||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\pjmedia\jbuf2.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\pjmedia\master_port.c"
|
||||
>
|
||||
|
@ -1140,6 +1144,10 @@
|
|||
RelativePath="..\include\pjmedia\jbuf.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjmedia\jbuf2.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjmedia\master_port.h"
|
||||
>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <pjmedia/endpoint.h>
|
||||
#include <pjmedia/g711.h>
|
||||
#include <pjmedia/jbuf.h>
|
||||
#include <pjmedia/jbuf2.h>
|
||||
#include <pjmedia/master_port.h>
|
||||
#include <pjmedia/mem_port.h>
|
||||
#include <pjmedia/null_port.h>
|
||||
|
|
|
@ -0,0 +1,302 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2003-2007 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
|
||||
*/
|
||||
|
||||
#ifndef __PJMEDIA_JBUF2_H__
|
||||
#define __PJMEDIA_JBUF2_H__
|
||||
|
||||
|
||||
/**
|
||||
* @file jbuf2.h
|
||||
* @brief Adaptive jitter buffer implementation.
|
||||
*/
|
||||
#include <pjmedia/types.h>
|
||||
|
||||
/**
|
||||
* @defgroup PJMED_JBUF2 Adaptive jitter buffer
|
||||
* @ingroup PJMEDIA_FRAME_OP
|
||||
* @{
|
||||
* This section describes PJMEDIA's implementation of de-jitter buffer.
|
||||
* The de-jitter buffer may be set to operate in adaptive mode or fixed
|
||||
* delay mode.
|
||||
*
|
||||
* The jitter buffer is also able to report the status of the current
|
||||
* frame (#pjmedia_jb_frame_type). This status is used for examply by
|
||||
* @ref PJMED_STRM to invoke the codec's @ref PJMED_PLC algorithm.
|
||||
*/
|
||||
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
|
||||
/**
|
||||
* Opaque declaration for jitter buffer.
|
||||
*/
|
||||
typedef struct pjmedia_jb2_t pjmedia_jb2_t;
|
||||
|
||||
/**
|
||||
* Types of jitter buffer phase.
|
||||
*/
|
||||
typedef enum pjmedia_jb2_phase
|
||||
{
|
||||
PJMEDIA_JB_PH_IDLE = 0, /**< No activity in PUT/GET or both */
|
||||
PJMEDIA_JB_PH_LEARNING = 1, /**< Learning */
|
||||
PJMEDIA_JB_PH_RUNNING = 2, /**< Running */
|
||||
} pjmedia_jb2_phase;
|
||||
|
||||
/**
|
||||
* Types of frame returned by the jitter buffer.
|
||||
*/
|
||||
typedef enum pjmedia_jb2_frame_type
|
||||
{
|
||||
PJMEDIA_JB_FT_NULL_FRAME = 0, /**< Frame not available */
|
||||
PJMEDIA_JB_FT_NORMAL_FRAME = 1, /**< Normal encoded frame */
|
||||
PJMEDIA_JB_FT_NORMAL_RAW_FRAME = 2, /**< Normal PCM frame */
|
||||
PJMEDIA_JB_FT_INTERP_RAW_FRAME = 3, /**< Interpolation frame */
|
||||
} pjmedia_jb2_frame_type ;
|
||||
|
||||
/**
|
||||
* Jitter buffer frame definition.
|
||||
*/
|
||||
typedef struct pjmedia_jb2_frame
|
||||
{
|
||||
pjmedia_jb2_frame_type type;
|
||||
void *buffer;
|
||||
pj_size_t size; /* in bytes */
|
||||
pj_uint8_t pt;
|
||||
pj_uint16_t seq;
|
||||
pj_uint32_t ts;
|
||||
} pjmedia_jb2_frame;
|
||||
|
||||
/*
|
||||
* Jitter buffer state.
|
||||
*/
|
||||
typedef struct pjmedia_jb2_state
|
||||
{
|
||||
pjmedia_jb2_phase phase;
|
||||
|
||||
/* in frames */
|
||||
pj_uint16_t level;
|
||||
pj_uint32_t frame_cnt;
|
||||
|
||||
/* in samples */
|
||||
pj_int32_t drift;
|
||||
pj_uint32_t drift_span;
|
||||
pj_uint32_t cur_size;
|
||||
pj_uint32_t opt_size;
|
||||
|
||||
} pjmedia_jb2_state;
|
||||
|
||||
|
||||
/*
|
||||
* Jitter buffer statistic.
|
||||
*/
|
||||
typedef struct pjmedia_jb2_stat
|
||||
{
|
||||
/* in frames */
|
||||
pj_uint32_t lost;
|
||||
pj_uint32_t late;
|
||||
pj_uint32_t ooo;
|
||||
pj_uint32_t out;
|
||||
pj_uint32_t in;
|
||||
|
||||
/* in ticks */
|
||||
pj_uint32_t full;
|
||||
pj_uint32_t empty;
|
||||
|
||||
/* in samples */
|
||||
pj_uint32_t max_size;
|
||||
pj_int32_t max_drift;
|
||||
pj_int32_t max_drift_span;
|
||||
pj_int32_t max_comp;
|
||||
|
||||
/* in frames */
|
||||
pj_uint16_t max_level;
|
||||
} pjmedia_jb2_stat;
|
||||
|
||||
/**
|
||||
* @see pjmedia_jb_frame_type.
|
||||
*/
|
||||
typedef enum pjmedia_jb2_frame_type pjmedia_jb2_frame_type;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes jitter buffer callback
|
||||
*/
|
||||
typedef struct pjmedia_jb2_cb
|
||||
{
|
||||
pj_status_t (*decode) (pjmedia_jb2_frame *frame, void *userdata);
|
||||
pj_status_t (*plc) (pjmedia_jb2_frame *frame, void *userdata);
|
||||
pj_status_t (*cng) (pjmedia_jb2_frame *frame, void *userdata);
|
||||
void *user_data;
|
||||
} pjmedia_jb2_cb;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes jitter buffer current status.
|
||||
*/
|
||||
typedef struct pjmedia_jb2_setting
|
||||
{
|
||||
/**
|
||||
* The maximum bytes number of each raw/PCM frame that will be kept in
|
||||
* the jitter buffer.
|
||||
*/
|
||||
pj_size_t frame_size;
|
||||
|
||||
/**
|
||||
* The number of samples for each frame in GET or PUT operation.
|
||||
* == timestamp interval between two consecutive frames.
|
||||
*/
|
||||
unsigned samples_per_frame;
|
||||
|
||||
/**
|
||||
* Maximum number of frames that can be kept in the jitter buffer. This
|
||||
* effectively means the maximum delay that may be introduced by this jitter
|
||||
* buffer. Set this to zero for adaptive jitter buffer and non-zero for
|
||||
* fixed jitter buffer.
|
||||
*/
|
||||
unsigned max_frames;
|
||||
|
||||
} pjmedia_jb2_setting;
|
||||
|
||||
|
||||
/**
|
||||
* Create an adaptive jitter buffer according to the specification. If
|
||||
* application wants to have a fixed jitter buffer, it may call
|
||||
* #pjmedia_jb2_set_fixed() after the jitter buffer is created.
|
||||
*
|
||||
* This function may allocate large chunk of memory to keep the frames in
|
||||
* the buffer.
|
||||
*
|
||||
* @param pool The pool to allocate memory.
|
||||
* @param name Name to identify the jitter buffer for logging
|
||||
* purpose.
|
||||
* @param p_jb Pointer to receive jitter buffer instance.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_create(pj_pool_t *pool,
|
||||
const pj_str_t *name,
|
||||
const pjmedia_jb2_setting *setting,
|
||||
const pjmedia_jb2_cb *cb,
|
||||
pjmedia_jb2_t **p_jb);
|
||||
|
||||
|
||||
/**
|
||||
* Destroy jitter buffer instance.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_destroy(pjmedia_jb2_t *jb);
|
||||
|
||||
|
||||
/**
|
||||
* Restart jitter. This function flushes all packets in the buffer and
|
||||
* reset the internal sequence number.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
*
|
||||
* @param setting New setting to be applied, it can be NULL
|
||||
* if user want to use the current setting.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_reset(pjmedia_jb2_t *jb);
|
||||
|
||||
|
||||
/**
|
||||
* Put a frame to the jitter buffer. If the frame can be accepted (based
|
||||
* on the sequence number), the jitter buffer will copy the frame and put
|
||||
* it in the appropriate position in the buffer.
|
||||
*
|
||||
* Application MUST manage it's own synchronization when multiple threads
|
||||
* are accessing the jitter buffer at the same time.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
* @param frame Pointer to frame buffer to be stored in the jitter
|
||||
* buffer.
|
||||
* @param size The frame size.
|
||||
* @param frame_seq The frame sequence number.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_put_frame(pjmedia_jb2_t *jb,
|
||||
const pjmedia_jb2_frame *frm);
|
||||
|
||||
/**
|
||||
* Get a frame from the jitter buffer. The jitter buffer will return the
|
||||
* oldest frame from it's buffer, when it is available.
|
||||
*
|
||||
* Application MUST manage it's own synchronization when multiple threads
|
||||
* are accessing the jitter buffer at the same time.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
* @param frame Buffer to receive the payload from the jitter buffer.
|
||||
* Application MUST make sure that the buffer has
|
||||
* appropriate size (i.e. not less than the frame size,
|
||||
* as specified when the jitter buffer was created).
|
||||
* The jitter buffer only copied a frame to this
|
||||
* buffer when the frame type returned by this function
|
||||
* is PJMEDIA_JB_NORMAL_FRAME.
|
||||
* @param p_frm_type Pointer to receive frame type. If jitter buffer is
|
||||
* currently empty or bufferring, the frame type will
|
||||
* be set to PJMEDIA_JB_ZERO_FRAME, and no frame will
|
||||
* be copied. If the jitter buffer detects that frame is
|
||||
* missing with current sequence number, the frame type
|
||||
* will be set to PJMEDIA_JB_MISSING_FRAME, and no
|
||||
* frame will be copied. If there is a frame, the jitter
|
||||
* buffer will copy the frame to the buffer, and frame
|
||||
* type will be set to PJMEDIA_JB_NORMAL_FRAME.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_get_frame(pjmedia_jb2_t *jb,
|
||||
pjmedia_jb2_frame *frm);
|
||||
|
||||
|
||||
/**
|
||||
* Get jitter buffer current state.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
* @param state Buffer to receive jitter buffer state.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_get_state(pjmedia_jb2_t *jb,
|
||||
pjmedia_jb2_state *state);
|
||||
|
||||
|
||||
/**
|
||||
* Get jitter buffer current statistic.
|
||||
*
|
||||
* @param jb The jitter buffer.
|
||||
* @param statistic Buffer to receive jitter buffer statistic.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjmedia_jb2_get_stat(pjmedia_jb2_t *jb,
|
||||
pjmedia_jb2_stat *stat);
|
||||
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* __PJMEDIA_JBUF2_H__ */
|
|
@ -768,17 +768,18 @@ static pj_status_t spx_codec_decode( pjmedia_codec *codec,
|
|||
unsigned output_buf_len,
|
||||
struct pjmedia_frame *output)
|
||||
{
|
||||
struct spx_private *spx;
|
||||
struct spx_private *spx = (struct spx_private*) codec->codec_data;
|
||||
unsigned pcm_size;
|
||||
|
||||
spx = (struct spx_private*) codec->codec_data;
|
||||
pcm_size = spx_factory.speex_param[spx->param_id].samples_per_frame << 1;
|
||||
|
||||
PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT);
|
||||
PJ_ASSERT_RETURN(output_buf_len >= pcm_size, PJMEDIA_CODEC_EPCMTOOSHORT);
|
||||
|
||||
if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) {
|
||||
pjmedia_zero_samples((pj_int16_t*)output->buf, 160);
|
||||
output->size = 320;
|
||||
output->size = pcm_size;
|
||||
output->timestamp.u64 = input->timestamp.u64;
|
||||
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
||||
pj_bzero(output->buf, output->size);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -789,7 +790,7 @@ static pj_status_t spx_codec_decode( pjmedia_codec *codec,
|
|||
speex_decode_int(spx->dec, &spx->dec_bits, (spx_int16_t*)output->buf);
|
||||
|
||||
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
|
||||
output->size = 320;
|
||||
output->size = pcm_size;
|
||||
output->timestamp.u64 = input->timestamp.u64;
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,7 @@
|
|||
#include <pjmedia/errno.h>
|
||||
#include <pjmedia/rtp.h>
|
||||
#include <pjmedia/rtcp.h>
|
||||
#include <pjmedia/jbuf.h>
|
||||
#include <pjmedia/jbuf2.h>
|
||||
#include <pj/array.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/ctype.h>
|
||||
|
@ -108,8 +108,7 @@ struct pjmedia_stream
|
|||
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 */
|
||||
pjmedia_jb2_t *jb; /**< Jitter buffer. */
|
||||
|
||||
pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */
|
||||
|
||||
|
@ -152,6 +151,89 @@ static void stream_perror(const char *sender, const char *title,
|
|||
PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status));
|
||||
}
|
||||
|
||||
static pj_status_t jb_decode(pjmedia_jb2_frame *frame, void *userdata)
|
||||
{
|
||||
pjmedia_stream *stream = (pjmedia_stream*) userdata;
|
||||
pj_status_t status = PJ_SUCCESS;
|
||||
pjmedia_frame frame_in, frame_out;
|
||||
|
||||
pj_assert(frame && userdata);
|
||||
|
||||
/* Decode */
|
||||
frame_in.buf = stream->dec->out_pkt;
|
||||
frame_in.size = frame->size;
|
||||
frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;
|
||||
pj_memcpy(frame_in.buf, frame->buffer, frame->size);
|
||||
|
||||
frame_out.buf = frame->buffer;
|
||||
frame_out.size = (stream->codec_param.info.clock_rate *
|
||||
stream->codec_param.info.frm_ptime / 1000)*
|
||||
(stream->codec_param.info.pcm_bits_per_sample/8);
|
||||
|
||||
status = stream->codec->op->decode( stream->codec, &frame_in,
|
||||
frame_out.size, &frame_out);
|
||||
|
||||
/* NOTE: frame_out.size is not valid for speex/16000 */
|
||||
frame->size = frame_out.size;
|
||||
if (status != PJ_SUCCESS) {
|
||||
LOGERR_((stream->port.info.name.ptr, "codec decode() error",
|
||||
status));
|
||||
|
||||
pj_bzero(frame->buffer, frame->size);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static pj_status_t jb_plc(pjmedia_jb2_frame *frame, void *userdata)
|
||||
{
|
||||
pjmedia_stream *stream = (pjmedia_stream*) userdata;
|
||||
pj_status_t status = PJ_SUCCESS;
|
||||
|
||||
pj_assert(frame && stream);
|
||||
|
||||
/* Activate PLC */
|
||||
if (stream->codec->op->recover &&
|
||||
stream->codec_param.setting.plc)
|
||||
{
|
||||
pjmedia_frame frame_out;
|
||||
|
||||
frame_out.buf = frame->buffer;
|
||||
frame_out.size = frame->size;
|
||||
status = (*stream->codec->op->recover)(stream->codec,
|
||||
frame_out.size,
|
||||
&frame_out);
|
||||
} else {
|
||||
status = PJ_EINVAL;
|
||||
}
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
/* Either PLC failed or PLC not supported/enabled */
|
||||
pj_bzero(frame->buffer, frame->size);
|
||||
PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!"));
|
||||
|
||||
} else {
|
||||
PJ_LOG(5,(stream->port.info.name.ptr,
|
||||
"Lost frame recovered"));
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static pj_status_t jb_cng(pjmedia_jb2_frame *frame, void *userdata)
|
||||
{
|
||||
pjmedia_stream *stream = (pjmedia_stream*) userdata;
|
||||
|
||||
pj_assert(frame && stream);
|
||||
|
||||
frame->size = (stream->codec_param.info.clock_rate *
|
||||
stream->codec_param.info.frm_ptime / 1000)*
|
||||
(stream->codec_param.info.pcm_bits_per_sample/8);
|
||||
pj_bzero(frame->buffer, frame->size);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* play_callback()
|
||||
|
@ -159,6 +241,7 @@ static void stream_perror(const char *sender, const char *title,
|
|||
* 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;
|
||||
|
@ -166,7 +249,7 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame)
|
|||
unsigned samples_count, samples_per_frame, samples_required;
|
||||
pj_int16_t *p_out_samp;
|
||||
pj_status_t status;
|
||||
|
||||
pjmedia_jb2_frame jb_frame;
|
||||
|
||||
/* Return no frame is channel is paused */
|
||||
if (channel->paused) {
|
||||
|
@ -191,157 +274,10 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame)
|
|||
for (samples_count=0; samples_count < samples_required;
|
||||
samples_count += samples_per_frame)
|
||||
{
|
||||
char frame_type;
|
||||
|
||||
/* Get frame from jitter buffer. */
|
||||
pjmedia_jbuf_get_frame(stream->jb, channel->out_pkt, &frame_type);
|
||||
|
||||
if (frame_type == PJMEDIA_JB_MISSING_FRAME) {
|
||||
|
||||
/* Activate PLC */
|
||||
if (stream->codec->op->recover &&
|
||||
stream->codec_param.setting.plc)
|
||||
{
|
||||
pjmedia_frame frame_out;
|
||||
|
||||
frame_out.buf = p_out_samp + samples_count;
|
||||
frame_out.size = frame->size - samples_count*2;
|
||||
status = (*stream->codec->op->recover)(stream->codec,
|
||||
frame_out.size,
|
||||
&frame_out);
|
||||
|
||||
} 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);
|
||||
PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!"));
|
||||
|
||||
} else {
|
||||
PJ_LOG(5,(stream->port.info.name.ptr,
|
||||
"Lost frame recovered"));
|
||||
}
|
||||
|
||||
} else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) {
|
||||
|
||||
/* Jitter buffer is empty. If this is the first "empty" state,
|
||||
* activate PLC to smoothen the fade-out, otherwise zero
|
||||
* the frame.
|
||||
*/
|
||||
if (frame_type != stream->jb_last_frm) {
|
||||
pjmedia_jb_state jb_state;
|
||||
|
||||
/* Activate PLC to smoothen the missing frame */
|
||||
if (stream->codec->op->recover &&
|
||||
stream->codec_param.setting.plc)
|
||||
{
|
||||
pjmedia_frame frame_out;
|
||||
|
||||
do {
|
||||
frame_out.buf = p_out_samp + samples_count;
|
||||
frame_out.size = frame->size - samples_count*2;
|
||||
status = (*stream->codec->op->recover)(stream->codec,
|
||||
frame_out.size,
|
||||
&frame_out);
|
||||
if (status != PJ_SUCCESS)
|
||||
break;
|
||||
samples_count += samples_per_frame;
|
||||
|
||||
} while (samples_count < samples_required);
|
||||
|
||||
}
|
||||
|
||||
/* Report the state of jitter buffer */
|
||||
pjmedia_jbuf_get_state(stream->jb, &jb_state);
|
||||
PJ_LOG(5,(stream->port.info.name.ptr,
|
||||
"Jitter buffer empty (prefetch=%d)",
|
||||
jb_state.prefetch));
|
||||
|
||||
}
|
||||
|
||||
if (samples_count < samples_required) {
|
||||
pjmedia_zero_samples(p_out_samp + samples_count,
|
||||
samples_required - samples_count);
|
||||
samples_count = samples_required;
|
||||
}
|
||||
|
||||
stream->jb_last_frm = frame_type;
|
||||
break;
|
||||
|
||||
} else if (frame_type != PJMEDIA_JB_NORMAL_FRAME) {
|
||||
|
||||
pjmedia_jb_state jb_state;
|
||||
|
||||
/* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */
|
||||
pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME);
|
||||
|
||||
/* Get the state of jitter buffer */
|
||||
pjmedia_jbuf_get_state(stream->jb, &jb_state);
|
||||
|
||||
/* Always activate PLC when it's available.. */
|
||||
if (stream->codec->op->recover &&
|
||||
stream->codec_param.setting.plc)
|
||||
{
|
||||
pjmedia_frame frame_out;
|
||||
|
||||
do {
|
||||
frame_out.buf = p_out_samp + samples_count;
|
||||
frame_out.size = frame->size - samples_count*2;
|
||||
status = (*stream->codec->op->recover)(stream->codec,
|
||||
frame_out.size,
|
||||
&frame_out);
|
||||
if (status != PJ_SUCCESS)
|
||||
break;
|
||||
samples_count += samples_per_frame;
|
||||
|
||||
} while (samples_count < samples_required);
|
||||
|
||||
if (stream->jb_last_frm != frame_type) {
|
||||
PJ_LOG(5,(stream->port.info.name.ptr,
|
||||
"Jitter buffer is bufferring with plc (prefetch=%d)",
|
||||
jb_state.prefetch));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (samples_count < samples_required) {
|
||||
pjmedia_zero_samples(p_out_samp + samples_count,
|
||||
samples_required - samples_count);
|
||||
samples_count = samples_required;
|
||||
PJ_LOG(5,(stream->port.info.name.ptr,
|
||||
"Jitter buffer is bufferring (prefetch=%d)..",
|
||||
jb_state.prefetch));
|
||||
}
|
||||
|
||||
stream->jb_last_frm = frame_type;
|
||||
break;
|
||||
|
||||
} else {
|
||||
/* Got "NORMAL" frame from jitter buffer */
|
||||
pjmedia_frame frame_in, frame_out;
|
||||
|
||||
/* Decode */
|
||||
frame_in.buf = channel->out_pkt;
|
||||
frame_in.size = stream->frame_size;
|
||||
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 = stream->codec->op->decode( stream->codec, &frame_in,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
stream->jb_last_frm = frame_type;
|
||||
jb_frame.buffer = p_out_samp + samples_count;
|
||||
jb_frame.size = frame->size - samples_count*2;
|
||||
status = pjmedia_jb2_get_frame(stream->jb, &jb_frame);
|
||||
}
|
||||
|
||||
|
||||
|
@ -999,7 +935,7 @@ static void on_rx_rtp( void *data,
|
|||
*/
|
||||
pj_mutex_lock( stream->jb_mutex );
|
||||
if (seq_st.status.flag.restart) {
|
||||
status = pjmedia_jbuf_reset(stream->jb);
|
||||
status = pjmedia_jb2_reset(stream->jb);
|
||||
PJ_LOG(4,(stream->port.info.name.ptr, "Jitter buffer reset"));
|
||||
|
||||
} else {
|
||||
|
@ -1013,6 +949,7 @@ static void on_rx_rtp( void *data,
|
|||
unsigned i, count = MAX;
|
||||
unsigned samples_per_frame;
|
||||
pjmedia_frame frames[MAX];
|
||||
pjmedia_jb2_frame jb_frame;
|
||||
|
||||
/* Get the timestamp of the first sample */
|
||||
ts.u64 = pj_ntohl(hdr->ts);
|
||||
|
@ -1038,17 +975,22 @@ static void on_rx_rtp( void *data,
|
|||
1000;
|
||||
|
||||
for (i=0; i<count; ++i) {
|
||||
unsigned ext_seq;
|
||||
|
||||
ext_seq = (unsigned)(frames[i].timestamp.u64 /
|
||||
samples_per_frame);
|
||||
pjmedia_jbuf_put_frame(stream->jb, frames[i].buf,
|
||||
frames[i].size, ext_seq);
|
||||
|
||||
jb_frame.buffer = frames[i].buf;
|
||||
jb_frame.pt = (pj_uint8_t)hdr->pt;
|
||||
jb_frame.seq = ntohs(hdr->seq);
|
||||
jb_frame.size = frames[i].size;
|
||||
jb_frame.ts = ntohl(hdr->ts) + samples_per_frame*i;
|
||||
jb_frame.type = PJMEDIA_JB_FT_NORMAL_FRAME;
|
||||
|
||||
status = pjmedia_jb2_put_frame(stream->jb, &jb_frame);
|
||||
if (status != PJ_SUCCESS) {
|
||||
LOGERR_((stream->port.info.name.ptr, "Jitter buffer put() error",
|
||||
status));
|
||||
}
|
||||
}
|
||||
}
|
||||
pj_mutex_unlock( stream->jb_mutex );
|
||||
|
||||
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",
|
||||
|
@ -1057,12 +999,6 @@ static void on_rx_rtp( void *data,
|
|||
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));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1160,8 +1096,9 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
|
|||
enum { M = 32 };
|
||||
pjmedia_stream *stream;
|
||||
pj_str_t name;
|
||||
unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
|
||||
pj_status_t status;
|
||||
pjmedia_jb2_setting jb_setting;
|
||||
pjmedia_jb2_cb jb_cb;
|
||||
|
||||
PJ_ASSERT_RETURN(pool && info && p_stream, PJ_EINVAL);
|
||||
|
||||
|
@ -1315,39 +1252,28 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
|
|||
|
||||
|
||||
/* Init jitter buffer parameters: */
|
||||
if (info->jb_max > 0)
|
||||
jb_max = info->jb_max;
|
||||
else
|
||||
jb_max = 360 / stream->codec_param.info.frm_ptime;
|
||||
|
||||
if (info->jb_min_pre >= 0)
|
||||
jb_min_pre = info->jb_min_pre;
|
||||
else
|
||||
jb_min_pre = 60 / stream->codec_param.info.frm_ptime;
|
||||
|
||||
if (info->jb_max_pre > 0)
|
||||
jb_max_pre = info->jb_max_pre;
|
||||
else
|
||||
jb_max_pre = 240 / stream->codec_param.info.frm_ptime;
|
||||
|
||||
if (info->jb_init >= 0)
|
||||
jb_init = info->jb_init;
|
||||
else
|
||||
jb_init = (jb_min_pre + jb_max_pre) / 2;
|
||||
pj_bzero(&jb_setting, sizeof(jb_setting));
|
||||
jb_setting.samples_per_frame = info->fmt.clock_rate *
|
||||
stream->codec_param.info.frm_ptime /
|
||||
1000;
|
||||
jb_setting.frame_size = jb_setting.samples_per_frame *
|
||||
stream->codec_param.info.pcm_bits_per_sample / 8;
|
||||
jb_setting.max_frames = 0;
|
||||
|
||||
pj_bzero(&jb_cb, sizeof(jb_cb));
|
||||
jb_cb.decode = &jb_decode;
|
||||
jb_cb.plc = &jb_plc;
|
||||
jb_cb.cng = NULL; //&jb_cng;
|
||||
jb_cb.user_data = stream;
|
||||
|
||||
/* 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);
|
||||
status = pjmedia_jb2_create(pool, &stream->port.info.name, &jb_setting,
|
||||
&jb_cb, &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,
|
||||
|
@ -1416,6 +1342,12 @@ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream )
|
|||
stream->codec = NULL;
|
||||
}
|
||||
|
||||
/* Destroy JB */
|
||||
if (stream->jb) {
|
||||
pjmedia_jb2_destroy(stream->jb);
|
||||
stream->jb = NULL;
|
||||
}
|
||||
|
||||
/* Free mutex */
|
||||
|
||||
if (stream->jb_mutex) {
|
||||
|
@ -1507,7 +1439,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_pause( pjmedia_stream *stream,
|
|||
|
||||
/* Also reset jitter buffer */
|
||||
pj_mutex_lock( stream->jb_mutex );
|
||||
pjmedia_jbuf_reset(stream->jb);
|
||||
pjmedia_jb2_reset(stream->jb);
|
||||
pj_mutex_unlock( stream->jb_mutex );
|
||||
|
||||
PJ_LOG(4,(stream->port.info.name.ptr, "Decoder stream paused"));
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2003-2007 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 <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <pjmedia/jbuf2.h>
|
||||
#include <pj/pool.h>
|
||||
|
||||
#pragma warning(disable: 4996)
|
||||
|
||||
#define REPORT
|
||||
#define PRINT_COMMENT
|
||||
|
||||
#define SAMPLES_PER_FRAME 160
|
||||
#define FRAME_SIZE (SAMPLES_PER_FRAME*2)
|
||||
|
||||
int jbuf2_main(pj_pool_factory *pf)
|
||||
{
|
||||
pjmedia_jb2_t *jb;
|
||||
pjmedia_jb2_setting jb_setting;
|
||||
pjmedia_jb2_cb jb_cb;
|
||||
pjmedia_jb2_frame jb_get_frame;
|
||||
pjmedia_jb2_frame jb_put_frame;
|
||||
pjmedia_jb2_state jb_last_state;
|
||||
pjmedia_jb2_state jb_state;
|
||||
pjmedia_jb2_stat jb_stat;
|
||||
FILE *input = fopen("..\\bin\\JB2TEST.DAT", "rt");
|
||||
char line[1024*128], *p;
|
||||
pj_pool_t *pool;
|
||||
pj_status_t status;
|
||||
char dummy[640];
|
||||
|
||||
pj_init();
|
||||
pool = pj_pool_create(pf, "JBPOOL", 256*16, 256*16, NULL);
|
||||
|
||||
jb_setting.max_frames = 0;
|
||||
jb_setting.samples_per_frame = SAMPLES_PER_FRAME;
|
||||
jb_setting.frame_size = FRAME_SIZE;
|
||||
|
||||
pj_bzero(&jb_cb, sizeof(jb_cb));
|
||||
pj_bzero(&jb_get_frame, sizeof(jb_get_frame));
|
||||
pj_bzero(&jb_put_frame, sizeof(jb_put_frame));
|
||||
|
||||
pjmedia_jb2_create(pool, NULL, &jb_setting, &jb_cb, &jb);
|
||||
|
||||
jb_put_frame.type = PJMEDIA_JB_FT_NORMAL_RAW_FRAME;
|
||||
jb_put_frame.size = jb_setting.frame_size;
|
||||
jb_put_frame.buffer = dummy;
|
||||
|
||||
jb_get_frame.buffer = dummy;
|
||||
|
||||
while ((p=fgets(line, sizeof(line), input)) != NULL) {
|
||||
int i;
|
||||
|
||||
while (*p && isspace(*p))
|
||||
++p;
|
||||
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
/* RTrim */
|
||||
i = strlen(p);
|
||||
while (--i >= 0 && p[i] == '\r' || p[i] == '\n' || p[i] == ' ')
|
||||
p[i] = '\0';
|
||||
|
||||
if (*p == '#') {
|
||||
#ifdef PRINT_COMMENT
|
||||
printf("%s\n", p+1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ignored line */
|
||||
if (!*p || *p == ';') {
|
||||
if (*p && *(p+1)==';')
|
||||
break;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
pjmedia_jb2_reset(jb);
|
||||
pj_bzero(&jb_last_state, sizeof(jb_last_state));
|
||||
pj_bzero(&jb_put_frame, sizeof(jb_put_frame));
|
||||
jb_put_frame.type = PJMEDIA_JB_FT_NORMAL_RAW_FRAME;
|
||||
jb_put_frame.size = jb_setting.frame_size;
|
||||
jb_put_frame.buffer = dummy;
|
||||
|
||||
while (*p) {
|
||||
int c;
|
||||
unsigned seq = 0;
|
||||
|
||||
c = *p++;
|
||||
if (isspace(c))
|
||||
continue;
|
||||
|
||||
if (c == '/') {
|
||||
char *end;
|
||||
|
||||
printf("/*");
|
||||
|
||||
do {
|
||||
putchar(*++p);
|
||||
} while (*p != '/');
|
||||
|
||||
putchar('\n');
|
||||
|
||||
c = *++p;
|
||||
end = p;
|
||||
|
||||
}
|
||||
|
||||
if (isspace(c))
|
||||
continue;
|
||||
|
||||
switch (toupper(c)) {
|
||||
case 'G': /* get */
|
||||
printf("G");
|
||||
jb_get_frame.size = jb_setting.frame_size;
|
||||
status = pjmedia_jb2_get_frame(jb, &jb_get_frame);
|
||||
break;
|
||||
case 'P': /* put */
|
||||
printf("P");
|
||||
status = pjmedia_jb2_put_frame(jb, &jb_put_frame);
|
||||
jb_put_frame.ts += jb_setting.samples_per_frame;
|
||||
jb_put_frame.seq += 1;
|
||||
break;
|
||||
case 'L': /* loss */
|
||||
printf("L");
|
||||
jb_put_frame.ts += jb_setting.samples_per_frame;
|
||||
jb_put_frame.seq += 1;
|
||||
break;
|
||||
default:
|
||||
printf("Unknown character '%c'\n", c);
|
||||
break;
|
||||
}
|
||||
pjmedia_jb2_get_state(jb, &jb_state);
|
||||
if (jb_state.drift != jb_last_state.drift
|
||||
|| jb_state.level != jb_last_state.level
|
||||
|| jb_state.opt_size != jb_last_state.opt_size
|
||||
)
|
||||
{
|
||||
printf("\n");
|
||||
printf("drift:%3d/%d ", jb_state.drift, jb_state.drift_span);
|
||||
printf("level:%3d ", jb_state.level);
|
||||
printf("cur_size:%5d ", jb_state.cur_size);
|
||||
printf("opt_size:%5d ", jb_state.opt_size);
|
||||
printf("\n");
|
||||
jb_last_state = jb_state;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef REPORT
|
||||
pjmedia_jb2_get_state(jb, &jb_state);
|
||||
printf("\nStop condition:\n");
|
||||
printf("drift:%3d/%d ", jb_state.drift, jb_state.drift_span);
|
||||
printf("level:%3d ", jb_state.level);
|
||||
printf("cur_size:%5d ", jb_state.cur_size);
|
||||
printf("opt_size:%5d ", jb_state.opt_size);
|
||||
printf("frame_cnt:%d ", jb_state.frame_cnt);
|
||||
printf("\n");
|
||||
|
||||
pjmedia_jb2_get_stat(jb, &jb_stat);
|
||||
printf("lost\t\t = %d\n", jb_stat.lost);
|
||||
printf("ooo\t\t = %d\n", jb_stat.ooo);
|
||||
printf("full\t\t = %d\n", jb_stat.full);
|
||||
printf("empty\t\t = %d\n", jb_stat.empty);
|
||||
printf("out\t\t = %d\n", jb_stat.out);
|
||||
printf("in\t\t = %d\n", jb_stat.in);
|
||||
printf("max_size\t = %d\n", jb_stat.max_size);
|
||||
printf("max_comp\t = %d\n", jb_stat.max_comp);
|
||||
printf("max_drift\t = %d/%d\n", jb_stat.max_drift,
|
||||
jb_stat.max_drift_span);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
if (input != stdin)
|
||||
fclose(input);
|
||||
|
||||
pj_pool_release(pool);
|
||||
return 0;
|
||||
}
|
|
@ -35,7 +35,7 @@ pj_pool_factory *mem;
|
|||
|
||||
void app_perror(pj_status_t status, const char *msg)
|
||||
{
|
||||
char errbuf[PJMEDIA_ERR_MSG_SIZE];
|
||||
char errbuf[PJ_ERR_MSG_SIZE];
|
||||
|
||||
pjmedia_strerror(status, errbuf, sizeof(errbuf));
|
||||
|
||||
|
@ -50,15 +50,17 @@ int test_main(void)
|
|||
pj_init();
|
||||
pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
|
||||
|
||||
pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
|
||||
pj_log_set_decor(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC);
|
||||
pj_log_set_level(4);
|
||||
|
||||
mem = &caching_pool.factory;
|
||||
|
||||
DO_TEST(sdp_neg_test());
|
||||
//DO_TEST(sdp_neg_test());
|
||||
//sdp_test (&caching_pool.factory);
|
||||
//rtp_test(&caching_pool.factory);
|
||||
//session_test (&caching_pool.factory);
|
||||
//jbuf_main(&caching_pool.factory);
|
||||
DO_TEST(jbuf2_main(&caching_pool.factory));
|
||||
|
||||
PJ_LOG(3,(THIS_FILE," "));
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ int session_test(void);
|
|||
int rtp_test(void);
|
||||
int sdp_test(void);
|
||||
int jbuf_main(void);
|
||||
int jbuf2_main(pj_pool_factory *pf);
|
||||
int sdp_neg_test(void);
|
||||
|
||||
extern pj_pool_factory *mem;
|
||||
|
|
|
@ -0,0 +1,451 @@
|
|||
/* $Id: sndtest.c 1663 2008-01-04 18:00:11Z bennylp $ */
|
||||
/*
|
||||
* Copyright (C) 2003-2007 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.h>
|
||||
#include <pjlib.h>
|
||||
#include <pjlib-util.h>
|
||||
#include <pjmedia/jbuf2.h>
|
||||
|
||||
#include <stdlib.h> /* atoi() */
|
||||
#include <stdio.h>
|
||||
|
||||
#define THIS_FILE "jbtest.c"
|
||||
|
||||
struct test_data {
|
||||
unsigned clock_rate;
|
||||
unsigned samples_per_frame;
|
||||
pjmedia_jb2_t *jb;
|
||||
pjmedia_port *read_port;
|
||||
pjmedia_port *write_port;
|
||||
pjmedia_resample *resampler;
|
||||
pj_lock_t *mutex;
|
||||
pj_int16_t seq;
|
||||
unsigned ts;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static const char *desc =
|
||||
THIS_FILE "\n"
|
||||
" \n"
|
||||
" PURPOSE: \n"
|
||||
" Test drift compensation of jbuf2 with real sound device.\n"
|
||||
" \n"
|
||||
" USAGE: \n"
|
||||
" sndtest --help \n"
|
||||
" sndtest [options] wavfile \n"
|
||||
" \n"
|
||||
" options: \n"
|
||||
" --id=ID -i Use device ID (default is -1) \n"
|
||||
" --rate=HZ -r Set test clock rate (default=8000)\n"
|
||||
" --drift=N -d Set clock drift (-2000 < N < 2000)\n"
|
||||
" --verbose -v Show verbose result \n"
|
||||
" --help -h Show this screen \n"
|
||||
;
|
||||
|
||||
static void app_perror(const char *title, pj_status_t status)
|
||||
{
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
printf( "%s: %s (err=%d)\n",
|
||||
title, errmsg, status);
|
||||
}
|
||||
|
||||
static void enum_devices(void)
|
||||
{
|
||||
int i, count;
|
||||
|
||||
count = pjmedia_snd_get_dev_count();
|
||||
if (count == 0) {
|
||||
PJ_LOG(3,(THIS_FILE, "No devices found"));
|
||||
return;
|
||||
}
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
|
||||
for (i=0; i<count; ++i) {
|
||||
const pjmedia_snd_dev_info *info;
|
||||
|
||||
info = pjmedia_snd_get_dev_info(i);
|
||||
pj_assert(info != NULL);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
|
||||
i, info->name, info->input_count, info->output_count));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback to be called for each clock ticks.
|
||||
*/
|
||||
static void clock_callback(const pj_timestamp *ts, void *user_data)
|
||||
{
|
||||
struct test_data *test_data = user_data;
|
||||
pjmedia_jb2_frame f;
|
||||
pjmedia_frame f_;
|
||||
pj_status_t status;
|
||||
char buf[5*1024];
|
||||
|
||||
PJ_UNUSED_ARG(ts);
|
||||
|
||||
PJ_ASSERT_ON_FAIL(test_data, return);
|
||||
|
||||
f_.buf = buf;
|
||||
f_.size = test_data->read_port->info.bytes_per_frame;
|
||||
status = pjmedia_port_get_frame(test_data->read_port, &f_);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Failed to get frame from file", status);
|
||||
return;
|
||||
}
|
||||
|
||||
test_data->seq++;
|
||||
test_data->ts += test_data->read_port->info.samples_per_frame;
|
||||
|
||||
f.buffer = f_.buf;
|
||||
f.pt = 0;
|
||||
f.seq = test_data->seq;
|
||||
f.ts = test_data->ts;
|
||||
f.size = f_.size;
|
||||
f.type = PJMEDIA_JB_FT_NORMAL_RAW_FRAME;
|
||||
|
||||
|
||||
pj_lock_acquire(test_data->mutex);
|
||||
pjmedia_jb2_put_frame(test_data->jb, &f);
|
||||
pj_lock_release(test_data->mutex);
|
||||
}
|
||||
|
||||
static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
|
||||
void *output, unsigned size)
|
||||
{
|
||||
struct test_data *test_data = user_data;
|
||||
pjmedia_jb2_frame f;
|
||||
pjmedia_frame f_;
|
||||
pj_status_t status;
|
||||
char buf[5*1024];
|
||||
|
||||
PJ_UNUSED_ARG(timestamp);
|
||||
PJ_ASSERT_RETURN(test_data, PJ_EINVAL);
|
||||
|
||||
f.buffer = test_data->resampler? buf : output;
|
||||
f.size = sizeof(buf);
|
||||
|
||||
pj_lock_acquire(test_data->mutex);
|
||||
pjmedia_jb2_get_frame(test_data->jb, &f);
|
||||
pj_lock_release(test_data->mutex);
|
||||
|
||||
if (test_data->resampler)
|
||||
pjmedia_resample_run(test_data->resampler, f.buffer, output);
|
||||
|
||||
f_.buf = output;
|
||||
f_.size = size;
|
||||
f_.type = PJMEDIA_FRAME_TYPE_AUDIO;
|
||||
f_.timestamp.u64 = f.ts;
|
||||
status = pjmedia_port_put_frame(test_data->write_port, &f_);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Failed to put frame to file", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static int perform_test(pj_pool_t *pool, const char *inputfile, int dev_id,
|
||||
unsigned clock_rate, int drift)
|
||||
{
|
||||
const int PTIME = 20;
|
||||
|
||||
pj_status_t status = PJ_SUCCESS;
|
||||
pjmedia_snd_stream *strm;
|
||||
pjmedia_clock *clock;
|
||||
struct test_data test_data;
|
||||
pjmedia_snd_stream_info si;
|
||||
unsigned samples_per_frame;
|
||||
|
||||
pjmedia_jb2_setting jb_setting;
|
||||
pjmedia_jb2_cb jb_cb;
|
||||
pjmedia_jb2_state jb_state;
|
||||
pjmedia_jb2_stat jb_stat;
|
||||
char s[8];
|
||||
|
||||
PJ_ASSERT_RETURN(pool && inputfile, PJ_EINVAL);
|
||||
|
||||
samples_per_frame = PTIME * clock_rate / 1000;
|
||||
/*
|
||||
* Init test parameters
|
||||
*/
|
||||
pj_bzero(&test_data, sizeof(test_data));
|
||||
test_data.clock_rate = clock_rate;
|
||||
test_data.samples_per_frame = samples_per_frame;
|
||||
|
||||
pj_lock_create_recursive_mutex(pool, "sndtest", &test_data.mutex);
|
||||
|
||||
/* Create WAV player port */
|
||||
status = pjmedia_wav_player_port_create( pool, /* memory pool */
|
||||
inputfile,/* file to play */
|
||||
PTIME, /* ptime. */
|
||||
0, /* flags */
|
||||
0, /* default buffer */
|
||||
&test_data.read_port
|
||||
/* returned port */
|
||||
);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to create WAV player", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (test_data.read_port->info.channel_count != 1) {
|
||||
app_perror("WAV file not mono.", status);
|
||||
return PJ_EINVAL;
|
||||
}
|
||||
|
||||
/* Init JB setting */
|
||||
pj_bzero(&jb_setting, sizeof(jb_setting));
|
||||
jb_setting.max_frames = 0;
|
||||
jb_setting.samples_per_frame = test_data.read_port->info.samples_per_frame;
|
||||
jb_setting.frame_size = test_data.read_port->info.bytes_per_frame;
|
||||
|
||||
/* Init JB callback */
|
||||
pj_bzero(&jb_cb, sizeof(jb_cb));
|
||||
|
||||
/* Create jitter buffer */
|
||||
status = pjmedia_jb2_create(pool, NULL, &jb_setting, &jb_cb, &test_data.jb);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to create jitter buffer", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Create the resample port when needed. */
|
||||
if (test_data.read_port->info.clock_rate != clock_rate) {
|
||||
status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
|
||||
test_data.read_port->info.clock_rate,
|
||||
clock_rate,
|
||||
jb_setting.samples_per_frame,
|
||||
&test_data.resampler);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to create resample port", status);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
test_data.resampler = NULL;
|
||||
}
|
||||
|
||||
/* Create WAV writer port */
|
||||
status = pjmedia_wav_writer_port_create( pool, "jbtestout.wav",
|
||||
clock_rate, 1,
|
||||
samples_per_frame,
|
||||
16, 0, 0,
|
||||
&test_data.write_port);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to create WAV writer", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Create media clock */
|
||||
status = pjmedia_clock_create(pool, clock_rate + drift, samples_per_frame,
|
||||
0, &clock_callback, &test_data, &clock);
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to create clock", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open device.
|
||||
*/
|
||||
status = pjmedia_snd_open_player( dev_id, clock_rate, 1,
|
||||
samples_per_frame, 16, &play_cb,
|
||||
&test_data, &strm);
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to open device for playing", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
pjmedia_snd_stream_get_info(strm, &si);
|
||||
if (si.play_id >= 0) {
|
||||
PJ_LOG(3,(THIS_FILE, "Testing playback device %s:",
|
||||
pjmedia_snd_get_dev_info(si.play_id)->name));
|
||||
PJ_LOG(4,(THIS_FILE, "Channel: %d clock: %dhz samples/frame: %d",
|
||||
si.channel_count, si.clock_rate, si.samples_per_frame));
|
||||
}
|
||||
|
||||
/*
|
||||
* Start the stream.
|
||||
*/
|
||||
status = pjmedia_snd_stream_start(strm);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to start capture stream", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start the clock.
|
||||
*/
|
||||
status = pjmedia_clock_start(clock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror("Unable to start clock", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Begin gather data */
|
||||
|
||||
puts("\nTest is running...");
|
||||
puts("\nPress <ENTER> to quit");
|
||||
fgets(s, sizeof(s), stdin);
|
||||
|
||||
/*
|
||||
* Print results.
|
||||
*/
|
||||
pjmedia_jb2_get_state(test_data.jb, &jb_state);
|
||||
printf("\nStop condition:\n");
|
||||
printf("drift:%3d/%d ", jb_state.drift, jb_state.drift_span);
|
||||
printf("level:%3d ", jb_state.level);
|
||||
printf("cur_size:%5d ", jb_state.cur_size);
|
||||
printf("opt_size:%5d ", jb_state.opt_size);
|
||||
printf("frame_cnt:%d ", jb_state.frame_cnt);
|
||||
printf("\n");
|
||||
|
||||
pjmedia_jb2_get_stat(test_data.jb, &jb_stat);
|
||||
printf("lost\t\t = %d\n", jb_stat.lost);
|
||||
printf("ooo\t\t = %d\n", jb_stat.ooo);
|
||||
printf("full\t\t = %d\n", jb_stat.full);
|
||||
printf("empty\t\t = %d\n", jb_stat.empty);
|
||||
printf("out\t\t = %d\n", jb_stat.out);
|
||||
printf("in\t\t = %d\n", jb_stat.in);
|
||||
printf("max_size\t = %d\n", jb_stat.max_size);
|
||||
printf("max_comp\t = %d\n", jb_stat.max_comp);
|
||||
printf("max_drift\t = %d/%d\n", jb_stat.max_drift,
|
||||
jb_stat.max_drift_span);
|
||||
|
||||
/* Close stream */
|
||||
pjmedia_snd_stream_close(strm);
|
||||
|
||||
/* Destroy clock */
|
||||
pjmedia_clock_destroy(clock);
|
||||
|
||||
/* Destroy resample */
|
||||
if (test_data.resampler)
|
||||
pjmedia_resample_destroy(test_data.resampler);
|
||||
|
||||
/* Destroy file port */
|
||||
pjmedia_port_destroy(test_data.read_port);
|
||||
pjmedia_port_destroy(test_data.write_port);
|
||||
|
||||
/* Destroy jitter buffer */
|
||||
pjmedia_jb2_destroy(test_data.jb);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
pj_caching_pool cp;
|
||||
pj_pool_t *pool;
|
||||
pjmedia_endpt *med_endpt;
|
||||
int id = -1, verbose = 0;
|
||||
int clock_rate = 8000;
|
||||
char *inputfile = NULL;
|
||||
int drift = 0;
|
||||
struct pj_getopt_option long_options[] = {
|
||||
{ "id", 1, 0, 'i' },
|
||||
{ "rate", 1, 0, 'r' },
|
||||
{ "drift", 1, 0, 'd' },
|
||||
{ "verbose", 0, 0, 'v' },
|
||||
{ "help", 0, 0, 'h' },
|
||||
{ NULL, 0, 0, 0 }
|
||||
};
|
||||
int c, option_index;
|
||||
|
||||
|
||||
pj_status_t status;
|
||||
|
||||
/* Init pjlib */
|
||||
status = pj_init();
|
||||
PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
|
||||
|
||||
/* Must create a pool factory before we can allocate any memory. */
|
||||
pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
|
||||
|
||||
/* Also create pool for misc purposes */
|
||||
pool = pj_pool_create(&cp.factory, "jbuf2test", 1000, 1000, NULL);
|
||||
|
||||
/*
|
||||
* Initialize media endpoint.
|
||||
* This will implicitly initialize PJMEDIA too.
|
||||
*/
|
||||
status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
|
||||
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
|
||||
|
||||
/* Print devices */
|
||||
enum_devices();
|
||||
|
||||
/* Parse options */
|
||||
pj_optind = 0;
|
||||
while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
|
||||
long_options, &option_index))!=-1)
|
||||
{
|
||||
switch (c) {
|
||||
case 'i':
|
||||
id = atoi(pj_optarg);
|
||||
break;
|
||||
case 'r':
|
||||
clock_rate = atoi(pj_optarg);
|
||||
break;
|
||||
case 'd':
|
||||
drift = atoi(pj_optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
case 'h':
|
||||
puts(desc);
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
printf("Error: invalid options %s\n", argv[pj_optind-1]);
|
||||
puts(desc);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - pj_optind) != 1) {
|
||||
printf("Error: invalid options\n");
|
||||
puts(desc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inputfile = argv[argc - 1];
|
||||
|
||||
if (!verbose)
|
||||
pj_log_set_level(3);
|
||||
else
|
||||
pj_log_set_level(4);
|
||||
|
||||
status = perform_test(pool, inputfile, id, clock_rate, drift);
|
||||
|
||||
pjmedia_endpt_destroy(med_endpt);
|
||||
pj_pool_release(pool);
|
||||
pj_caching_pool_destroy(&cp);
|
||||
pj_shutdown();
|
||||
|
||||
return status == PJ_SUCCESS ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue