TS
Tobias Schneider
Mon, Jul 23, 2018 2:48 PM
If stream input is not connected to any transmitter, it will send
silence frames through RTP to keep NAT active. Unfortunately this leads
to a jitterbuffer filled to capacity if early media is used but audio
stream is not connected to any sink (e.g. if only video stream is being
connected in early media) because audio frames are not fetched from
jitterbuffer. If users than connects audio stream with audio device
after call is accepted, they will encounter several discarded frames and
therefore frame losts, even though the network stream is fine and does
not have any lost frames itself, just because jitterbuffer algorithm now
tries to reduce jitterbuffer size.
I deeply analyzed this using G722 codec with PJProject 2.4.5 but the
problem seems to be still existent in current version of PJProject. I
made two patches to resolve this issue but I am not sure, which one
should be preferred, so I want you to check whether one of those patches
is correct and can be used safely. I will send those patches as several
posting.
If stream input is not connected to any transmitter, it will send
silence frames through RTP to keep NAT active. Unfortunately this leads
to a jitterbuffer filled to capacity if early media is used but audio
stream is not connected to any sink (e.g. if only video stream is being
connected in early media) because audio frames are not fetched from
jitterbuffer. If users than connects audio stream with audio device
after call is accepted, they will encounter several discarded frames and
therefore frame losts, even though the network stream is fine and does
not have any lost frames itself, just because jitterbuffer algorithm now
tries to reduce jitterbuffer size.
I deeply analyzed this using G722 codec with PJProject 2.4.5 but the
problem seems to be still existent in current version of PJProject. I
made two patches to resolve this issue but I am not sure, which one
should be preferred, so I want you to check whether one of those patches
is correct and can be used safely. I will send those patches as several
posting.
TS
Tobias Schneider
Mon, Jul 23, 2018 2:57 PM
This is variant 1 to fix that issue by directly calling
pjmedia_port_get_frame in conference.c (write_port) after calling
pjmedia_portput_frame to ensure that there are "snychronous" calls of
put- and get-frame.
pjmedia/src/pjmedia/conference.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/pjmedia/src/pjmedia/conference.c
b/pjmedia/src/pjmedia/conference.c
index 2ef65380..42e741d6 100644
--- a/pjmedia/src/pjmedia/conference.c
+++ b/pjmedia/src/pjmedia/conference.c
@@ -214,6 +214,11 @@ struct conf_port
* Burst and drift are handled by delay buffer.
*/
pjmedia_delay_buf delay_buf;
+
+ / RX null buffer is a temporary buffer to be used if port is muted or
+ * nobody is transmitting to this port, transmit NULL frame.
+ */
+ pj_int16_t *rx_null_buf; /**< Rx null
buffer. /
};
@@ -396,6 +401,10 @@ static pj_status_t create_conf_port( pj_pool_t pool,
PJ_ASSERT_RETURN(conf_port->tx_buf, PJ_ENOMEM);
}
+ / create null frame to store silence frames /
+ conf_port->rx_null_buf = (pj_int16_t)
+ pj_pool_alloc(pool,
conf_port->samples_per_frame *
+
sizeof(conf_port->rx_null_buf[0]));
/ Create mix buffer. /
conf_port->mix_buf = (pj_int32_t)
@@ -1587,6 +1596,15 @@ static pj_status_t write_port(pjmedia_conf *conf,
struct conf_port cport,
cport->tx_heart_beat -= cport->samples_per_frame;
frame.timestamp.u64 += cport->samples_per_frame;
+
+ / directly call get_frame to have synchron access and avoid
+ JB at full capacity */
+ pjmedia_frame null_frame;
+ pj_bzero(&null_frame, sizeof(null_frame));
+ null_frame.buf = cport->rx_null_buf;
+ null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ null_frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
+ pjmedia_port_get_frame(cport->port, &null_frame);
}
}
2.13.7
This is variant 1 to fix that issue by directly calling
pjmedia_port_get_frame in conference.c (write_port) after calling
pjmedia_portput_frame to ensure that there are "snychronous" calls of
put- and get-frame.
---
pjmedia/src/pjmedia/conference.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/pjmedia/src/pjmedia/conference.c
b/pjmedia/src/pjmedia/conference.c
index 2ef65380..42e741d6 100644
--- a/pjmedia/src/pjmedia/conference.c
+++ b/pjmedia/src/pjmedia/conference.c
@@ -214,6 +214,11 @@ struct conf_port
* Burst and drift are handled by delay buffer.
*/
pjmedia_delay_buf *delay_buf;
+
+ /* RX null buffer is a temporary buffer to be used if port is muted or
+ * nobody is transmitting to this port, transmit NULL frame.
+ */
+ pj_int16_t *rx_null_buf; /**< Rx null
buffer. */
};
@@ -396,6 +401,10 @@ static pj_status_t create_conf_port( pj_pool_t *pool,
PJ_ASSERT_RETURN(conf_port->tx_buf, PJ_ENOMEM);
}
+ /* create null frame to store silence frames */
+ conf_port->rx_null_buf = (pj_int16_t*)
+ pj_pool_alloc(pool,
conf_port->samples_per_frame *
+
sizeof(conf_port->rx_null_buf[0]));
/* Create mix buffer. */
conf_port->mix_buf = (pj_int32_t*)
@@ -1587,6 +1596,15 @@ static pj_status_t write_port(pjmedia_conf *conf,
struct conf_port *cport,
cport->tx_heart_beat -= cport->samples_per_frame;
frame.timestamp.u64 += cport->samples_per_frame;
+
+ /* directly call get_frame to have synchron access and avoid
+ JB at full capacity */
+ pjmedia_frame null_frame;
+ pj_bzero(&null_frame, sizeof(null_frame));
+ null_frame.buf = cport->rx_null_buf;
+ null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ null_frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE;
+ pjmedia_port_get_frame(cport->port, &null_frame);
}
}
--
2.13.7
TS
Tobias Schneider
Mon, Jul 23, 2018 3:01 PM
This is variant 2 to fix that issue by directly calling
pjmedia_port_get_frame in stream.c after packet was sent via RTP to
ensure that there are "snychronous" calls of put- and get-frame.
pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index adcaa628..1f2d0457 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
/* Zero audio frame samples /
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
+/ null buffer to hold result of get_frame after transmit of NULL frame.
-
- Make it big enough to hold any kind of media port size connected to conf
-
- bridge, e.g. up to 30ms ptime and 16kHz samplerate
- */
+static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
/*
* Print error.
/
@@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port port,
(const void)&rtphdr,
&rtphdrlen);
+ /* directly call get_frame of stream to avoid filling of JB during
+ * silence period /
+ pjmedia_frame null_frame;
+ pj_bzero(&null_frame, sizeof(null_frame));
+ null_frame.buf = null_buffer;
+ null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ null_frame.size = stream->enc_samples_per_pkt * 2;
+ pjmedia_port_get_frame(&stream->port, &null_frame);
/ Encode audio frame */
} else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
2.13.7
This is variant 2 to fix that issue by directly calling
pjmedia_port_get_frame in stream.c after packet was sent via RTP to
ensure that there are "snychronous" calls of put- and get-frame.
---
pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index adcaa628..1f2d0457 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
/* Zero audio frame samples */
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
+/* null buffer to hold result of get_frame after transmit of NULL frame.
+ * Make it big enough to hold any kind of media port size connected to conf
+ * bridge, e.g. up to 30ms ptime and 16kHz samplerate
+ */
+static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
+
/*
* Print error.
*/
@@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port *port,
(const void**)&rtphdr,
&rtphdrlen);
+ /* directly call get_frame of stream to avoid filling of JB during
+ * silence period */
+ pjmedia_frame null_frame;
+ pj_bzero(&null_frame, sizeof(null_frame));
+ null_frame.buf = null_buffer;
+ null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ null_frame.size = stream->enc_samples_per_pkt * 2;
+ pjmedia_port_get_frame(&stream->port, &null_frame);
/* Encode audio frame */
} else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
--
2.13.7
M
Ming
Tue, Jul 24, 2018 8:12 AM
Hi Tobias,
There seems to be an easier workaround: i.e. to connect the audio to a
sink during the early media instead. It can use null audio device or
mute it if it doesn't want to play it at first.
The patches seem to assume that: - the decoding is not connected
during the silence frames, or - when we are sending silence frames,
the remote are sending ones as well, which is not always true.
Regards,
Ming
On Mon, Jul 23, 2018 at 11:01 PM, Tobias Schneider
tobias.schneider@voiceinterconnect.de wrote:
This is variant 2 to fix that issue by directly calling
pjmedia_port_get_frame in stream.c after packet was sent via RTP to
ensure that there are "snychronous" calls of put- and get-frame.
pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index adcaa628..1f2d0457 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
/* Zero audio frame samples */
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
+/* null buffer to hold result of get_frame after transmit of NULL frame.
-
- Make it big enough to hold any kind of media port size connected to conf
-
- bridge, e.g. up to 30ms ptime and 16kHz samplerate
- */
+static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
/*
- Print error.
/
@@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port port,
(const void)&rtphdr,
&rtphdrlen);
-
/* directly call get_frame of stream to avoid filling of JB during
-
* silence period */
-
pjmedia_frame null_frame;
-
pj_bzero(&null_frame, sizeof(null_frame));
-
null_frame.buf = null_buffer;
-
null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-
null_frame.size = stream->enc_samples_per_pkt * 2;
-
pjmedia_port_get_frame(&stream->port, &null_frame);
/* Encode audio frame */
} else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
--
2.13.7
Visit our blog: http://blog.pjsip.org
pjsip mailing list
pjsip@lists.pjsip.org
http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org
Hi Tobias,
There seems to be an easier workaround: i.e. to connect the audio to a
sink during the early media instead. It can use null audio device or
mute it if it doesn't want to play it at first.
The patches seem to assume that: - the decoding is not connected
during the silence frames, or - when we are sending silence frames,
the remote are sending ones as well, which is not always true.
Regards,
Ming
On Mon, Jul 23, 2018 at 11:01 PM, Tobias Schneider
<tobias.schneider@voiceinterconnect.de> wrote:
> This is variant 2 to fix that issue by directly calling
> pjmedia_port_get_frame in stream.c after packet was sent via RTP to
> ensure that there are "snychronous" calls of put- and get-frame.
>
> ---
> pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
> index adcaa628..1f2d0457 100644
> --- a/pjmedia/src/pjmedia/stream.c
> +++ b/pjmedia/src/pjmedia/stream.c
> @@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
> /* Zero audio frame samples */
> static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
>
> +/* null buffer to hold result of get_frame after transmit of NULL frame.
> + * Make it big enough to hold any kind of media port size connected to conf
> + * bridge, e.g. up to 30ms ptime and 16kHz samplerate
> + */
> +static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
> +
> /*
> * Print error.
> */
> @@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port *port,
> (const void**)&rtphdr,
> &rtphdrlen);
>
> + /* directly call get_frame of stream to avoid filling of JB during
> + * silence period */
> + pjmedia_frame null_frame;
> + pj_bzero(&null_frame, sizeof(null_frame));
> + null_frame.buf = null_buffer;
> + null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
> + null_frame.size = stream->enc_samples_per_pkt * 2;
> + pjmedia_port_get_frame(&stream->port, &null_frame);
>
> /* Encode audio frame */
> } else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
> --
> 2.13.7
>
>
>
> _______________________________________________
> Visit our blog: http://blog.pjsip.org
>
> pjsip mailing list
> pjsip@lists.pjsip.org
> http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org
TS
Tobias Schneider
Tue, Aug 21, 2018 6:45 AM
Hello Ming,
for me it is okay to have a workaround like connecting audio stream to
null audio device, even though the code change for this is much bigger
than some fixes in PJMedia. I also doubt that it is desired behaviour of
PJMedia, that the jitterbuffer gets filled to maximum capacity if user
does not connect any sink to a running audio stream, but thats your
decision of course.
Anyway, as my patches might not cover all corner cases, it is safer for
now to use the above mentioned workaround.
Regards,
Tobias
On 24.07.2018 10:12, Ming wrote:
Hi Tobias,
There seems to be an easier workaround: i.e. to connect the audio to a
sink during the early media instead. It can use null audio device or
mute it if it doesn't want to play it at first.
The patches seem to assume that: - the decoding is not connected
during the silence frames, or - when we are sending silence frames,
the remote are sending ones as well, which is not always true.
Regards,
Ming
On Mon, Jul 23, 2018 at 11:01 PM, Tobias Schneider
tobias.schneider@voiceinterconnect.de wrote:
This is variant 2 to fix that issue by directly calling
pjmedia_port_get_frame in stream.c after packet was sent via RTP to
ensure that there are "snychronous" calls of put- and get-frame.
pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
index adcaa628..1f2d0457 100644
--- a/pjmedia/src/pjmedia/stream.c
+++ b/pjmedia/src/pjmedia/stream.c
@@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
/* Zero audio frame samples */
static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
+/* null buffer to hold result of get_frame after transmit of NULL frame.
-
- Make it big enough to hold any kind of media port size connected to conf
-
- bridge, e.g. up to 30ms ptime and 16kHz samplerate
- */
+static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
/*
- Print error.
/
@@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port port,
(const void)&rtphdr,
&rtphdrlen);
-
/* directly call get_frame of stream to avoid filling of JB during
-
* silence period */
-
pjmedia_frame null_frame;
-
pj_bzero(&null_frame, sizeof(null_frame));
-
null_frame.buf = null_buffer;
-
null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
-
null_frame.size = stream->enc_samples_per_pkt * 2;
-
pjmedia_port_get_frame(&stream->port, &null_frame);
/* Encode audio frame */
} else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
--
2.13.7
Visit our blog: http://blog.pjsip.org
pjsip mailing list
pjsip@lists.pjsip.org
http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org
Hello Ming,
for me it is okay to have a workaround like connecting audio stream to
null audio device, even though the code change for this is much bigger
than some fixes in PJMedia. I also doubt that it is desired behaviour of
PJMedia, that the jitterbuffer gets filled to maximum capacity if user
does not connect any sink to a running audio stream, but thats your
decision of course.
Anyway, as my patches might not cover all corner cases, it is safer for
now to use the above mentioned workaround.
Regards,
Tobias
On 24.07.2018 10:12, Ming wrote:
> Hi Tobias,
>
> There seems to be an easier workaround: i.e. to connect the audio to a
> sink during the early media instead. It can use null audio device or
> mute it if it doesn't want to play it at first.
>
> The patches seem to assume that: - the decoding is not connected
> during the silence frames, or - when we are sending silence frames,
> the remote are sending ones as well, which is not always true.
>
> Regards,
> Ming
>
> On Mon, Jul 23, 2018 at 11:01 PM, Tobias Schneider
> <tobias.schneider@voiceinterconnect.de> wrote:
>> This is variant 2 to fix that issue by directly calling
>> pjmedia_port_get_frame in stream.c after packet was sent via RTP to
>> ensure that there are "snychronous" calls of put- and get-frame.
>>
>> ---
>> pjmedia/src/pjmedia/stream.c | 14 ++++++++++++++
>> 1 file changed, 14 insertions(+)
>>
>> diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c
>> index adcaa628..1f2d0457 100644
>> --- a/pjmedia/src/pjmedia/stream.c
>> +++ b/pjmedia/src/pjmedia/stream.c
>> @@ -240,6 +240,12 @@ static const char digitmap[17] = { '0', '1', '2', '3',
>> /* Zero audio frame samples */
>> static pj_int16_t zero_frame[2 * 30 * 16000 / 1000];
>>
>> +/* null buffer to hold result of get_frame after transmit of NULL frame.
>> + * Make it big enough to hold any kind of media port size connected to conf
>> + * bridge, e.g. up to 30ms ptime and 16kHz samplerate
>> + */
>> +static pj_int16_t null_buffer[2 * 30 * 16000 / 1000];
>> +
>> /*
>> * Print error.
>> */
>> @@ -1318,6 +1324,14 @@ static pj_status_t put_frame_imp( pjmedia_port *port,
>> (const void**)&rtphdr,
>> &rtphdrlen);
>>
>> + /* directly call get_frame of stream to avoid filling of JB during
>> + * silence period */
>> + pjmedia_frame null_frame;
>> + pj_bzero(&null_frame, sizeof(null_frame));
>> + null_frame.buf = null_buffer;
>> + null_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
>> + null_frame.size = stream->enc_samples_per_pkt * 2;
>> + pjmedia_port_get_frame(&stream->port, &null_frame);
>>
>> /* Encode audio frame */
>> } else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
>> --
>> 2.13.7
>>
>>
>>
>> _______________________________________________
>> Visit our blog: http://blog.pjsip.org
>>
>> pjsip mailing list
>> pjsip@lists.pjsip.org
>> http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org
--
Dipl.-Ing. (FH) Tobias Schneider, M. Sc.
voice INTER connect GmbH
Ammonstraße 35, 01067 Dresden, Germany
phone: +49 351 407526-64
<https://telefonanlage.vic.site/modules/dial.php?Predial=0&Extension=364&Number=035140752664>
fax: +49 351 407526-55
<https://telefonanlage.vic.site/modules/dial.php?Predial=0&Extension=364&Number=035140752655>
eMail: tobias.schneider@voiceinterconnect.de
visit: www.voiceinterconnect.de
Geschäftsführung: Eingetragen im Handelsregister:
Dr.-Ing. Diane Hirschfeld, Amtsgericht Dresden HRB 19466
Ludwig Linkenheil
... smart signal processing for electronic devices