Ticket #504: final installment to support stereo audio all the way in PJMEDIA. Please see tickiet #504 for more info

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1898 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2008-03-29 12:24:20 +00:00
parent a171e9edbe
commit 7d60d052eb
14 changed files with 1355 additions and 265 deletions

View File

@ -58,6 +58,7 @@ SOURCE session.c
SOURCE silencedet.c
SOURCE sound_port.c
SOURCE splitcomb.c
SOURCE stereo_port.c
SOURCE stream.c
SOURCE tonegen.c
SOURCE transport_ice.c

View File

@ -52,8 +52,8 @@ export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
null_port.o plc_common.o port.o splitcomb.o \
resample_resample.o resample_libsamplerate.o \
resample_port.o rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o \
session.o silencedet.o sound_port.o stream.o \
tonegen.o transport_ice.o transport_srtp.o \
session.o silencedet.o sound_port.o stereo_port.o \
stream.o tonegen.o transport_ice.o transport_srtp.o \
transport_udp.o \
wav_player.o wav_playlist.o wav_writer.o wave.o \
wsola.o $(SOUND_OBJS) $(NULLSOUND_OBJS)

View File

@ -237,6 +237,10 @@ SOURCE=..\src\pjmedia\splitcomb.c
# End Source File
# Begin Source File
SOURCE=..\src\pjmedia\stereo_port.c
# End Source File
# Begin Source File
SOURCE=..\src\pjmedia\stream.c
# End Source File
# Begin Source File
@ -401,6 +405,10 @@ SOURCE=..\include\pjmedia\splitcomb.h
# End Source File
# Begin Source File
SOURCE=..\include\pjmedia\stereo.h
# End Source File
# Begin Source File
SOURCE=..\include\pjmedia\stream.h
# End Source File
# Begin Source File

View File

@ -917,6 +917,10 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="..\src\pjmedia\stereo_port.c"
>
</File>
<File
RelativePath="..\src\pjmedia\stream.c"
>
@ -1208,6 +1212,10 @@
RelativePath="..\include\pjmedia\splitcomb.h"
>
</File>
<File
RelativePath="..\include\pjmedia\stereo.h"
>
</File>
<File
RelativePath="..\include\pjmedia\stream.h"
>

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,7 @@
#include <pjmedia/sound.h>
#include <pjmedia/sound_port.h>
#include <pjmedia/splitcomb.h>
#include <pjmedia/stereo.h>
#include <pjmedia/tonegen.h>
#include <pjmedia/transport.h>
#include <pjmedia/transport_ice.h>

View File

@ -24,6 +24,7 @@
#include <pjmedia/resample.h>
#include <pjmedia/silencedet.h>
#include <pjmedia/sound_port.h>
#include <pjmedia/stereo.h>
#include <pjmedia/stream.h>
#include <pj/array.h>
#include <pj/assert.h>
@ -112,11 +113,11 @@ struct conf_port
unsigned listener_cnt; /**< Number of listeners. */
SLOT_TYPE *listener_slots;/**< Array of listeners. */
unsigned transmitter_cnt;/**<Number of transmitters. */
pjmedia_silence_det *vad; /**< VAD for this port. */
/* Shortcut for port info. */
unsigned clock_rate; /**< Port's clock rate. */
unsigned samples_per_frame; /**< Port's samples per frame. */
unsigned channel_count; /**< Port's channel count. */
/* Calculated signal levels: */
unsigned tx_level; /**< Last tx level to this port. */
@ -285,26 +286,14 @@ static pj_status_t create_conf_port( pj_pool_t *pool,
conf_port->port = port;
conf_port->clock_rate = port->info.clock_rate;
conf_port->samples_per_frame = port->info.samples_per_frame;
conf_port->channel_count = port->info.channel_count;
} else {
conf_port->port = NULL;
conf_port->clock_rate = conf->clock_rate;
conf_port->samples_per_frame = conf->samples_per_frame;
conf_port->channel_count = conf->channel_count;
}
/* Create and init vad. */
status = pjmedia_silence_det_create( pool,
conf_port->clock_rate,
conf_port->samples_per_frame,
&conf_port->vad);
if (status != PJ_SUCCESS)
return status;
/* Set fixed */
pjmedia_silence_det_set_fixed(conf_port->vad, 2);
/* Set VAD name */
pjmedia_silence_det_set_name(conf_port->vad, conf_port->name.ptr);
/* If port's clock rate is different than conference's clock rate,
* create a resample sessions.
*/
@ -346,16 +335,48 @@ static pj_status_t create_conf_port( pj_pool_t *pool,
/*
* Initialize rx and tx buffer, only when port's samples per frame or
* port's clock rate is different then the conference bridge settings.
* port's clock rate or channel number is different then the conference
* bridge settings.
*/
if (conf_port->clock_rate != conf->clock_rate ||
conf_port->channel_count != conf->channel_count ||
conf_port->samples_per_frame != conf->samples_per_frame)
{
unsigned port_ptime, conf_ptime, buff_ptime;
port_ptime = conf_port->samples_per_frame / conf_port->channel_count *
1000 / conf_port->clock_rate;
conf_ptime = conf->samples_per_frame / conf->channel_count *
1000 / conf->clock_rate;
/* Calculate the size (in ptime) for the port buffer according to
* this formula:
* - if either ptime is an exact multiple of the other, then use
* the larger ptime (e.g. 20ms and 40ms, use 40ms).
* - if not, then the ptime is sum of both ptimes (e.g. 20ms
* and 30ms, use 50ms)
*/
if (port_ptime > conf_ptime) {
buff_ptime = conf_ptime * (port_ptime / conf_ptime);
if (port_ptime % conf_ptime)
buff_ptime += conf_ptime;
} else {
buff_ptime = port_ptime * (conf_ptime / port_ptime);
if (conf_ptime % port_ptime)
buff_ptime += port_ptime;
}
/* Create RX buffer. */
conf_port->rx_buf_cap = (unsigned)(conf_port->samples_per_frame +
conf->samples_per_frame *
conf_port->clock_rate * 1.0 /
conf->clock_rate);
//conf_port->rx_buf_cap = (unsigned)(conf_port->samples_per_frame +
// conf->samples_per_frame *
// conf_port->clock_rate * 1.0 /
// conf->clock_rate);
conf_port->rx_buf_cap = conf_port->clock_rate * buff_ptime / 1000;
if (conf_port->channel_count > conf->channel_count)
conf_port->rx_buf_cap *= conf_port->channel_count;
else
conf_port->rx_buf_cap *= conf->channel_count;
conf_port->rx_buf_count = 0;
conf_port->rx_buf = (pj_int16_t*)
pj_pool_alloc(pool, conf_port->rx_buf_cap *
@ -698,10 +719,13 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
if (!port_name)
port_name = &strm_port->info.name;
/* For this version of PJMEDIA, port MUST have the same number of
* PCM channels.
/* For this version of PJMEDIA, channel(s) number MUST be:
* - same between port & conference bridge.
* - monochannel on port or conference bridge.
*/
if (strm_port->info.channel_count != conf->channel_count) {
if (strm_port->info.channel_count != conf->channel_count &&
(strm_port->info.channel_count != 1 && conf->channel_count != 1))
{
pj_assert(!"Number of channels mismatch");
return PJMEDIA_ENCCHANNEL;
}
@ -766,10 +790,13 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf,
PJ_ASSERT_RETURN(conf && pool, PJ_EINVAL);
/* For this version of PJMEDIA, port MUST have the same number of
* PCM channels.
/* For this version of PJMEDIA, channel(s) number MUST be:
* - same between port & conference bridge.
* - monochannel on port or conference bridge.
*/
if (channel_count != conf->channel_count) {
if (channel_count != conf->channel_count &&
(channel_count != 1 && conf->channel_count != 1))
{
pj_assert(!"Number of channels mismatch");
return PJMEDIA_ENCCHANNEL;
}
@ -1140,7 +1167,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf,
info->listener_cnt = conf_port->listener_cnt;
info->listener_slots = conf_port->listener_slots;
info->clock_rate = conf_port->clock_rate;
info->channel_count = conf->channel_count;
info->channel_count = conf_port->channel_count;
info->samples_per_frame = conf_port->samples_per_frame;
info->bits_per_sample = conf->bits_per_sample;
info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL;
@ -1274,8 +1301,10 @@ static pj_status_t read_port( pjmedia_conf *conf,
(int)cport->name.slen, cport->name.ptr,
count));
/* If port's samples per frame and sampling rate matches conference
* bridge's settings, get the frame directly from the port.
/*
* If port's samples per frame and sampling rate and channel count
* matche conference bridge's settings, get the frame directly from
* the port.
*/
if (cport->rx_buf_cap == 0) {
pjmedia_frame f;
@ -1295,6 +1324,7 @@ static pj_status_t read_port( pjmedia_conf *conf,
return status;
} else {
unsigned samples_req;
/* Initialize frame type */
if (cport->rx_buf_count == 0) {
@ -1306,10 +1336,14 @@ static pj_status_t read_port( pjmedia_conf *conf,
/*
* If we don't have enough samples in rx_buf, read from the port
* first. Remember that rx_buf may be in different clock rate!
* first. Remember that rx_buf may be in different clock rate and
* channel count!
*/
while (cport->rx_buf_count < count * 1.0 *
cport->clock_rate / conf->clock_rate) {
samples_req = (unsigned) (count * 1.0 *
cport->clock_rate / conf->clock_rate);
while (cport->rx_buf_count < samples_req) {
pjmedia_frame f;
pj_status_t status;
@ -1336,7 +1370,26 @@ static pj_status_t read_port( pjmedia_conf *conf,
*type = PJMEDIA_FRAME_TYPE_AUDIO;
}
cport->rx_buf_count += cport->samples_per_frame;
/* Adjust channels */
if (cport->channel_count != conf->channel_count) {
if (cport->channel_count == 1) {
pjmedia_convert_channel_1ton(f.buf, f.buf,
conf->channel_count,
cport->samples_per_frame,
0);
cport->rx_buf_count += (cport->samples_per_frame *
conf->channel_count);
} else { /* conf->channel_count == 1 */
pjmedia_convert_channel_nto1(f.buf, f.buf,
cport->channel_count,
cport->samples_per_frame,
PJMEDIA_STEREO_MIX, 0);
cport->rx_buf_count += (cport->samples_per_frame /
cport->channel_count);
}
} else {
cport->rx_buf_count += cport->samples_per_frame;
}
TRACE_((THIS_FILE, " rx buffer size is now %d",
cport->rx_buf_count));
@ -1361,7 +1414,7 @@ static pj_status_t read_port( pjmedia_conf *conf,
conf->clock_rate);
cport->rx_buf_count -= src_count;
if (cport->rx_buf_count) {
pjmedia_copy_samples(cport->rx_buf, cport->rx_buf+src_count,
pjmedia_move_samples(cport->rx_buf, cport->rx_buf+src_count,
cport->rx_buf_count);
}
@ -1373,7 +1426,7 @@ static pj_status_t read_port( pjmedia_conf *conf,
pjmedia_copy_samples(frame, cport->rx_buf, count);
cport->rx_buf_count -= count;
if (cport->rx_buf_count) {
pjmedia_copy_samples(cport->rx_buf, cport->rx_buf+count,
pjmedia_move_samples(cport->rx_buf, cport->rx_buf+count,
cport->rx_buf_count);
}
}
@ -1395,6 +1448,7 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
pj_status_t status;
pj_int32_t adj_level;
pj_int32_t tx_level;
unsigned dst_count;
*frm_type = PJMEDIA_FRAME_TYPE_AUDIO;
@ -1412,7 +1466,8 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
/* Add sample counts to heart-beat samples */
cport->tx_heart_beat += conf->samples_per_frame * cport->clock_rate /
conf->clock_rate;
conf->clock_rate *
cport->channel_count / conf->channel_count;
/* Set frame timestamp */
frame.timestamp.u64 = timestamp->u64 * cport->clock_rate /
@ -1476,8 +1531,8 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
tx_level = 0;
for (j=0; j<conf->samples_per_frame; ++j) {
if (adj_level != NORMAL_LEVEL) {
if (adj_level != NORMAL_LEVEL) {
for (j=0; j<conf->samples_per_frame; ++j) {
pj_int32_t itemp = cport->mix_buf[j];
/* Adjust the level */
@ -1490,11 +1545,14 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
/* Put back in the buffer. */
buf[j] = (pj_int16_t) itemp;
} else {
buf[j] = (pj_int16_t) cport->mix_buf[j];
}
tx_level += (buf[j]>0? buf[j] : -buf[j]);
tx_level += (buf[j]>=0? buf[j] : -buf[j]);
}
} else {
for (j=0; j<conf->samples_per_frame; ++j) {
buf[j] = (pj_int16_t) cport->mix_buf[j];
tx_level += (buf[j]>=0? buf[j] : -buf[j]);
}
}
tx_level /= conf->samples_per_frame;
@ -1504,11 +1562,13 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
cport->tx_level = tx_level;
/* If port has the same clock_rate and samples_per_frame settings as
* the conference bridge, transmit the frame as is.
/* If port has the same clock_rate and samples_per_frame and
* number of channels as the conference bridge, transmit the
* frame as is.
*/
if (cport->clock_rate == conf->clock_rate &&
cport->samples_per_frame == conf->samples_per_frame)
cport->samples_per_frame == conf->samples_per_frame &&
cport->channel_count == conf->channel_count)
{
if (cport->port != NULL) {
pjmedia_frame frame;
@ -1532,25 +1592,39 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
/* If it has different clock_rate, must resample. */
if (cport->clock_rate != conf->clock_rate) {
unsigned dst_count;
pjmedia_resample_run( cport->tx_resample, buf,
cport->tx_buf + cport->tx_buf_count );
dst_count = (unsigned)(conf->samples_per_frame * 1.0 *
cport->clock_rate / conf->clock_rate);
cport->tx_buf_count += dst_count;
} else {
/* Same clock rate.
* Just copy the samples to tx_buffer.
*/
pjmedia_copy_samples( cport->tx_buf + cport->tx_buf_count,
buf, conf->samples_per_frame );
cport->tx_buf_count += conf->samples_per_frame;
dst_count = conf->samples_per_frame;
}
/* Adjust channels */
if (cport->channel_count != conf->channel_count) {
pj_int16_t *tx_buf = cport->tx_buf + cport->tx_buf_count;
if (conf->channel_count == 1) {
pjmedia_convert_channel_1ton(tx_buf, tx_buf,
cport->channel_count,
dst_count, 0);
dst_count *= cport->channel_count;
} else { /* cport->channel_count == 1 */
pjmedia_convert_channel_nto1(tx_buf, tx_buf,
conf->channel_count,
dst_count, PJMEDIA_STEREO_MIX, 0);
dst_count /= conf->channel_count;
}
}
cport->tx_buf_count += dst_count;
pj_assert(cport->tx_buf_count <= cport->tx_buf_cap);
/* Transmit while we have enough frame in the tx_buf. */
status = PJ_SUCCESS;
ts = 0;
@ -1589,7 +1663,7 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport,
cport->tx_buf_count -= cport->samples_per_frame;
if (cport->tx_buf_count) {
pjmedia_copy_samples(cport->tx_buf,
pjmedia_move_samples(cport->tx_buf,
cport->tx_buf + cport->samples_per_frame,
cport->tx_buf_count);
}
@ -1715,8 +1789,8 @@ static pj_status_t get_frame(pjmedia_port *this_port,
/* Adjust the RX level from this port
* and calculate the average level at the same time.
*/
for (j=0; j<conf->samples_per_frame; ++j) {
if (conf_port->rx_adj_level != NORMAL_LEVEL) {
if (conf_port->rx_adj_level != NORMAL_LEVEL) {
for (j=0; j<conf->samples_per_frame; ++j) {
/* For the level adjustment, we need to store the sample to
* a temporary 32bit integer value to avoid overflowing the
* 16bit sample storage.
@ -1736,10 +1810,13 @@ static pj_status_t get_frame(pjmedia_port *this_port,
else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
p_in[j] = (pj_int16_t) itemp;
level += (p_in[j]>=0? p_in[j] : -p_in[j]);
}
level += (p_in[j]>0? p_in[j] : -p_in[j]);
}
} else {
for (j=0; j<conf->samples_per_frame; ++j) {
level += (p_in[j]>=0? p_in[j] : -p_in[j]);
}
}
level /= conf->samples_per_frame;

View File

@ -68,6 +68,7 @@ SAMPLES = $(BINDIR)\confsample.exe \
$(BINDIR)\sndtest.exe \
$(BINDIR)\stateful_proxy.exe \
$(BINDIR)\stateless_proxy.exe \
$(BINDIR)\stereotest.exe \
$(BINDIR)\streamutil.exe \
$(BINDIR)\strerror.exe \
$(BINDIR)\tonegen.exe

View File

@ -31,6 +31,7 @@ SAMPLES := confsample \
sndtest \
stateful_proxy \
stateless_proxy \
stereotest \
streamutil \
strerror \
tonegen

View File

@ -178,6 +178,10 @@ SOURCE=..\src\samples\stateless_proxy.c
# End Source File
# Begin Source File
SOURCE=..\src\samples\stereotest.c
# End Source File
# Begin Source File
SOURCE=..\src\samples\streamutil.c
# End Source File
# Begin Source File

View File

@ -175,6 +175,7 @@ static void usage(void)
puts (" --dis-codec=name Disable codec (can be specified multiple times)");
puts (" --clock-rate=N Override conference bridge clock rate");
puts (" --snd-clock-rate=N Override sound device clock rate");
puts (" --stereo Audio device and conference bridge opened in stereo mode");
puts (" --null-audio Use NULL audio device");
puts (" --play-file=file Register WAV file in conference bridge.");
puts (" This can be specified multiple times.");
@ -389,8 +390,8 @@ static pj_status_t parse_args(int argc, char *argv[],
OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_USE_ICE,
OPT_USE_SRTP, OPT_SRTP_SECURE,
OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO,
OPT_USE_ICE, OPT_USE_SRTP, OPT_SRTP_SECURE,
OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
@ -413,6 +414,7 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "version", 0, 0, OPT_VERSION},
{ "clock-rate", 1, 0, OPT_CLOCK_RATE},
{ "snd-clock-rate", 1, 0, OPT_SND_CLOCK_RATE},
{ "stereo", 0, 0, OPT_STEREO},
{ "null-audio", 0, 0, OPT_NULL_AUDIO},
{ "local-port", 1, 0, OPT_LOCAL_PORT},
{ "ip-addr", 1, 0, OPT_IP_ADDR},
@ -582,6 +584,10 @@ static pj_status_t parse_args(int argc, char *argv[],
cfg->media_cfg.snd_clock_rate = lval;
break;
case OPT_STEREO:
cfg->media_cfg.channel_count = 2;
break;
case OPT_LOCAL_PORT: /* local-port */
lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
if (lval < 0 || lval > 65535) {
@ -1412,6 +1418,12 @@ static int write_settings(const struct app_config *config,
pj_strcat2(&cfg, line);
}
/* Stereo mode. */
if (config->media_cfg.channel_count == 2) {
pj_ansi_sprintf(line, "--stereo\n");
pj_strcat2(&cfg, line);
}
/* quality */
if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
pj_ansi_sprintf(line, "--quality %d\n",
@ -2314,10 +2326,11 @@ static void conf_list(void)
pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
pj_ansi_strcat(txlist, s);
}
printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
printf("Port #%02d[%2dKHz/%dms/%d] %20.*s transmitting to: %s\n",
info.slot_id,
info.clock_rate/1000,
info.samples_per_frame * 1000 / info.clock_rate,
info.channel_count,
(int)info.name.slen,
info.name.ptr,
txlist);

View File

@ -3748,6 +3748,12 @@ struct pjsua_media_config
*/
unsigned snd_clock_rate;
/**
* Channel count be applied when opening the sound device and
* conference bridge.
*/
unsigned channel_count;
/**
* Specify audio frame ptime. The value here will affect the
* samples per frame of both the sound device and the conference

View File

@ -163,6 +163,7 @@ PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg)
cfg->clock_rate = PJSUA_DEFAULT_CLOCK_RATE;
cfg->snd_clock_rate = 0;
cfg->channel_count = 1;
cfg->audio_frame_ptime = PJSUA_DEFAULT_AUDIO_FRAME_PTIME;
cfg->max_media_ports = PJSUA_MAX_CONF_PORTS;
cfg->has_ioqueue = PJ_TRUE;

View File

@ -169,7 +169,7 @@ pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
/* Save additional conference bridge parameters for future
* reference.
*/
pjsua_var.mconf_cfg.channel_count = 1;
pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
pjsua_var.mconf_cfg.bits_per_sample = 16;
pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
pjsua_var.mconf_cfg.channel_count *
@ -1834,8 +1834,10 @@ PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
playback_dev,
clock_rates[i], 1,
clock_rates[i]/fps,
clock_rates[i],
pjsua_var.media_cfg.channel_count,
clock_rates[i]/fps *
pjsua_var.media_cfg.channel_count,
16, 0, &pjsua_var.snd_port);
if (status == PJ_SUCCESS) {