Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
20 changes: 20 additions & 0 deletions node/src/DirectTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import {
parseTransportTraceEventData,
} from './Transport';
import type { SctpParameters } from './sctpParametersTypes';
import type { RtpParameters } from './rtpParametersTypes';
import type { Producer, ProducerOptions } from './ProducerTypes';
import type { AppData } from './types';
import * as ortc from './ortc';
import * as utils from './utils';
import { UnsupportedError } from './errors';
import { Event, Notification } from './fbs/notification';
import * as FbsDirectTransport from './fbs/direct-transport';
Expand Down Expand Up @@ -154,6 +158,22 @@ export class DirectTransportImpl<
);
}

override async produce<ProducerAppData extends AppData = AppData>(
options: ProducerOptions<ProducerAppData>
): Promise<Producer<ProducerAppData>> {
logger.debug('produce()');

// Clone given RTP parameters since bandwidth estimation stuff is removed
// from them below and they belong to the application.
const rtpParameters = utils.clone<RtpParameters>(options.rtpParameters);

// A DirectTransport doesn't run bandwidth estimation, so whatever was
// negotiated for it is dropped rather than honoured.
ortc.disableBweInRtpParameters(rtpParameters);

return super.produce({ ...options, rtpParameters });
}

sendRtcp(rtcpPacket: Buffer): void {
if (!Buffer.isBuffer(rtcpPacket)) {
throw new TypeError('rtcpPacket must be a Buffer');
Expand Down
18 changes: 17 additions & 1 deletion node/src/PipeTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
parseTuple,
parseTransportTraceEventData,
} from './Transport';
import type { Producer } from './ProducerTypes';
import type { Producer, ProducerOptions } from './ProducerTypes';
import type { Consumer, ConsumerType } from './ConsumerTypes';
import { ConsumerImpl } from './Consumer';
import type { RtpParameters } from './rtpParametersTypes';
Expand Down Expand Up @@ -223,6 +223,22 @@ export class PipeTransportImpl<PipeTransportAppData extends AppData = AppData>
}
}

override async produce<ProducerAppData extends AppData = AppData>(
options: ProducerOptions<ProducerAppData>
): Promise<Producer<ProducerAppData>> {
logger.debug('produce()');

// Clone given RTP parameters since bandwidth estimation stuff is removed
// from them below and they belong to the application.
const rtpParameters = utils.clone<RtpParameters>(options.rtpParameters);

// A PipeTransport doesn't run bandwidth estimation, so whatever was
// negotiated for it is dropped rather than honoured.
ortc.disableBweInRtpParameters(rtpParameters);

return super.produce({ ...options, rtpParameters });
}

override async consume<ConsumerAppData extends AppData = AppData>({
producerId,
appData,
Expand Down
4 changes: 4 additions & 0 deletions node/src/Transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ export abstract class TransportImpl<
remoteRtpCapabilities: clonedRtpCapabilities,
pipe,
enableRtx,
// A DirectTransport doesn't run bandwidth estimation, so it must not
// negotiate it whatever the given RTP capabilities say. A PipeTransport
// doesn't need this since its consume() overrides this method.
disableBwe: this.type === 'direct',
});

