Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix matching codecs with different rate or channels #3021

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

aler9
Copy link
Member

@aler9 aler9 commented Jan 25, 2025

Description

In pion/webrtc, currently codecs are matched regardless of the clock rate and the channel count, and this makes impossible to fully support codecs that might have a clock rate or channel count different than the default one, in particular LPCM, PCMU, PCMA and multiopus (the last one is a custom Opus variant present in the Chrome source code to support multichannel Opus).

For instance, let's suppose a peer (receiver) wants to receive an audio track encoded with LPCM, 48khz sample rate and 2 channels. This receiver doesn't know the audio codec yet, therefore it advertises all supported sample rates in the SDP:

LPCM/44100
LPCM/48000
LPCM/44100/2
LPCM/48000/2

The other peer (sender) receives the SDP, but since the clock rate and channel count are not taken into consideration when matching codecs, the sender codec LPCM/48000/2 is wrongly associated with the receiver codec LPCM/44100. The result is that the audio track cannot be decoded correctly from the receiver side.

This patch fixes the issue and has been running smoothly in MediaMTX for almost a year.

Unfortunately, in lots of examples and tests, the clock rate is not present (and in fact they are producing horrible SDPs that contain VP8/0 instead of VP8/90000 and are incompatible with lots of servers) therefore this new check causes troubles in existing code. In order to maintain compatibility, i added exceptions for the VP8, H264 and VP9 codecs.

In the future, it might be better to update examples (i can do it in a future patch) and remove the exception.

@aler9 aler9 force-pushed the patch-codecs-match branch from a488626 to 0e8d4ec Compare January 25, 2025 13:46
Copy link

codecov bot commented Jan 25, 2025

Codecov Report

Attention: Patch coverage is 93.18182% with 6 lines in your changes missing coverage. Please review.

Project coverage is 78.58%. Comparing base (47bde05) to head (b31dde1).

Files with missing lines Patch % Lines
internal/fmtp/fmtp.go 89.83% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3021      +/-   ##
==========================================
+ Coverage   78.51%   78.58%   +0.06%     
==========================================
  Files          89       89              
  Lines       11037    11104      +67     
==========================================
+ Hits         8666     8726      +60     
- Misses       1885     1891       +6     
- Partials      486      487       +1     
Flag Coverage Δ
go 80.13% <93.18%> (+0.13%) ⬆️
wasm 63.25% <44.87%> (-0.53%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@aler9 aler9 force-pushed the patch-codecs-match branch 7 times, most recently from 54a37a9 to eb71ae1 Compare January 25, 2025 15:35
@aler9 aler9 changed the title Fix codec matching in case of different clock rate or channels Fix codec matching with different rate or channels Jan 25, 2025
@aler9 aler9 force-pushed the patch-codecs-match branch from eb71ae1 to 55b280c Compare January 25, 2025 15:36
@aler9 aler9 changed the title Fix codec matching with different rate or channels Fix matching codecs with different rate or channels Jan 25, 2025
@aler9 aler9 force-pushed the patch-codecs-match branch from 55b280c to c10ed6e Compare January 25, 2025 15:37
Consider clock rate and channels when matching codecs. This allows to
support codecs with the same MIME type but sample rate or channel count
that might be different from the default ones, like PCMU, PCMA, LPCM
and multiopus.
@aler9 aler9 force-pushed the patch-codecs-match branch from c10ed6e to b31dde1 Compare January 25, 2025 15:39
@aler9 aler9 requested a review from JoeTurki January 25, 2025 15:45
Copy link
Member

@JoeTurki JoeTurki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this! Just few random thoughts; although I'm not fully confident with the Pion codebase yet to approve this so you'll have to wait for @Sean-Der , this change does make sense to me.

Thank you again <3

@@ -24,6 +24,74 @@ func parseParameters(line string) map[string]string {
return parameters
}

// ClockRateEqual checks whether two clock rates are equal.
func ClockRateEqual(mimeType string, valA, valB uint32) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but I feel like this function ClockRateEqual might not belong in the fmtp library. Could this logic be more appropriate elsewhere? same for ChannelsEqual etc

// Existing implementations often use VP8, H264 or Opus without setting clock rate or channels.
// Keep compatibility with these situations.
// It would be better to remove this exception in a future major release.
switch {
Copy link
Member

@JoeTurki JoeTurki Jan 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to handle this for other codecs like H.264 and VP9 as well?

Should we also match if either clock rate is 0 regardless of the codec (to maintain compatibility with cases where clock rate is not explicitly set)?

Edit: I think you forgot to add cases for them.

@@ -36,7 +36,7 @@ func Test_RTPSender_ReplaceTrack(t *testing.T) { //nolint:cyclop
trackA, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
assert.NoError(t, err)

trackB, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeH264}, "video", "pion")
trackB, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeH264, ClockRate: 90000}, "video", "pion")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep this test as it was to ensure we fallback to matching without the clockRate for backward compatibility? Instead, we could add a new test

@@ -400,7 +400,7 @@ func Test_TrackLocalStatic_Payloader(t *testing.T) {

customPayloader := &customCodecPayloader{}
track, err := NewTrackLocalStaticSample(
RTPCodecCapability{MimeType: mimeTypeCustomCodec},
RTPCodecCapability{MimeType: mimeTypeCustomCodec, ClockRate: 90000},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep this test as it was to ensure we fallback to matching without the clockRate for backward compatibility? Instead, we could add a new test

@JoeTurki JoeTurki requested a review from Sean-Der January 25, 2025 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants