Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/switch_core_media.c
Original file line number Diff line number Diff line change
Expand Up @@ -3710,11 +3710,22 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_set_codec(switch_core_session_
switch_core_session_set_write_impl(session, a_engine->write_codec.implementation);

if (switch_rtp_ready(a_engine->rtp_session)) {
uint32_t samples_per_interval = a_engine->read_impl.samples_per_packet;

switch_assert(a_engine->read_codec.implementation);

/* RFC 7587: Opus advances its RTP timestamp at a fixed 48 kHz clock regardless of the
negotiated payload rate, so the interval must come from samples_per_second, not
samples_per_packet. switch_rtp_new() applies this at init; mirror it here so the
post-negotiation change does not reset the Opus clock to the payload rate. */
if (!strcasecmp("opus", a_engine->read_impl.iananame)) {
samples_per_interval = a_engine->read_impl.samples_per_second *
(a_engine->read_impl.microseconds_per_packet / 1000) / 1000;
}

if (switch_rtp_change_interval(a_engine->rtp_session,
a_engine->read_impl.microseconds_per_packet,
a_engine->read_impl.samples_per_packet) != SWITCH_STATUS_SUCCESS) {
samples_per_interval) != SWITCH_STATUS_SUCCESS) {
switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
switch_goto_status(SWITCH_STATUS_FALSE, end);
}
Expand Down Expand Up @@ -5521,11 +5532,18 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
}
}

if (fmtp_remote_codec_rate) {
remote_codec_rate = fmtp_remote_codec_rate;
}
for (i = 0; i < smh->mparams->num_codecs && i < total_codecs; i++) {
const switch_codec_implementation_t *imp = codec_array[i];
uint32_t bit_rate = imp->bits_per_second;
uint32_t codec_rate = imp->samples_per_second;

if (!strcasecmp(map->rm_encoding, "opus")) {
codec_rate = imp->actual_samples_per_second;
}

if (imp->codec_type != SWITCH_CODEC_TYPE_AUDIO) {
continue;
}
Expand All @@ -5538,10 +5556,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
} else {
match = (!strcasecmp(rm_encoding, imp->iananame) &&
((map->rm_pt < 96 && imp->ianacode < 96) || (map->rm_pt > 95 && imp->ianacode > 95)) &&
(remote_codec_rate == codec_rate || fmtp_remote_codec_rate == imp->actual_samples_per_second)) ? 1 : 0;
if (fmtp_remote_codec_rate) {
remote_codec_rate = fmtp_remote_codec_rate;
}
(remote_codec_rate == codec_rate || remote_codec_rate == imp->actual_samples_per_second)) ? 1 : 0;
}

if (match && bit_rate && map_bit_rate && map_bit_rate != bit_rate && strcasecmp(map->rm_encoding, "ilbc") &&
Expand Down Expand Up @@ -16208,7 +16223,14 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_write_frame(switch_core_sess
session->enc_write_frame.codec = session->write_codec;
session->enc_write_frame.samples = enc_frame->datalen / sizeof(int16_t) / session->write_impl.number_of_channels;
session->enc_write_frame.channels = session->write_impl.number_of_channels;
if (frame->codec->implementation->samples_per_packet != session->write_impl.samples_per_packet) {
if (frame->codec->implementation->samples_per_packet != session->write_impl.samples_per_packet ||
frame->codec->implementation->samples_per_second != session->write_impl.samples_per_second) {
/* RFC 7587: a clock-rate change (e.g. AMR-WB/16000 -> Opus, whose RTP clock is
48 kHz) must regenerate the RTP timestamp at the write codec's clock instead of
passing the source timestamp through. samples_per_packet alone misses this for
a same-rate transcode (AMR-WB 16k and Opus 16k both pack 320 samples/20ms), so
the Opus timestamp would advance at 16 kHz. samples_per_interval carries the
correct 48 kHz Opus clock (see switch_core_media_set_codec). */
session->enc_write_frame.timestamp = 0;
} else {
session->enc_write_frame.timestamp = frame->timestamp;
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/switch_core_codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,45 @@ FST_CORE_BEGIN("./conf")
}
FST_TEST_END()

FST_TEST_BEGIN(test_opus_samples_per_packet)
{
/* RFC 7587: the Opus RTP timestamp advances at a fixed 48 kHz clock
(samples_per_second == 48000) regardless of the audio rate
(actual_samples_per_second). samples_per_packet must stay the TRUE
decoded PCM frame count (rate * ptime), because media bugs,
record_session and playback read it to size PCM frames -- inflating
it to the 48 kHz value corrupts them. Guard that invariant for the
8k/16k/48k Opus implementations at 20 ms. */
switch_codec_settings_t codec_settings = {{ 0 }};
int i;
struct { uint32_t rate; uint32_t samples_per_packet; } cases[] = {
{ 8000, 160 },
{ 16000, 320 },
{ 48000, 960 },
};

for (i = 0; i < 3; i++) {
switch_codec_t codec = { 0 };
switch_status_t status = switch_core_codec_init(&codec,
"OPUS",
"mod_opus",
NULL,
cases[i].rate,
20,
1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
&codec_settings, fst_pool);
fst_check(status == SWITCH_STATUS_SUCCESS);
if (status == SWITCH_STATUS_SUCCESS) {
fst_check_int_equals(codec.implementation->actual_samples_per_second, cases[i].rate);
fst_check_int_equals(codec.implementation->samples_per_second, 48000);
fst_check_int_equals(codec.implementation->microseconds_per_packet, 20000);
fst_check_int_equals(codec.implementation->samples_per_packet, cases[i].samples_per_packet);
switch_core_codec_destroy(&codec);
}
}
}
FST_TEST_END()

}
FST_SUITE_END()
}
Expand Down