// Set MID.
Expand Down
46 changes: 43 additions & 3 deletions node/src/ortc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,11 +616,13 @@ export function getConsumerRtpParameters({
remoteRtpCapabilities,
pipe,
enableRtx,
disableBwe = false,
}: {
consumableRtpParameters: RtpParameters;
remoteRtpCapabilities: RtpCapabilities;
pipe: boolean;
enableRtx: boolean;
disableBwe?: boolean;
}): RtpParameters {
const consumerParams: RtpParameters = {
codecs: [],
Expand Down Expand Up @@ -694,6 +696,19 @@ export function getConsumerRtpParameters({
)
);

// A transport that doesn't run bandwidth estimation must not negotiate it.
// Dropping its header extensions is enough, since the RTCP feedback below is
// reduced out of them.
if (disableBwe) {
consumerParams.headerExtensions = consumerParams.headerExtensions.filter(
ext =>
ext.uri !==
'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' &&
ext.uri !==
'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'
);
}

// Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise.
if (
consumerParams.headerExtensions.some(
Expand Down Expand Up @@ -831,11 +846,13 @@ export function getPipeConsumerRtpParameters({
continue;
}

// Drop the bandwidth estimation feedback, which a PipeTransport doesn't run,
// and the generic NACK when there is no RTX to retransmit with.
codec.rtcpFeedback = codec.rtcpFeedback!.filter(
fb =>
(fb.type === 'nack' && fb.parameter === 'pli') ||
(fb.type === 'ccm' && fb.parameter === 'fir') ||
(enableRtx && fb.type === 'nack' && !fb.parameter)
fb.type !== 'goog-remb' &&
fb.type !== 'transport-cc' &&
(enableRtx || fb.type !== 'nack' || Boolean(fb.parameter))
);

consumerParams.codecs.push(codec);
Expand Down Expand Up @@ -890,6 +907,29 @@ export function getPipeConsumerRtpParameters({
return consumerParams;
}

/**
* Remove from the given RTP parameters everything related to bandwidth
* estimation, which a transport that doesn't run it must not negotiate.
*
* It mutates the given RTP parameters, so the caller must not pass the ones the
* application gave.
*/
export function disableBweInRtpParameters(rtpParameters: RtpParameters): void {
rtpParameters.headerExtensions = rtpParameters.headerExtensions?.filter(
ext =>
ext.uri !==
'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' &&
ext.uri !==
'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'
);

for (const codec of rtpParameters.codecs) {
codec.rtcpFeedback = codec.rtcpFeedback?.filter(
fb => fb.type !== 'transport-cc' && fb.type !== 'goog-remb'
);
}
}

function isRtxCodec(codec: RtpCodecCapability | RtpCodecParameters): boolean {
return /.+\/rtx$/i.test(codec.mimeType);
}
Expand Down
10 changes: 1 addition & 9 deletions node/src/supportedRtpCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,19 @@ const supportedRtpCapabilities: RouterRtpCapabilities = {
preferredEncrypt: false,
direction: 'recvonly',
},
{
kind: 'audio',
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
preferredId: 4,
preferredEncrypt: false,
direction: 'sendrecv',
},
{
kind: 'video',
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
preferredId: 4,
preferredEncrypt: false,
direction: 'sendrecv',
},
// NOTE: For audio we just enable transport-wide-cc-01 when receiving media.
{
kind: 'audio',
uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
preferredId: 5,
preferredEncrypt: false,
direction: 'recvonly',
direction: 'sendrecv',
},
{
kind: 'video',
Expand Down
8 changes: 1 addition & 7 deletions node/src/test/test-Consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,20 +744,14 @@ test('consumer.dump() succeeds', async () => {
});
expect(dump1.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([]);
expect(Array.isArray(dump1.rtpParameters.headerExtensions)).toBe(true);
expect(dump1.rtpParameters.headerExtensions!.length).toBe(3);
expect(dump1.rtpParameters.headerExtensions!.length).toBe(2);
expect(dump1.rtpParameters.headerExtensions).toEqual([
{
uri: 'urn:ietf:params:rtp-hdrext:sdes:mid',
id: 1,
encrypt: false,
parameters: {},
},
{
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
id: 4,
parameters: {},
encrypt: false,
},
{
uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
id: 6,
Expand Down
119 changes: 119 additions & 0 deletions node/src/test/test-DirectTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,125 @@ test('dataProducer.send() with subchannels succeeds', async () => {
}
}, 5000);

test('directTransport.produce() removes BWE stuff from RTP parameters', async () => {
const router = await ctx.worker!.createRouter({
mediaCodecs: [
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
},
],
});

const directTransport = await router.createDirectTransport();

const producer = await directTransport.produce({
kind: 'video',
rtpParameters: {
mid: 'VIDEO',
codecs: [
{
mimeType: 'video/VP8',
payloadType: 112,
clockRate: 90000,
rtcpFeedback: [
{ type: 'nack', parameter: 'pli' },
{ type: 'goog-remb' },
{ type: 'transport-cc' },
],
},
],
headerExtensions: [
{
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
id: 4,
},
{
uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
id: 5,
},
{
uri: 'urn:3gpp:video-orientation',
id: 13,
},
],
encodings: [{ ssrc: 22222222 }],
rtcp: { cname: 'video-1' },
},
});

expect(producer.rtpParameters.headerExtensions).toEqual([
{
uri: 'urn:3gpp:video-orientation',
id: 13,
encrypt: false,
parameters: {},
},
]);
expect(producer.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([
{ type: 'nack', parameter: 'pli' },
]);

router.close();
}, 2000);

test('directTransport.consume() removes BWE stuff from RTP parameters', async () => {
const router = await ctx.worker!.createRouter({
mediaCodecs: [
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
},
],
});

const directTransport = await router.createDirectTransport();

const producer = await directTransport.produce({
kind: 'video',
rtpParameters: {
mid: 'VIDEO',
codecs: [
{
mimeType: 'video/VP8',
payloadType: 112,
clockRate: 90000,
rtcpFeedback: [{ type: 'nack', parameter: 'pli' }],
},
],
encodings: [{ ssrc: 22222222 }],
rtcp: { cname: 'video-1' },
},
});

// Consume with the full Router RTP capabilities, which do include the BWE
// header extensions and RTCP feedback.
const consumer = await directTransport.consume({
producerId: producer.id,
rtpCapabilities: router.rtpCapabilities,
});

expect(
consumer.rtpParameters.headerExtensions!.map(ext => ext.uri)
).not.toContain('http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time');
expect(
consumer.rtpParameters.headerExtensions!.map(ext => ext.uri)
).not.toContain(
'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'
);

for (const codec of consumer.rtpParameters.codecs) {
expect(codec.rtcpFeedback!.map(fb => fb.type)).not.toContain('goog-remb');
expect(codec.rtcpFeedback!.map(fb => fb.type)).not.toContain(
'transport-cc'
);
}

router.close();
}, 2000);

test('DirectTransport methods reject if closed', async () => {
const directTransport = await ctx.router!.createDirectTransport();
const onObserverClose = jest.fn();
Expand Down
35 changes: 33 additions & 2 deletions rust/src/ortc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ pub(crate) fn get_consumer_rtp_parameters(
remote_rtp_capabilities: &RtpCapabilities,
pipe: bool,
enable_rtx: bool,
disable_bwe: bool,
) -> Result<RtpParameters, ConsumerRtpParametersError> {
let mut consumer_params = RtpParameters {
rtcp: consumable_rtp_parameters.rtcp.clone(),
Expand Down Expand Up @@ -835,6 +836,17 @@ pub(crate) fn get_consumer_rtp_parameters(
.cloned()
.collect();

// A transport that doesn't run bandwidth estimation must not negotiate it. Dropping its header
// extensions is enough, since the RTCP feedback below is reduced out of them.
if disable_bwe {
consumer_params.header_extensions.retain(|ext| {
!matches!(
ext.uri,
RtpHeaderExtensionUri::AbsSendTime | RtpHeaderExtensionUri::TransportWideCcDraft01
)
});
}

// Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise.
if consumer_params
.header_extensions
Expand Down Expand Up @@ -953,9 +965,11 @@ pub(crate) fn get_pipe_consumer_rtp_parameters(

let mut codec = codec.clone();

// Drop the bandwidth estimation feedback, which a PipeTransport doesn't run, and the
// generic NACK when there is no RTX to retransmit with.
codec.rtcp_feedback_mut().retain(|fb| {
matches!(fb, RtcpFeedback::NackPli | RtcpFeedback::CcmFir)
|| (enable_rtx && fb == &RtcpFeedback::Nack)
!matches!(fb, RtcpFeedback::GoogRemb | RtcpFeedback::TransportCc)
&& (enable_rtx || fb != &RtcpFeedback::Nack)
});

consumer_params.codecs.push(codec);
Expand Down Expand Up @@ -1013,6 +1027,23 @@ pub(crate) fn get_pipe_consumer_rtp_parameters(
consumer_params
}

/// Removes from the given RTP parameters everything related to bandwidth estimation, which a
/// transport that doesn't run it must not negotiate.
pub(crate) fn disable_bwe_in_rtp_parameters(rtp_parameters: &mut RtpParameters) {
rtp_parameters.header_extensions.retain(|ext| {
!matches!(
ext.uri,
RtpHeaderExtensionUri::AbsSendTime | RtpHeaderExtensionUri::TransportWideCcDraft01
)
});

for codec in &mut rtp_parameters.codecs {
codec
.rtcp_feedback_mut()
.retain(|fb| !matches!(fb, RtcpFeedback::GoogRemb | RtcpFeedback::TransportCc));
}
}

struct CodecToMatch<'a> {
channels: Option<NonZeroU8>,
clock_rate: NonZeroU32,
Expand Down
Loading
Loading