Repository: pion/webrtc
Pull request #1517 (2020-11-11 07:06:45)

New Track API
The Pion WebRTC API has been dramatically redesigned. The design docs
are located here [0]

You can also read the release notes [1] on how to migrate your
application.

[0] https://github.com/pion/webrtc-v3-design/issues/33
[1] https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0


Sean-Der: This is still a WIP. I really think I will be done tomorrow, if anyone is interested I would love help!

* [ ] Finish TestMediaEngineRemoteDescription
      - This test reads the remote description and updates our local PayloadTypes to their proper values
* [ ] Finish all TODOs
* [ ] Review examples. Some now allow all codecs instead of just VP8/Opus. MediaEngine needs to come back in them
* [ ] Write `TrackLocal` tests.
codecov[bot]: # [Codecov](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=h1) Report
> Merging [#1517](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=desc) (f33c0ab) into [master](https://codecov.io/gh/pion/webrtc/commit/79d93e983587ca80d0e0119306721cd11b929eaa?el=desc) (79d93e9) will **decrease** coverage by `3.27%`.
> The diff coverage is `10.00%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/webrtc/pull/1517/graphs/tree.svg?width=650&height=150&src=pr&token=XOOuQo07I2)](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1517      +/-   ##
==========================================
- Coverage   74.15%   70.87%   -3.28%     
==========================================
  Files          76       57      -19     
  Lines        5413     2074    -3339     
==========================================
- Hits         4014     1470    -2544     
+ Misses       1036      504     -532     
+ Partials      363      100     -263     
```

| Flag | Coverage Δ | |
|---|---|---|
| go | `?` | |
| wasm | `70.87% <10.00%> (-0.64%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [rtpcodec.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-cnRwY29kZWMuZ28=) | `0.00% <0.00%> (ø)` | |
| [stats.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-c3RhdHMuZ28=) | `0.00% <ø> (-67.86%)` | :arrow_down: |
| [track\_local.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-dHJhY2tfbG9jYWwuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/media/samplebuilder/samplebuilder.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-cGtnL21lZGlhL3NhbXBsZWJ1aWxkZXIvc2FtcGxlYnVpbGRlci5nbw==) | `91.22% <100.00%> (ø)` | |
| [icetcp.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-aWNldGNwLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [sdpsemantics.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-c2Rwc2VtYW50aWNzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [icecandidatepair.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-aWNlY2FuZGlkYXRlcGFpci5nbw==) | `0.00% <0.00%> (-85.72%)` | :arrow_down: |
| [gathering\_complete\_promise.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-Z2F0aGVyaW5nX2NvbXBsZXRlX3Byb21pc2UuZ28=) | `50.00% <0.00%> (-50.00%)` | :arrow_down: |
| [dtlsrole.go](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree#diff-ZHRsc3JvbGUuZ28=) | `68.96% <0.00%> (-31.04%)` | :arrow_down: |
| ... and [20 more](https://codecov.io/gh/pion/webrtc/pull/1517/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=footer). Last update [79d93e9...f33c0ab](https://codecov.io/gh/pion/webrtc/pull/1517?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jeremija: This looks really nice! From what I can tell this looks good to me, I'll try to find some time to test the new functionality. I'm excited about finally being able to get rid of the need to always start negotiating on the server side.

I tried to help out by writing the tests for `TrackLocal` and I got a little stuck at this bullet point:

```
// * Offer fail when Answerer doesn't support a codec
```

I am not able to get it to fail - I'm getting no errors. The only thing I see is that the SDP does not contain the required `mid` when the codecs don't match. Is this the expected behavior?
Sean-Der: > This looks really nice! From what I can tell this looks good to me, I'll try to find some time to test the new functionality. I'm excited about finally being able to get rid of the need to always start negotiating on the server side.
> 
> I tried to help out by writing the tests for `TrackLocal` and I got a little stuck at this bullet point:
> 
> ```
> // * Offer fail when Answerer doesn't support a codec
> ```
> 
> I am not able to get it to fail - I'm getting no errors. The only thing I see is that the SDP does not contain the required `mid` when the codecs don't match. Is this the expected behavior?
> 
> For more details, see the last test in `track_local_static_test.go` in `jeremija/track-local-tests` branch.

That would be so amazing! Thanks for helping :) The issue is the errors are async is the issue, `Bind` is called in a goroutine so they don't get returned by `SetRemoteDescription`

We have two choices
* Add any additional 'validate codec' to a Track
* Call `Bind` earlier? 
Sean-Der: IT'S GREEN :fire:

I also tested all the examples. I was able to broadcast from Chrome and then view in Chrome/FireFox/Safari (that felt awesome)

Personally I would like to merge this, and then get help from others going forward. I would really love to get @jeremija involved, I have been working on this for two weeks so I feel pretty done with this PR :p 
jeremija: @Sean-Der let me know if you think we should take this discussion elsewhere since I think it's only indirectly related to this PR 🙂 

> We have two choices
>
>  - Add any additional 'validate codec' to a Track
> - Call Bind earlier?

I _think_ we should be able to call `Bind` earlier. I'll try to move it out of the `RTPSender::Send` and see if I find any issues.

Side question. Why do we need arguments to `RTPSender::Send`? The only place it's called from is in `startRTPSenders` and all the parameters passed to it are read from the same instance of `RTPSender`:

```go
			err := transceiver.Sender().Send(RTPSendParameters{
				Encodings: RTPEncodingParameters{
					RTPCodingParameters{
						SSRC:        transceiver.Sender().ssrc,
						PayloadType: transceiver.Sender().payloadType,
					},
				},
			})
```

Why not make it `err := transceiver.Sender().Send()`, since the `Send` method should already have access to `RTPSender::ssrc` and `RTPSender::payloadType`? Is there something I'm missing here?
masterada: > We can also then rebase @masterada PR and merge that quickly. I would love to release `v3.0.0` before thanksgiving with NACKs (or something similar) and this. It would be a massive improvement and pull a lot of people into Pion I think :)

I want to try to make the interceptor design a bit closed to how TrackLocal works, maybe even use TrackLocalContext somehow, or something similar to that. Will have time for this on Tuesday afternoon (Europe time).

Patch:
diff --git a/errors.go b/errors.go
index 2a27ad9b..8fb5b9a1 100644
--- a/errors.go
+++ b/errors.go
@@ -129,6 +129,18 @@ var (
 	// ErrFailedToGenerateCertificateFingerprint indicates that we failed to generate the fingerprint used for comparing certificates
 	ErrFailedToGenerateCertificateFingerprint = errors.New("failed to generate certificate fingerprint")
 
+	// ErrNoCodecsAvailable indicates that operation isn't possible because the MediaEngine has no codecs available
+	ErrNoCodecsAvailable = errors.New("operation failed no codecs are available")
+
+	// ErrUnsupportedCodec indicates the remote peer doesn't support the requested codec
+	ErrUnsupportedCodec = errors.New("unable to start track, codec is not supported by remote")
+
+	// ErrUnbindFailed indicates that a TrackLocal was not able to be unbind
+	ErrUnbindFailed = errors.New("failed to unbind TrackLocal from PeerConnection")
+
+	// ErrNoPayloaderForCodec indicates that the requested codec does not have a payloader
+	ErrNoPayloaderForCodec = errors.New("the requested codec does not have a payloader")
+
 	errDetachNotEnabled                 = errors.New("enable detaching by calling webrtc.DetachDataChannels()")
 	errDetachBeforeOpened               = errors.New("datachannel not opened yet, try calling Detach from OnOpen")
 	errDtlsTransportNotStarted          = errors.New("the DTLS transport has not started yet")
@@ -149,9 +161,7 @@ var (
 	errICEProtocolUnknown             = errors.New("unknown protocol")
 	errICEGathererNotStarted          = errors.New("gatherer not started")
 
-	errMediaEngineParseError    = errors.New("format parse error")
-	errMediaEngineCodecNotFound = errors.New("could not find codec")
-	errNetworkTypeUnknown       = errors.New("unknown network type")
+	errNetworkTypeUnknown = errors.New("unknown network type")
 
 	errSDPDoesNotMatchOffer                           = errors.New("new sdp does not match previous offer")
 	errSDPDoesNotMatchAnswer                          = errors.New("new sdp does not match previous answer")
@@ -163,16 +173,15 @@ var (
 	errPeerConnRemoteDescriptionNil                   = errors.New("remoteDescription has not been set yet")
 	errPeerConnSingleMediaSectionHasExplicitSSRC      = errors.New("single media section has an explicit SSRC")
 	errPeerConnRemoteSSRCAddTransceiver               = errors.New("could not add transceiver for remote SSRC")
-	errPeerConnSimulcastMidAndRidRTPExtensionRequired = errors.New("mid and rid RTP Extensions required for Simulcast")
+	errPeerConnSimulcastMidRTPExtensionRequired       = errors.New("mid RTP Extensions required for Simulcast")
+	errPeerConnSimulcastStreamIDRTPExtensionRequired  = errors.New("stream id RTP Extensions required for Simulcast")
 	errPeerConnSimulcastIncomingSSRCFailed            = errors.New("incoming SSRC failed Simulcast probing")
 	errPeerConnAddTransceiverFromKindOnlyAcceptsOne   = errors.New("AddTransceiverFromKind only accepts one RtpTransceiverInit")
 	errPeerConnAddTransceiverFromTrackOnlyAcceptsOne  = errors.New("AddTransceiverFromTrack only accepts one RtpTransceiverInit")
-	errPeerConnCodecsNotFound                         = errors.New("no codecs found")
-	errPeerConnAddTransceiverFromKindSupport          = errors.New("AddTransceiverFromKind currently only supports recvonly and sendrecv")
-	errPeerConnAddTransceiverFromTrackOneTransceiver  = errors.New("AddTransceiverFromTrack only accepts one RtpTransceiverInit")
+	errPeerConnAddTransceiverFromKindSupport          = errors.New("AddTransceiverFromKind currently only supports recvonly")
+	errPeerConnAddTransceiverFromTrackSupport         = errors.New("AddTransceiverFromTrack currently only supports sendonly and sendrecv")
 	errPeerConnSetIdentityProviderNotImplemented      = errors.New("TODO SetIdentityProvider")
 	errPeerConnWriteRTCPOpenWriteStream               = errors.New("WriteRTCP failed to open WriteStream")
-	errPeerConnCodecPayloaderNotSet                   = errors.New("codec payloader not set")
 	errPeerConnTranscieverMidNil                      = errors.New("cannot find transceiver with mid")
 
 	errRTPReceiverDTLSTransportNil            = errors.New("DTLSTransport must not be nil")
@@ -181,11 +190,9 @@ var (
 	errRTPReceiverForSSRCTrackStreamNotFound  = errors.New("no trackStreams found for SSRC")
 	errRTPReceiverForRIDTrackStreamNotFound   = errors.New("no trackStreams found for RID")
 
-	errRTPSenderTrackNil                   = errors.New("Track must not be nil")
-	errRTPSenderDTLSTransportNil           = errors.New("DTLSTransport must not be nil")
-	errRTPSenderCannotConstructRemoteTrack = errors.New("RTPSender can not be constructed with remote track")
-	errRTPSenderSendAlreadyCalled          = errors.New("Send has already been called")
-	errRTPSenderStopped                    = errors.New("RTPSender has been stopped")
+	errRTPSenderTrackNil          = errors.New("Track must not be nil")
+	errRTPSenderDTLSTransportNil  = errors.New("DTLSTransport must not be nil")
+	errRTPSenderSendAlreadyCalled = errors.New("Send has already been called")
 
 	errRTPTransceiverCannotChangeMid        = errors.New("errRTPSenderTrackNil")
 	errRTPTransceiverSetSendingInvalidState = errors.New("invalid state change in RTPTransceiver.setSending")
@@ -195,8 +202,6 @@ var (
 	errSDPZeroTransceivers                 = errors.New("addTransceiverSDP() called with 0 transceivers")
 	errSDPMediaSectionMediaDataChanInvalid = errors.New("invalid Media Section. Media + DataChannel both enabled")
 	errSDPMediaSectionMultipleTrackInvalid = errors.New("invalid Media Section. Can not have multiple tracks in one MediaSection in UnifiedPlan")
-	errSDPParseExtMap                      = errors.New("failed to parse ExtMap")
-	errSDPRemoteDescriptionChangedExtMap   = errors.New("RemoteDescription changed some extmaps values")
 
 	errSettingEngineSetAnsweringDTLSRole = errors.New("SetAnsweringDTLSRole must DTLSRoleClient or DTLSRoleServer")
 
@@ -204,8 +209,4 @@ var (
 	errSignalingStateProposedTransitionInvalid = errors.New("invalid proposed signaling state transition")
 
 	errStatsICECandidateStateInvalid = errors.New("cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state")
-
-	errTrackLocalTrackRead   = errors.New("this is a local track and must not be read from")
-	errTrackLocalTrackWrite  = errors.New("this is a remote track and must not be written to")
-	errTrackSSRCNewTrackZero = errors.New("SSRC supplied to NewTrack() must be non-zero")
 )
diff --git a/examples/broadcast/main.go b/examples/broadcast/main.go
index 04f9a260..dfee2c00 100644
--- a/examples/broadcast/main.go
+++ b/examples/broadcast/main.go
@@ -23,16 +23,6 @@ func main() { // nolint:gocognit
 	signal.Decode(<-sdpChan, &offer)
 	fmt.Println("")
 
-	// Since we are answering use PayloadTypes declared by offerer
-	mediaEngine := webrtc.MediaEngine{}
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
-		panic(err)
-	}
-
-	// Create the API object with the MediaEngine
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-
 	peerConnectionConfig := webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
 			{
@@ -42,7 +32,7 @@ func main() { // nolint:gocognit
 	}
 
 	// Create a new RTCPeerConnection
-	peerConnection, err := api.NewPeerConnection(peerConnectionConfig)
+	peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfig)
 	if err != nil {
 		panic(err)
 	}
@@ -52,23 +42,23 @@ func main() { // nolint:gocognit
 		panic(err)
 	}
 
-	localTrackChan := make(chan *webrtc.Track)
+	localTrackChan := make(chan *webrtc.TrackLocalStaticRTP)
 	// Set a handler for when a new remote track starts, this just distributes all our packets
 	// to connected peers
-	peerConnection.OnTrack(func(remoteTrack *webrtc.Track, receiver *webrtc.RTPReceiver) {
+	peerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
 		// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
 		// This can be less wasteful by processing incoming RTCP events, then we would emit a NACK/PLI when a viewer requests it
 		go func() {
 			ticker := time.NewTicker(rtcpPLIInterval)
 			for range ticker.C {
-				if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: remoteTrack.SSRC()}}); rtcpSendErr != nil {
+				if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(remoteTrack.SSRC())}}); rtcpSendErr != nil {
 					fmt.Println(rtcpSendErr)
 				}
 			}
 		}()
 
 		// Create a local track, all our SFU clients will be fed via this track
-		localTrack, newTrackErr := peerConnection.NewTrack(remoteTrack.PayloadType(), remoteTrack.SSRC(), "video", "pion")
+		localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", "pion")
 		if newTrackErr != nil {
 			panic(newTrackErr)
 		}
@@ -126,7 +116,7 @@ func main() { // nolint:gocognit
 		signal.Decode(<-sdpChan, &recvOnlyOffer)
 
 		// Create a new PeerConnection
-		peerConnection, err := api.NewPeerConnection(peerConnectionConfig)
+		peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfig)
 		if err != nil {
 			panic(err)
 		}
diff --git a/examples/custom-logger/main.go b/examples/custom-logger/main.go
index 4f344dab..7e8edcef 100644
--- a/examples/custom-logger/main.go
+++ b/examples/custom-logger/main.go
@@ -61,6 +61,11 @@ func main() {
 		panic(err)
 	}
 
+	// We need a DataChannel so we can have ICE Candidates
+	if _, err = offerPeerConnection.CreateDataChannel("custom-logger", nil); err != nil {
+		panic(err)
+	}
+
 	// Create a new RTCPeerConnection
 	answerPeerConnection, err := api.NewPeerConnection(webrtc.Configuration{})
 	if err != nil {
diff --git a/examples/ice-tcp/main.go b/examples/ice-tcp/main.go
index ba0c2779..528d254c 100644
--- a/examples/ice-tcp/main.go
+++ b/examples/ice-tcp/main.go
@@ -17,7 +17,9 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
 
 	if peerConnection == nil {
 		m := webrtc.MediaEngine{}
-		m.RegisterDefaultCodecs()
+		if err = m.RegisterDefaultCodecs(); err != nil {
+			panic(err)
+		}
 
 		settingEngine := webrtc.SettingEngine{}
 
diff --git a/examples/insertable-streams/main.go b/examples/insertable-streams/main.go
index 691626ad..f6169809 100644
--- a/examples/insertable-streams/main.go
+++ b/examples/insertable-streams/main.go
@@ -7,7 +7,6 @@ import (
 	"os"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/examples/internal/signal"
 	"github.com/pion/webrtc/v3/pkg/media"
@@ -17,34 +16,7 @@ import (
 const cipherKey = 0xAA
 
 func main() {
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it.  This because we must use the
-	// dynamic media type from the sender in our answer. This is not required if we are the offerer
-	mediaEngine := webrtc.MediaEngine{}
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
-		panic(err)
-	}
-
-	// Search for VP8 Payload type. If the offer doesn't support VP8 exit since
-	// since they won't be able to decode anything we send them
-	var payloadType uint8
-	for _, videoCodec := range mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo) {
-		if videoCodec.Name == "VP8" {
-			payloadType = videoCodec.PayloadType
-			break
-		}
-	}
-	if payloadType == 0 {
-		panic("Remote peer does not support VP8")
-	}
-
-	// Create a new RTCPeerConnection
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-	peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
+	peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
 			{
 				URLs: []string{"stun:stun.l.google.com:19302"},
@@ -56,7 +28,7 @@ func main() {
 	}
 
 	// Create a video track
-	videoTrack, err := peerConnection.NewTrack(payloadType, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	if err != nil {
 		panic(err)
 	}
@@ -100,7 +72,7 @@ func main() {
 			}
 
 			time.Sleep(sleepTime)
-			if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Samples: 90000}); ivfErr != nil {
+			if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil {
 				panic(ivfErr)
 			}
 		}
@@ -115,6 +87,10 @@ func main() {
 		}
 	})
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	// Set the remote SessionDescription
 	if err = peerConnection.SetRemoteDescription(offer); err != nil {
 		panic(err)
diff --git a/examples/play-from-disk-renegotation/index.html b/examples/play-from-disk-renegotation/index.html
index a1a22d39..2a0ab5c4 100644
--- a/examples/play-from-disk-renegotation/index.html
+++ b/examples/play-from-disk-renegotation/index.html
@@ -17,7 +17,7 @@
 
   <script>
     let activeVideos = 0
-    let pc = new RTCPeerConnection()({
+    let pc = new RTCPeerConnection({
       iceServers: [
         {
           urls: 'stun:stun.l.google.com:19302'
diff --git a/examples/play-from-disk-renegotation/main.go b/examples/play-from-disk-renegotation/main.go
index d19d920a..029e4a10 100644
--- a/examples/play-from-disk-renegotation/main.go
+++ b/examples/play-from-disk-renegotation/main.go
@@ -66,9 +66,8 @@ func createPeerConnection(w http.ResponseWriter, r *http.Request) {
 
 // Add a single video track
 func addVideo(w http.ResponseWriter, r *http.Request) {
-	videoTrack, err := peerConnection.NewTrack(
-		webrtc.DefaultPayloadTypeVP8,
-		randutil.NewMathRandomGenerator().Uint32(),
+	videoTrack, err := webrtc.NewTrackLocalStaticSample(
+		webrtc.RTPCodecCapability{MimeType: "video/vp8"},
 		fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
 		fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
 	)
@@ -121,7 +120,7 @@ func main() {
 
 // Read a video file from disk and write it to a webrtc.Track
 // When the video has been completely read this exits without error
-func writeVideoToTrack(t *webrtc.Track) {
+func writeVideoToTrack(t *webrtc.TrackLocalStaticSample) {
 	// Open a IVF file and start reading using our IVFReader
 	file, err := os.Open("output.ivf")
 	if err != nil {
@@ -144,7 +143,7 @@ func writeVideoToTrack(t *webrtc.Track) {
 		}
 
 		time.Sleep(sleepTime)
-		if err = t.WriteSample(media.Sample{Data: frame, Samples: 90000}); err != nil {
+		if err = t.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
 			fmt.Printf("Finish writing video track: %s ", err)
 			return
 		}
diff --git a/examples/play-from-disk/main.go b/examples/play-from-disk/main.go
index 820990f8..758ff549 100644
--- a/examples/play-from-disk/main.go
+++ b/examples/play-from-disk/main.go
@@ -7,7 +7,6 @@ import (
 	"os"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/examples/internal/signal"
 	"github.com/pion/webrtc/v3/pkg/media"
@@ -32,20 +31,8 @@ func main() {
 		panic("Could not find `" + audioFileName + "` or `" + videoFileName + "`")
 	}
 
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it.  This because we must use the
-	// dynamic media type from the sender in our answer. This is not required if we are the offerer
-	mediaEngine := webrtc.MediaEngine{}
-	if err = mediaEngine.PopulateFromSDP(offer); err != nil {
-		panic(err)
-	}
-
 	// Create a new RTCPeerConnection
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-	peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
+	peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
 			{
 				URLs: []string{"stun:stun.l.google.com:19302"},
@@ -59,12 +46,13 @@ func main() {
 
 	if haveVideoFile {
 		// Create a video track
-		videoTrack, addTrackErr := peerConnection.NewTrack(getPayloadType(mediaEngine, webrtc.RTPCodecTypeVideo, "VP8"), randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
-		if addTrackErr != nil {
-			panic(addTrackErr)
+		videoTrack, videoTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
+		if videoTrackErr != nil {
+			panic(videoTrackErr)
 		}
-		if _, addTrackErr = peerConnection.AddTrack(videoTrack); addTrackErr != nil {
-			panic(addTrackErr)
+
+		if _, videoTrackErr = peerConnection.AddTrack(videoTrack); videoTrackErr != nil {
+			panic(videoTrackErr)
 		}
 
 		go func() {
@@ -97,7 +85,7 @@ func main() {
 				}
 
 				time.Sleep(sleepTime)
-				if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Samples: 90000}); ivfErr != nil {
+				if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil {
 					panic(ivfErr)
 				}
 			}
@@ -106,12 +94,12 @@ func main() {
 
 	if haveAudioFile {
 		// Create a audio track
-		audioTrack, addTrackErr := peerConnection.NewTrack(getPayloadType(mediaEngine, webrtc.RTPCodecTypeAudio, "opus"), randutil.NewMathRandomGenerator().Uint32(), "audio", "pion")
-		if addTrackErr != nil {
-			panic(addTrackErr)
+		audioTrack, audioTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion")
+		if audioTrackErr != nil {
+			panic(audioTrackErr)
 		}
-		if _, addTrackErr = peerConnection.AddTrack(audioTrack); addTrackErr != nil {
-			panic(addTrackErr)
+		if _, audioTrackErr = peerConnection.AddTrack(audioTrack); audioTrackErr != nil {
+			panic(audioTrackErr)
 		}
 
 		go func() {
@@ -146,13 +134,13 @@ func main() {
 				// The amount of samples is the difference between the last and current timestamp
 				sampleCount := float64(pageHeader.GranulePosition - lastGranule)
 				lastGranule = pageHeader.GranulePosition
+				sampleDuration := time.Duration((sampleCount/48000)*1000) * time.Millisecond
 
-				if oggErr = audioTrack.WriteSample(media.Sample{Data: pageData, Samples: uint32(sampleCount)}); oggErr != nil {
+				if oggErr = audioTrack.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); oggErr != nil {
 					panic(oggErr)
 				}
 
-				// Convert seconds to Milliseconds, Sleep doesn't accept floats
-				time.Sleep(time.Duration((sampleCount/48000)*1000) * time.Millisecond)
+				time.Sleep(sampleDuration)
 			}
 		}()
 	}
@@ -166,6 +154,10 @@ func main() {
 		}
 	})
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	// Set the remote SessionDescription
 	if err = peerConnection.SetRemoteDescription(offer); err != nil {
 		panic(err)
@@ -196,15 +188,3 @@ func main() {
 	// Block forever
 	select {}
 }
-
-// Search for Codec PayloadType
-//
-// Since we are answering we need to match the remote PayloadType
-func getPayloadType(m webrtc.MediaEngine, codecType webrtc.RTPCodecType, codecName string) uint8 {
-	for _, codec := range m.GetCodecsByKind(codecType) {
-		if codec.Name == codecName {
-			return codec.PayloadType
-		}
-	}
-	panic(fmt.Sprintf("Remote peer does not support %s", codecName))
-}
diff --git a/examples/reflect/main.go b/examples/reflect/main.go
index 55a962ab..8ff53d18 100644
--- a/examples/reflect/main.go
+++ b/examples/reflect/main.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/rtcp"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/examples/internal/signal"
@@ -13,29 +12,20 @@ import (
 func main() {
 	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
 
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it. Since we are echoing their RTP packet
-	// back to them we are actually codec agnostic - we can accept all their codecs. This also ensures that we use the
-	// dynamic media type from the sender in our answer.
-	mediaEngine := webrtc.MediaEngine{}
+	// Create a MediaEngine object to configure the supported codec
+	m := webrtc.MediaEngine{}
 
-	// Add codecs to the mediaEngine. Note that even though we are only going to echo back the sender's video we also
-	// add audio codecs. This is because createAnswer will create an audioTransceiver and associated SDP and we currently
-	// cannot tell it not to. The audio SDP must match the sender's codecs too...
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
+	// Setup the codecs you want to use.
+	// We'll use a VP8 and Opus but you can also define your own
+	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
+		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
+		PayloadType:        96,
+	}, webrtc.RTPCodecTypeVideo); err != nil {
 		panic(err)
 	}
 
-	videoCodecs := mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo)
-	if len(videoCodecs) == 0 {
-		panic("Offer contained no video codecs")
-	}
-
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
+	// Create the API object with the MediaEngine
+	api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
 
 	// Prepare the configuration
 	config := webrtc.Configuration{
@@ -52,7 +42,7 @@ func main() {
 	}
 
 	// Create Track that we send video back to browser on
-	outputTrack, err := peerConnection.NewTrack(videoCodecs[0].PayloadType, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	if err != nil {
 		panic(err)
 	}
@@ -62,6 +52,10 @@ func main() {
 		panic(err)
 	}
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	// Set the remote SessionDescription
 	err = peerConnection.SetRemoteDescription(offer)
 	if err != nil {
@@ -70,20 +64,20 @@ func main() {
 
 	// Set a handler for when a new remote track starts, this handler copies inbound RTP packets,
 	// replaces the SSRC and sends them back
-	peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
+	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
 		// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
 		// This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it
 		go func() {
 			ticker := time.NewTicker(time.Second * 3)
 			for range ticker.C {
-				errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
+				errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}})
 				if errSend != nil {
 					fmt.Println(errSend)
 				}
 			}
 		}()
 
-		fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().Name)
+		fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType)
 		for {
 			// Read RTP packets being sent to Pion
 			rtp, readErr := track.ReadRTP()
@@ -91,10 +85,6 @@ func main() {
 				panic(readErr)
 			}
 
-			// Replace the SSRC with the SSRC of the outbound track.
-			// The only change we are making replacing the SSRC, the RTP packets are unchanged otherwise
-			rtp.SSRC = outputTrack.SSRC()
-
 			if writeErr := outputTrack.WriteRTP(rtp); writeErr != nil {
 				panic(writeErr)
 			}
@@ -116,8 +106,7 @@ func main() {
 	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
 
 	// Sets the LocalDescription, and starts our UDP listeners
-	err = peerConnection.SetLocalDescription(answer)
-	if err != nil {
+	if err = peerConnection.SetLocalDescription(answer); err != nil {
 		panic(err)
 	}
 
diff --git a/examples/rtp-forwarder/main.go b/examples/rtp-forwarder/main.go
index 5209bc95..07b2f09e 100644
--- a/examples/rtp-forwarder/main.go
+++ b/examples/rtp-forwarder/main.go
@@ -17,22 +17,29 @@ type udpConn struct {
 }
 
 func main() {
-	// Create context
-	ctx, cancel := context.WithCancel(context.Background())
+	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
 
 	// Create a MediaEngine object to configure the supported codec
 	m := webrtc.MediaEngine{}
 
 	// Setup the codecs you want to use.
-	// We'll use a VP8 codec but you can also define your own
-	m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
-	m.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
+	// We'll use a VP8 and Opus but you can also define your own
+	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
+		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
+		PayloadType:        96,
+	}, webrtc.RTPCodecTypeVideo); err != nil {
+		panic(err)
+	}
+	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
+		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/opus", ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
+		PayloadType:        111,
+	}, webrtc.RTPCodecTypeAudio); err != nil {
+		panic(err)
+	}
 
 	// Create the API object with the MediaEngine
 	api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
 
-	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
-
 	// Prepare the configuration
 	config := webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
@@ -87,7 +94,7 @@ func main() {
 	// Set a handler for when a new remote track starts, this handler will forward data to
 	// our UDP listeners.
 	// In your application this is where you would handle/process audio/video
-	peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
+	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
 		// Retrieve udp connection
 		c, ok := udpConns[track.Kind().String()]
 		if !ok {
@@ -98,7 +105,7 @@ func main() {
 		go func() {
 			ticker := time.NewTicker(time.Second * 2)
 			for range ticker.C {
-				if rtcpErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}}); rtcpErr != nil {
+				if rtcpErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil {
 					fmt.Println(rtcpErr)
 				}
 			}
@@ -128,6 +135,9 @@ func main() {
 		}
 	})
 
+	// Create context
+	ctx, cancel := context.WithCancel(context.Background())
+
 	// Set the handler for ICE connection state
 	// This will notify you when the peer has connected/disconnected
 	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
diff --git a/examples/rtp-to-webrtc/main.go b/examples/rtp-to-webrtc/main.go
index 60fee66f..1fd9916d 100644
--- a/examples/rtp-to-webrtc/main.go
+++ b/examples/rtp-to-webrtc/main.go
@@ -10,34 +10,7 @@ import (
 )
 
 func main() {
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it.  This because we must use the
-	// dynamic media type from the sender in our answer. This is not required if we are the offerer
-	mediaEngine := webrtc.MediaEngine{}
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
-		panic(err)
-	}
-
-	// Search for VP8 Payload type. If the offer doesn't support VP8 exit since
-	// since they won't be able to decode anything we send them
-	var payloadType uint8
-	for _, videoCodec := range mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo) {
-		if videoCodec.Name == "VP8" {
-			payloadType = videoCodec.PayloadType
-			break
-		}
-	}
-	if payloadType == 0 {
-		panic("Remote peer does not support VP8")
-	}
-
-	// Create a new RTCPeerConnection
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-	peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
+	peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
 			{
 				URLs: []string{"stun:stun.l.google.com:19302"},
@@ -74,8 +47,8 @@ func main() {
 		panic(err)
 	}
 
-	// Create a video track, using the same SSRC as the incoming RTP Packet
-	videoTrack, err := peerConnection.NewTrack(payloadType, packet.SSRC, "video", "pion")
+	// Create a video track
+	videoTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	if err != nil {
 		panic(err)
 	}
@@ -89,6 +62,10 @@ func main() {
 		fmt.Printf("Connection State has changed %s \n", connectionState.String())
 	})
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	// Set the remote SessionDescription
 	if err = peerConnection.SetRemoteDescription(offer); err != nil {
 		panic(err)
@@ -124,13 +101,7 @@ func main() {
 			panic(err)
 		}
 
-		packet := &rtp.Packet{}
-		if err := packet.Unmarshal(inboundRTPPacket[:n]); err != nil {
-			panic(err)
-		}
-		packet.Header.PayloadType = payloadType
-
-		if writeErr := videoTrack.WriteRTP(packet); writeErr != nil {
+		if _, writeErr := videoTrack.Write(inboundRTPPacket[:n]); writeErr != nil {
 			panic(writeErr)
 		}
 	}
diff --git a/examples/save-to-disk/main.go b/examples/save-to-disk/main.go
index 05c28715..1bfaea96 100644
--- a/examples/save-to-disk/main.go
+++ b/examples/save-to-disk/main.go
@@ -13,7 +13,7 @@ import (
 	"github.com/pion/webrtc/v3/pkg/media/oggwriter"
 )
 
-func saveToDisk(i media.Writer, track *webrtc.Track) {
+func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
 	defer func() {
 		if err := i.Close(); err != nil {
 			panic(err)
@@ -32,19 +32,29 @@ func saveToDisk(i media.Writer, track *webrtc.Track) {
 }
 
 func main() {
+	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
+
 	// Create a MediaEngine object to configure the supported codec
 	m := webrtc.MediaEngine{}
 
 	// Setup the codecs you want to use.
-	// We'll use a VP8 codec but you can also define your own
-	m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
-	m.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
+	// We'll use a VP8 and Opus but you can also define your own
+	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
+		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
+		PayloadType:        96,
+	}, webrtc.RTPCodecTypeVideo); err != nil {
+		panic(err)
+	}
+	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
+		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/opus", ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
+		PayloadType:        111,
+	}, webrtc.RTPCodecTypeAudio); err != nil {
+		panic(err)
+	}
 
 	// Create the API object with the MediaEngine
 	api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
 
-	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
-
 	// Prepare the configuration
 	config := webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
@@ -79,12 +89,12 @@ func main() {
 	// Set a handler for when a new remote track starts, this handler saves buffers to disk as
 	// an ivf file, since we could have multiple video tracks we provide a counter.
 	// In your application this is where you would handle/process video
-	peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
+	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
 		// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
 		go func() {
 			ticker := time.NewTicker(time.Second * 3)
 			for range ticker.C {
-				errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
+				errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}})
 				if errSend != nil {
 					fmt.Println(errSend)
 				}
@@ -92,10 +102,10 @@ func main() {
 		}()
 
 		codec := track.Codec()
-		if codec.Name == webrtc.Opus {
+		if codec.MimeType == "audio/opus" {
 			fmt.Println("Got Opus track, saving to disk as output.opus (48 kHz, 2 channels)")
 			saveToDisk(oggFile, track)
-		} else if codec.Name == webrtc.VP8 {
+		} else if codec.MimeType == "video/VP8" {
 			fmt.Println("Got VP8 track, saving to disk as output.ivf")
 			saveToDisk(ivfFile, track)
 		}
diff --git a/examples/simulcast/main.go b/examples/simulcast/main.go
index b5629ab6..630a5850 100644
--- a/examples/simulcast/main.go
+++ b/examples/simulcast/main.go
@@ -4,12 +4,9 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"net/url"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/rtcp"
-	"github.com/pion/sdp/v3"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/examples/internal/signal"
 )
@@ -17,46 +14,6 @@ import (
 func main() {
 	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
 
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it. Since we are echoing their RTP packet
-	// back to them we are actually codec agnostic - we can accept all their codecs. This also ensures that we use the
-	// dynamic media type from the sender in our answer.
-	mediaEngine := webrtc.MediaEngine{}
-
-	// Add codecs to the mediaEngine. Note that even though we are only going to echo back the sender's video we also
-	// add audio codecs. This is because createAnswer will create an audioTransceiver and associated SDP and we currently
-	// cannot tell it not to. The audio SDP must match the sender's codecs too...
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
-		panic(err)
-	}
-
-	videoCodecs := mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo)
-	if len(videoCodecs) == 0 {
-		panic("Offer contained no video codecs")
-	}
-
-	// Configure required extensions
-
-	sdes, _ := url.Parse(sdp.SDESRTPStreamIDURI)
-	sdedMid, _ := url.Parse(sdp.SDESMidURI)
-	exts := []sdp.ExtMap{
-		{
-			URI: sdes,
-		},
-		{
-			URI: sdedMid,
-		},
-	}
-
-	se := webrtc.SettingEngine{}
-	se.AddSDPExtensions(webrtc.SDPSectionVideo, exts)
-
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithSettingEngine((se)))
-
 	// Prepare the configuration
 	config := webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
@@ -66,27 +23,27 @@ func main() {
 		},
 	}
 	// Create a new RTCPeerConnection
-	peerConnection, err := api.NewPeerConnection(config)
+	peerConnection, err := webrtc.NewPeerConnection(config)
 	if err != nil {
 		panic(err)
 	}
 
-	outputTracks := map[string]*webrtc.Track{}
+	outputTracks := map[string]*webrtc.TrackLocalStaticRTP{}
 
 	// Create Track that we send video back to browser on
-	outputTrack, err := peerConnection.NewTrack(videoCodecs[0].PayloadType, randutil.NewMathRandomGenerator().Uint32(), "video_q", "pion_q")
+	outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video_q", "pion_q")
 	if err != nil {
 		panic(err)
 	}
 	outputTracks["q"] = outputTrack
 
-	outputTrack, err = peerConnection.NewTrack(videoCodecs[0].PayloadType, randutil.NewMathRandomGenerator().Uint32(), "video_h", "pion_h")
+	outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video_h", "pion_h")
 	if err != nil {
 		panic(err)
 	}
 	outputTracks["h"] = outputTrack
 
-	outputTrack, err = peerConnection.NewTrack(videoCodecs[0].PayloadType, randutil.NewMathRandomGenerator().Uint32(), "video_f", "pion_f")
+	outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video_f", "pion_f")
 	if err != nil {
 		panic(err)
 	}
@@ -103,12 +60,16 @@ func main() {
 		panic(err)
 	}
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	if err = peerConnection.SetRemoteDescription(offer); err != nil {
 		panic(err)
 	}
 
 	// Set a handler for when a new remote track starts
-	peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
+	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
 		fmt.Println("Track has started")
 
 		// Start reading from all the streams and sending them to the related output track
@@ -117,12 +78,12 @@ func main() {
 			ticker := time.NewTicker(3 * time.Second)
 			for range ticker.C {
 				fmt.Printf("Sending pli for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC())
-				if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}}); writeErr != nil {
+				if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil {
 					fmt.Println(writeErr)
 				}
 				// Send a remb message with a very high bandwidth to trigger chrome to send also the high bitrate stream
 				fmt.Printf("Sending remb for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC())
-				if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.ReceiverEstimatedMaximumBitrate{Bitrate: 10000000, SenderSSRC: track.SSRC()}}); writeErr != nil {
+				if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.ReceiverEstimatedMaximumBitrate{Bitrate: 10000000, SenderSSRC: uint32(track.SSRC())}}); writeErr != nil {
 					fmt.Println(writeErr)
 				}
 			}
@@ -134,8 +95,6 @@ func main() {
 				panic(readErr)
 			}
 
-			packet.SSRC = outputTracks[rid].SSRC()
-
 			if writeErr := outputTracks[rid].WriteRTP(packet); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) {
 				panic(writeErr)
 			}
diff --git a/examples/swap-tracks/main.go b/examples/swap-tracks/main.go
index 15d6d394..f437aaa6 100644
--- a/examples/swap-tracks/main.go
+++ b/examples/swap-tracks/main.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/rtcp"
 	"github.com/pion/rtp"
 	"github.com/pion/webrtc/v3"
@@ -16,30 +15,6 @@ import (
 func main() { // nolint:gocognit
 	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
 
-	// Wait for the offer to be pasted
-	offer := webrtc.SessionDescription{}
-	signal.Decode(signal.MustReadStdin(), &offer)
-
-	// We make our own mediaEngine so we can place the sender's codecs in it. Since we are echoing their RTP packet
-	// back to them we are actually codec agnostic - we can accept all their codecs. This also ensures that we use the
-	// dynamic media type from the sender in our answer.
-	mediaEngine := webrtc.MediaEngine{}
-
-	// Add codecs to the mediaEngine. Note that even though we are only going to echo back the sender's video we also
-	// add audio codecs. This is because createAnswer will create an audioTransceiver and associated SDP and we currently
-	// cannot tell it not to. The audio SDP must match the sender's codecs too...
-	err := mediaEngine.PopulateFromSDP(offer)
-	if err != nil {
-		panic(err)
-	}
-
-	videoCodecs := mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo)
-	if len(videoCodecs) == 0 {
-		panic("Offer contained no video codecs")
-	}
-
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-
 	// Prepare the configuration
 	config := webrtc.Configuration{
 		ICEServers: []webrtc.ICEServer{
@@ -49,13 +24,13 @@ func main() { // nolint:gocognit
 		},
 	}
 	// Create a new RTCPeerConnection
-	peerConnection, err := api.NewPeerConnection(config)
+	peerConnection, err := webrtc.NewPeerConnection(config)
 	if err != nil {
 		panic(err)
 	}
 
 	// Create Track that we send video back to browser on
-	outputTrack, err := peerConnection.NewTrack(videoCodecs[0].PayloadType, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	if err != nil {
 		panic(err)
 	}
@@ -78,6 +53,10 @@ func main() { // nolint:gocognit
 		panic(err)
 	}
 
+	// Wait for the offer to be pasted
+	offer := webrtc.SessionDescription{}
+	signal.Decode(signal.MustReadStdin(), &offer)
+
 	// Set the remote SessionDescription
 	err = peerConnection.SetRemoteDescription(offer)
 	if err != nil {
@@ -92,8 +71,8 @@ func main() { // nolint:gocognit
 	packets := make(chan *rtp.Packet, 60)
 
 	// Set a handler for when a new remote track starts
-	peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
-		fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().Name)
+	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
+		fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType)
 		trackNum := trackCount
 		trackCount++
 		// The last timestamp so that we can change the packet to only be the delta
@@ -123,7 +102,7 @@ func main() { // nolint:gocognit
 				// If just switched to this track, send PLI to get picture refresh
 				if !isCurrTrack {
 					isCurrTrack = true
-					if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}}); writeErr != nil {
+					if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil {
 						fmt.Println(writeErr)
 					}
 				}
@@ -170,8 +149,6 @@ func main() { // nolint:gocognit
 			// Timestamp on the packet is really a diff, so add it to current
 			currTimestamp += packet.Timestamp
 			packet.Timestamp = currTimestamp
-			// Set the output SSRC
-			packet.SSRC = outputTrack.SSRC()
 			// Keep an increasing sequence number
 			packet.SequenceNumber = i
 			// Write out the packet, ignoring closed pipe if nobody is listening
diff --git a/mediaengine.go b/mediaengine.go
index cf080603..14a27620 100644
--- a/mediaengine.go
+++ b/mediaengine.go
@@ -13,422 +13,420 @@ import (
 	"github.com/pion/sdp/v3"
 )
 
-// PayloadTypes for the default codecs
 const (
-	DefaultPayloadTypePCMU = 0
-	DefaultPayloadTypePCMA = 8
-	DefaultPayloadTypeG722 = 9
-	DefaultPayloadTypeOpus = 111
-	DefaultPayloadTypeVP8  = 96
-	DefaultPayloadTypeVP9  = 98
-	DefaultPayloadTypeH264 = 102
-
-	mediaNameAudio = "audio"
-	mediaNameVideo = "video"
+	mimeTypeH264 = "video/h264"
+	mimeTypeOpus = "audio/opus"
+	mimeTypeVP8  = "video/vp8"
+	mimeTypeVP9  = "video/vp9"
+	mimeTypeG722 = "audio/G722"
+	mimeTypePCMU = "audio/PCMU"
+	mimeTypePCMA = "audio/PCMA"
 )
 
-// A MediaEngine defines the codecs supported by a PeerConnection.
-// MediaEngines populated using RegisterCodec (and RegisterDefaultCodecs)
-// may be set up once and reused, including concurrently,
-// as long as no other codecs are added subsequently.
-// MediaEngines populated using PopulateFromSDP should be used
-// only for that session.
-type MediaEngine struct {
-	codecs []*RTPCodec
+type mediaEngineHeaderExtension struct {
+	uri              string
+	isAudio, isVideo bool
 }
 
-// RegisterCodec adds codec to m.
-// RegisterCodec is not safe for concurrent use.
-func (m *MediaEngine) RegisterCodec(codec *RTPCodec) uint8 {
-	// nolint:godox
-	// TODO: dynamically generate a payload type in the range 96-127 if one wasn't provided.
-	// See https://github.com/pion/webrtc/issues/43
-	m.codecs = append(m.codecs, codec)
-	return codec.PayloadType
+// A MediaEngine defines the codecs supported by a PeerConnection, and the
+// configuration of those codecs. A MediaEngine must not be shared between
+// PeerConnections.
+type MediaEngine struct {
+	// If we have attempted to negotiate a codec type yet.
+	negotiatedVideo, negotiatedAudio bool
+
+	videoCodecs, audioCodecs                     []RTPCodecParameters
+	negotiatedVideoCodecs, negotiatedAudioCodecs []RTPCodecParameters
+
+	headerExtensions           []mediaEngineHeaderExtension
+	negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension
 }
 
 // RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC.
 // RegisterDefaultCodecs is not safe for concurrent use.
-func (m *MediaEngine) RegisterDefaultCodecs() {
-	// Audio Codecs in descending order of preference
-	m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
-	m.RegisterCodec(NewRTPPCMUCodec(DefaultPayloadTypePCMU, 8000))
-	m.RegisterCodec(NewRTPPCMACodec(DefaultPayloadTypePCMA, 8000))
-	m.RegisterCodec(NewRTPG722Codec(DefaultPayloadTypeG722, 8000))
-
-	// Video Codecs in descending order of preference
-	m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
-	m.RegisterCodec(NewRTPVP9Codec(DefaultPayloadTypeVP9, 90000))
-	m.RegisterCodec(NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
-}
-
-// PopulateFromSDP finds all codecs in sd and adds them to m, using the dynamic
-// payload types and parameters from sd.
-// PopulateFromSDP is intended for use when answering a request.
-// The offerer sets the PayloadTypes for the connection.
-// PopulateFromSDP allows an answerer to properly match the PayloadTypes from the offerer.
-// A MediaEngine populated by PopulateFromSDP should be used only for a single session.
-func (m *MediaEngine) PopulateFromSDP(sd SessionDescription) error {
-	sdp := sdp.SessionDescription{}
-	if err := sdp.Unmarshal([]byte(sd.SDP)); err != nil {
-		return err
+func (m *MediaEngine) RegisterDefaultCodecs() error {
+	// Default Pion Audio Codecs
+	for _, codec := range []RTPCodecParameters{
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
+			PayloadType:        111,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeG722, 8000, 0, "", nil},
+			PayloadType:        9,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypePCMU, 8000, 0, "", nil},
+			PayloadType:        0,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypePCMA, 8000, 0, "", nil},
+			PayloadType:        8,
+		},
+	} {
+		if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil {
+			return err
+		}
 	}
 
-	for _, md := range sdp.MediaDescriptions {
-		if md.MediaName.Media != mediaNameAudio && md.MediaName.Media != mediaNameVideo {
-			continue
+	// Default Pion Audio Header Extensions
+	for _, extension := range []string{
+		"urn:ietf:params:rtp-hdrext:sdes:mid",
+		"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
+		"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
+	} {
+		if err := m.RegisterHeaderExtension(RTPHeaderExtensionCapability{extension}, RTPCodecTypeAudio); err != nil {
+			return err
 		}
+	}
 
-		for _, format := range md.MediaName.Formats {
-			pt, err := strconv.Atoi(format)
-			if err != nil {
-				return errMediaEngineParseError
-			}
+	videoRTCPFeedback := []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
+	for _, codec := range []RTPCodecParameters{
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeVP8, 90000, 0, "", videoRTCPFeedback},
+			PayloadType:        96,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
+			PayloadType:        97,
+		},
 
-			payloadType := uint8(pt)
-			payloadCodec, err := sdp.GetCodecForPayloadType(payloadType)
-			if err != nil {
-				return fmt.Errorf("%w: codec for payload type %d", errMediaEngineCodecNotFound, payloadType)
-			}
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeVP9, 90000, 0, "profile-id=0", videoRTCPFeedback},
+			PayloadType:        98,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
+			PayloadType:        99,
+		},
 
-			var codec *RTPCodec
-			switch {
-			case strings.EqualFold(payloadCodec.Name, PCMA):
-				codec = NewRTPPCMACodec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, PCMU):
-				codec = NewRTPPCMUCodec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, G722):
-				codec = NewRTPG722Codec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, Opus):
-				codec = NewRTPOpusCodec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, VP8):
-				codec = NewRTPVP8Codec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, VP9):
-				codec = NewRTPVP9Codec(payloadType, payloadCodec.ClockRate)
-			case strings.EqualFold(payloadCodec.Name, H264):
-				codec = NewRTPH264Codec(payloadType, payloadCodec.ClockRate)
-			default:
-				// ignoring other codecs
-				continue
-			}
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeVP9, 90000, 0, "profile-id=1", videoRTCPFeedback},
+			PayloadType:        100,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil},
+			PayloadType:        101,
+		},
 
-			codec.SDPFmtpLine = payloadCodec.Fmtp
-			m.RegisterCodec(codec)
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback},
+			PayloadType:        102,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil},
+			PayloadType:        121,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback},
+			PayloadType:        127,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil},
+			PayloadType:        120,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback},
+			PayloadType:        125,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=125", nil},
+			PayloadType:        107,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback},
+			PayloadType:        108,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil},
+			PayloadType:        109,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback},
+			PayloadType:        127,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil},
+			PayloadType:        120,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{mimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", videoRTCPFeedback},
+			PayloadType:        123,
+		},
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=123", nil},
+			PayloadType:        118,
+		},
+
+		{
+			RTPCodecCapability: RTPCodecCapability{"video/ulpfec", 90000, 0, "", nil},
+			PayloadType:        116,
+		},
+	} {
+		if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil {
+			return err
 		}
 	}
-	return nil
-}
 
-// GetCodecsByName returns all codecs by name that are supported by m.
-// The returned codecs should not be modified.
-func (m *MediaEngine) GetCodecsByName(codecName string) []*RTPCodec {
-	var codecs []*RTPCodec
-	for _, codec := range m.codecs {
-		if strings.EqualFold(codec.Name, codecName) {
-			codecs = append(codecs, codec)
+	// Default Pion Video Header Extensions
+	for _, extension := range []string{
+		"urn:ietf:params:rtp-hdrext:sdes:mid",
+		"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
+		"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
+	} {
+		if err := m.RegisterHeaderExtension(RTPHeaderExtensionCapability{extension}, RTPCodecTypeVideo); err != nil {
+			return err
 		}
 	}
-	return codecs
+
+	return nil
 }
 
-func (m *MediaEngine) getCodec(payloadType uint8) (*RTPCodec, error) {
-	for _, codec := range m.codecs {
-		if codec.PayloadType == payloadType {
-			return codec, nil
-		}
+// RegisterCodec adds codec to the MediaEngine
+// These are the list of codecs supported by this PeerConnection.
+// RegisterCodec is not safe for concurrent use.
+func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) error {
+	codec.statsID = fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano())
+	switch typ {
+	case RTPCodecTypeAudio:
+		m.audioCodecs = append(m.audioCodecs, codec)
+	case RTPCodecTypeVideo:
+		m.videoCodecs = append(m.videoCodecs, codec)
+	default:
+		return ErrUnknownType
 	}
-	return nil, ErrCodecNotFound
+	return nil
 }
 
-func (m *MediaEngine) getCodecSDP(sdpCodec sdp.Codec) (*RTPCodec, error) {
-	for _, codec := range m.codecs {
-		if strings.EqualFold(codec.Name, sdpCodec.Name) &&
-			codec.ClockRate == sdpCodec.ClockRate &&
-			(sdpCodec.EncodingParameters == "" ||
-				strconv.Itoa(int(codec.Channels)) == sdpCodec.EncodingParameters) &&
-			codec.SDPFmtpLine == sdpCodec.Fmtp { // pion/webrtc#43
-			return codec, nil
+// RegisterHeaderExtension adds a header extension to the MediaEngine
+// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete
+func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType) error {
+	extensionIndex := -1
+	for i := range m.headerExtensions {
+		if extension.URI == m.headerExtensions[i].uri {
+			extensionIndex = i
 		}
 	}
-	return nil, ErrCodecNotFound
-}
 
-// GetCodecsByKind returns all codecs of kind kind that are supported by m.
-// The returned codecs should not be modified.
-func (m *MediaEngine) GetCodecsByKind(kind RTPCodecType) []*RTPCodec {
-	var codecs []*RTPCodec
-	for _, codec := range m.codecs {
-		if codec.Type == kind {
-			codecs = append(codecs, codec)
-		}
+	if extensionIndex == -1 {
+		m.headerExtensions = append(m.headerExtensions, mediaEngineHeaderExtension{})
+		extensionIndex = len(m.headerExtensions) - 1
 	}
-	return codecs
-}
 
-// Names for the default codecs supported by Pion WebRTC
-const (
-	PCMU = "PCMU"
-	PCMA = "PCMA"
-	G722 = "G722"
-	Opus = "opus"
-	VP8  = "VP8"
-	VP9  = "VP9"
-	H264 = "H264"
-)
+	if typ == RTPCodecTypeAudio {
+		m.headerExtensions[extensionIndex].isAudio = true
+	} else if typ == RTPCodecTypeVideo {
+		m.headerExtensions[extensionIndex].isVideo = true
+	}
 
-// NewRTPPCMUCodec is a helper to create a PCMU codec
-func NewRTPPCMUCodec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeAudio,
-		PCMU,
-		clockrate,
-		0,
-		"",
-		payloadType,
-		&codecs.G711Payloader{})
-	return c
-}
+	m.headerExtensions[extensionIndex].uri = extension.URI
 
-// NewRTPPCMACodec is a helper to create a PCMA codec
-func NewRTPPCMACodec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeAudio,
-		PCMA,
-		clockrate,
-		0,
-		"",
-		payloadType,
-		&codecs.G711Payloader{})
-	return c
+	return nil
 }
 
-// NewRTPG722Codec is a helper to create a G722 codec
-func NewRTPG722Codec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeAudio,
-		G722,
-		clockrate,
-		0,
-		"",
-		payloadType,
-		&codecs.G722Payloader{})
-	return c
-}
+// GetHeaderExtensionID returns the negotiated ID for a header extension.
+// If the Header Extension isn't enabled ok will be false
+func (m *MediaEngine) GetHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
+	if m.negotiatedHeaderExtensions == nil {
+		m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
+	}
 
-// NewRTPOpusCodec is a helper to create an Opus codec
-func NewRTPOpusCodec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeAudio,
-		Opus,
-		clockrate,
-		2, // According to RFC7587, Opus RTP streams must have exactly 2 channels.
-		"minptime=10;useinbandfec=1",
-		payloadType,
-		&codecs.OpusPayloader{})
-	return c
-}
+	for id, h := range m.negotiatedHeaderExtensions {
+		if extension.URI == h.uri {
+			return id, h.isAudio, h.isVideo
+		}
+	}
 
-// NewRTPVP8Codec is a helper to create an VP8 codec
-func NewRTPVP8Codec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeVideo,
-		VP8,
-		clockrate,
-		0,
-		"",
-		payloadType,
-		&codecs.VP8Payloader{})
-	return c
+	return
 }
 
-// NewRTPVP8CodecExt is a helper to create an VP8 codec
-func NewRTPVP8CodecExt(payloadType uint8, clockrate uint32, rtcpfb []RTCPFeedback, fmtp string) *RTPCodec {
-	c := NewRTPCodecExt(RTPCodecTypeVideo,
-		VP8,
-		clockrate,
-		0,
-		fmtp,
-		payloadType,
-		rtcpfb,
-		&codecs.VP8Payloader{})
-	return c
-}
+func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, error) {
+	for _, codec := range m.negotiatedVideoCodecs {
+		if codec.PayloadType == payloadType {
+			return codec, nil
+		}
+	}
+	for _, codec := range m.negotiatedAudioCodecs {
+		if codec.PayloadType == payloadType {
+			return codec, nil
+		}
+	}
 
-// NewRTPVP9Codec is a helper to create an VP9 codec
-func NewRTPVP9Codec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeVideo,
-		VP9,
-		clockrate,
-		0,
-		"",
-		payloadType,
-		&codecs.VP9Payloader{})
-	return c
+	return RTPCodecParameters{}, ErrCodecNotFound
 }
 
-// NewRTPVP9CodecExt is a helper to create an VP8 codec
-func NewRTPVP9CodecExt(payloadType uint8, clockrate uint32, rtcpfb []RTCPFeedback, fmtp string) *RTPCodec {
-	c := NewRTPCodecExt(RTPCodecTypeVideo,
-		VP9,
-		clockrate,
-		0,
-		fmtp,
-		payloadType,
-		rtcpfb,
-		&codecs.VP9Payloader{})
-	return c
-}
+func (m *MediaEngine) collectStats(collector *statsReportCollector) {
+	statsLoop := func(codecs []RTPCodecParameters) {
+		for _, codec := range codecs {
+			collector.Collecting()
+			stats := CodecStats{
+				Timestamp:   statsTimestampFrom(time.Now()),
+				Type:        StatsTypeCodec,
+				ID:          codec.statsID,
+				PayloadType: codec.PayloadType,
+				MimeType:    codec.MimeType,
+				ClockRate:   codec.ClockRate,
+				Channels:    uint8(codec.Channels),
+				SDPFmtpLine: codec.SDPFmtpLine,
+			}
 
-// NewRTPH264Codec is a helper to create an H264 codec
-func NewRTPH264Codec(payloadType uint8, clockrate uint32) *RTPCodec {
-	c := NewRTPCodec(RTPCodecTypeVideo,
-		H264,
-		clockrate,
-		0,
-		"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
-		payloadType,
-		&codecs.H264Payloader{})
-	return c
-}
+			collector.Collect(stats.ID, stats)
+		}
+	}
 
-// NewRTPH264CodecExt is a helper to create an H264 codec
-func NewRTPH264CodecExt(payloadType uint8, clockrate uint32, rtcpfb []RTCPFeedback, fmtp string) *RTPCodec {
-	c := NewRTPCodecExt(RTPCodecTypeVideo,
-		H264,
-		clockrate,
-		0,
-		fmtp,
-		payloadType,
-		rtcpfb,
-		&codecs.H264Payloader{})
-	return c
+	statsLoop(m.videoCodecs)
+	statsLoop(m.audioCodecs)
 }
 
-// RTPCodecType determines the type of a codec
-type RTPCodecType int
+// Look up a codec and enable if it exists
+func (m *MediaEngine) updateCodecParameters(remoteCodec RTPCodecParameters, typ RTPCodecType) error {
+	codecs := m.videoCodecs
+	if typ == RTPCodecTypeAudio {
+		codecs = m.audioCodecs
+	}
 
-const (
+	pushCodec := func(codec RTPCodecParameters) error {
+		if typ == RTPCodecTypeAudio {
+			m.negotiatedAudioCodecs = append(m.negotiatedAudioCodecs, codec)
+		} else if typ == RTPCodecTypeVideo {
+			m.negotiatedVideoCodecs = append(m.negotiatedVideoCodecs, codec)
+		}
+		return nil
+	}
 
-	// RTPCodecTypeAudio indicates this is an audio codec
-	RTPCodecTypeAudio RTPCodecType = iota + 1
+	if strings.HasPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt=") {
+		payloadType, err := strconv.Atoi(strings.TrimPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt="))
+		if err != nil {
+			return err
+		}
 
-	// RTPCodecTypeVideo indicates this is a video codec
-	RTPCodecTypeVideo
-)
+		if _, err = m.getCodecByPayload(PayloadType(payloadType)); err != nil {
+			return nil // not an error, we just ignore this codec we don't support
+		}
+	}
 
-func (t RTPCodecType) String() string {
-	switch t {
-	case RTPCodecTypeAudio:
-		return "audio"
-	case RTPCodecTypeVideo:
-		return "video"
-	default:
-		return ErrUnknownType.Error()
+	if _, err := codecParametersFuzzySearch(remoteCodec, codecs); err == nil {
+		return pushCodec(remoteCodec)
 	}
+
+	return nil
 }
 
-// NewRTPCodecType creates a RTPCodecType from a string
-func NewRTPCodecType(r string) RTPCodecType {
-	switch {
-	case strings.EqualFold(r, "audio"):
-		return RTPCodecTypeAudio
-	case strings.EqualFold(r, "video"):
-		return RTPCodecTypeVideo
-	default:
-		return RTPCodecType(0)
+// Look up a header extension and enable if it exists
+func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error {
+	if m.negotiatedHeaderExtensions == nil {
+		m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
 	}
-}
 
-// RTPCodec represents a codec supported by the PeerConnection
-type RTPCodec struct {
-	RTPCodecCapability
-	Type        RTPCodecType
-	Name        string
-	PayloadType uint8
-	Payloader   rtp.Payloader
-	statsID     string
-}
+	for _, localExtension := range m.headerExtensions {
+		if localExtension.uri == extension {
+			h := mediaEngineHeaderExtension{uri: extension}
+			if existingValue, ok := m.negotiatedHeaderExtensions[id]; ok {
+				h = existingValue
+			}
 
-// NewRTPCodec is used to define a new codec
-func NewRTPCodec(
-	codecType RTPCodecType,
-	name string,
-	clockrate uint32,
-	channels uint16,
-	fmtp string,
-	payloadType uint8,
-	payloader rtp.Payloader,
-) *RTPCodec {
-	return &RTPCodec{
-		RTPCodecCapability: RTPCodecCapability{
-			MimeType:    codecType.String() + "/" + name,
-			ClockRate:   clockrate,
-			Channels:    channels,
-			SDPFmtpLine: fmtp,
-		},
-		PayloadType: payloadType,
-		Payloader:   payloader,
-		Type:        codecType,
-		Name:        name,
-		statsID:     fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano()),
+			switch {
+			case localExtension.isAudio && typ == RTPCodecTypeAudio:
+				h.isAudio = true
+			case localExtension.isVideo && typ == RTPCodecTypeVideo:
+				h.isVideo = true
+			}
+
+			m.negotiatedHeaderExtensions[id] = h
+		}
 	}
+	return nil
 }
 
-// NewRTPCodecExt is used to define a new codec
-func NewRTPCodecExt(
-	codecType RTPCodecType,
-	name string,
-	clockrate uint32,
-	channels uint16,
-	fmtp string,
-	payloadType uint8,
-	rtcpfb []RTCPFeedback,
-	payloader rtp.Payloader,
-) *RTPCodec {
-	return &RTPCodec{
-		RTPCodecCapability: RTPCodecCapability{
-			MimeType:     codecType.String() + "/" + name,
-			ClockRate:    clockrate,
-			Channels:     channels,
-			SDPFmtpLine:  fmtp,
-			RTCPFeedback: rtcpfb,
-		},
-		PayloadType: payloadType,
-		Payloader:   payloader,
-		Type:        codecType,
-		Name:        name,
+// Update the MediaEngine from a remote description
+func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error {
+	for _, media := range desc.MediaDescriptions {
+		var typ RTPCodecType
+		switch {
+		case !m.negotiatedAudio && strings.EqualFold(media.MediaName.Media, "audio"):
+			m.negotiatedAudio = true
+			typ = RTPCodecTypeAudio
+		case !m.negotiatedVideo && strings.EqualFold(media.MediaName.Media, "video"):
+			m.negotiatedVideo = true
+			typ = RTPCodecTypeVideo
+		default:
+			continue
+		}
+
+		codecs, err := codecsFromMediaDescription(media)
+		if err != nil {
+			return err
+		}
+
+		for _, codec := range codecs {
+			if err = m.updateCodecParameters(codec, typ); err != nil {
+				return err
+			}
+		}
+
+		extensions, err := rtpExtensionsFromMediaDescription(media)
+		if err != nil {
+			return err
+		}
+
+		for id, extension := range extensions {
+			if err = m.updateHeaderExtension(extension, id, typ); err != nil {
+				return err
+			}
+		}
 	}
+	return nil
 }
 
-// RTPCodecCapability provides information about codec capabilities.
-type RTPCodecCapability struct {
-	MimeType     string
-	ClockRate    uint32
-	Channels     uint16
-	SDPFmtpLine  string
-	RTCPFeedback []RTCPFeedback
-}
+func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
+	if typ == RTPCodecTypeVideo {
+		if m.negotiatedVideo {
+			return m.negotiatedVideoCodecs
+		}
 
-// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec.
-type RTPHeaderExtensionCapability struct {
-	URI string
-}
+		return m.videoCodecs
+	} else if typ == RTPCodecTypeAudio {
+		if m.negotiatedAudio {
+			return m.negotiatedAudioCodecs
+		}
 
-// RTPCapabilities represents the capabilities of a transceiver
-type RTPCapabilities struct {
-	Codecs           []RTPCodecCapability
-	HeaderExtensions []RTPHeaderExtensionCapability
+		return m.audioCodecs
+	}
+
+	return nil
 }
 
-func (m *MediaEngine) collectStats(collector *statsReportCollector) {
-	for _, codec := range m.codecs {
-		collector.Collecting()
-		stats := CodecStats{
-			Timestamp:   statsTimestampFrom(time.Now()),
-			Type:        StatsTypeCodec,
-			ID:          codec.statsID,
-			PayloadType: codec.PayloadType,
-			MimeType:    codec.MimeType,
-			ClockRate:   codec.ClockRate,
-			Channels:    uint8(codec.Channels),
-			SDPFmtpLine: codec.SDPFmtpLine,
+func (m *MediaEngine) negotiatedHeaderExtensionsForType(typ RTPCodecType) map[int]mediaEngineHeaderExtension {
+	headerExtensions := map[int]mediaEngineHeaderExtension{}
+	for id, e := range m.negotiatedHeaderExtensions {
+		if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo {
+			headerExtensions[id] = e
 		}
+	}
+
+	return headerExtensions
+}
 
-		collector.Collect(stats.ID, stats)
+func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) {
+	switch strings.ToLower(codec.MimeType) {
+	case mimeTypeH264:
+		return &codecs.H264Payloader{}, nil
+	case mimeTypeOpus:
+		return &codecs.OpusPayloader{}, nil
+	case mimeTypeVP8:
+		return &codecs.VP8Payloader{}, nil
+	case mimeTypeVP9:
+		return &codecs.VP9Payloader{}, nil
+	case mimeTypeG722:
+		return &codecs.G722Payloader{}, nil
+	case mimeTypePCMU, mimeTypePCMA:
+		return &codecs.G711Payloader{}, nil
+	default:
+		return nil, ErrNoPayloaderForCodec
 	}
 }
diff --git a/peerconnection.go b/peerconnection.go
index 435b666c..5cee2b8a 100644
--- a/peerconnection.go
+++ b/peerconnection.go
@@ -1,6 +1,5 @@
 // +build !js
 
-// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
 package webrtc
 
 import (
@@ -65,7 +64,7 @@ type PeerConnection struct {
 	onSignalingStateChangeHandler     func(SignalingState)
 	onICEConnectionStateChangeHandler func(ICEConnectionState)
 	onConnectionStateChangeHandler    func(PeerConnectionState)
-	onTrackHandler                    func(*Track, *RTPReceiver)
+	onTrackHandler                    func(*TrackRemote, *RTPReceiver)
 	onDataChannelHandler              func(*DataChannel)
 	onNegotiationNeededHandler        atomic.Value // func()
 
@@ -83,7 +82,9 @@ type PeerConnection struct {
 // codecs. See API.NewPeerConnection for details.
 func NewPeerConnection(configuration Configuration) (*PeerConnection, error) {
 	m := MediaEngine{}
-	m.RegisterDefaultCodecs()
+	if err := m.RegisterDefaultCodecs(); err != nil {
+		return nil, err
+	}
 	api := NewAPI(WithMediaEngine(m))
 	return api.NewPeerConnection(configuration)
 }
@@ -353,7 +354,7 @@ func (pc *PeerConnection) checkNegotiationNeeded() bool { //nolint:gocognit
 			// Step 5.3.1
 			if t.Direction() == RTPTransceiverDirectionSendrecv || t.Direction() == RTPTransceiverDirectionSendonly {
 				descMsid, okMsid := m.Attribute(sdp.AttrKeyMsid)
-				if !okMsid || descMsid != t.Sender().Track().Msid() {
+				if !okMsid || descMsid != t.Sender().Track().StreamID() {
 					return true
 				}
 			}
@@ -403,13 +404,13 @@ func (pc *PeerConnection) OnICEGatheringStateChange(f func(ICEGathererState)) {
 
 // OnTrack sets an event handler which is called when remote track
 // arrives from a remote peer.
-func (pc *PeerConnection) OnTrack(f func(*Track, *RTPReceiver)) {
+func (pc *PeerConnection) OnTrack(f func(*TrackRemote, *RTPReceiver)) {
 	pc.mu.Lock()
 	defer pc.mu.Unlock()
 	pc.onTrackHandler = f
 }
 
-func (pc *PeerConnection) onTrack(t *Track, r *RTPReceiver) {
+func (pc *PeerConnection) onTrack(t *TrackRemote, r *RTPReceiver) {
 	pc.mu.RLock()
 	handler := pc.onTrackHandler
 	pc.mu.RUnlock()
@@ -968,6 +969,10 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
 		return err
 	}
 
+	if err := pc.api.mediaEngine.updateFromRemoteDescription(*desc.parsed); err != nil {
+		return err
+	}
+
 	var t *RTPTransceiver
 	localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...)
 	detectedPlanB := descriptionIsPlanB(pc.RemoteDescription())
@@ -1099,7 +1104,7 @@ func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPRece
 	for i := range receiver.tracks {
 		receiver.tracks[i].track.mu.Lock()
 		receiver.tracks[i].track.id = incoming.id
-		receiver.tracks[i].track.label = incoming.label
+		receiver.tracks[i].track.streamID = incoming.streamID
 		receiver.tracks[i].track.mu.Unlock()
 	}
 
@@ -1114,14 +1119,14 @@ func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPRece
 			return
 		}
 
-		codec, err := pc.api.mediaEngine.getCodec(receiver.Track().PayloadType())
+		codec, err := pc.api.mediaEngine.getCodecByPayload(receiver.Track().PayloadType())
 		if err != nil {
 			pc.log.Warnf("no codec could be found for payloadType %d", receiver.Track().PayloadType())
 			return
 		}
 
 		receiver.Track().mu.Lock()
-		receiver.Track().kind = codec.Type
+		receiver.Track().kind = receiver.kind
 		receiver.Track().codec = codec
 		receiver.Track().mu.Unlock()
 
@@ -1208,8 +1213,8 @@ func (pc *PeerConnection) startRTPSenders(currentTransceivers []*RTPTransceiver)
 			err := transceiver.Sender().Send(RTPSendParameters{
 				Encodings: RTPEncodingParameters{
 					RTPCodingParameters{
-						SSRC:        transceiver.Sender().Track().SSRC(),
-						PayloadType: transceiver.Sender().Track().PayloadType(),
+						SSRC:        transceiver.Sender().ssrc,
+						PayloadType: transceiver.Sender().payloadType,
 					},
 				},
 			})
@@ -1257,7 +1262,7 @@ func (pc *PeerConnection) startSCTP() {
 	pc.sctpTransport.lock.Unlock()
 }
 
-func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc uint32) error { //nolint:gocognit
+func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc SSRC) error { //nolint:gocognit
 	remoteDescription := pc.RemoteDescription()
 	if remoteDescription == nil {
 		return errPeerConnRemoteDescriptionNil
@@ -1290,16 +1295,14 @@ func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc uint32)
 		return nil
 	}
 
-	//  Simulcast no longer uses SSRCes, but RID instead. We then use that value to populate rest of Track Data
-	matchedSDPMap, err := matchedAnswerExt(pc.RemoteDescription().parsed, pc.api.settingEngine.getSDPExtensions())
-	if err != nil {
-		return err
+	midExtensionID, audioSupported, videoSupported := pc.api.mediaEngine.GetHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
+	if !audioSupported && !videoSupported {
+		return errPeerConnSimulcastMidRTPExtensionRequired
 	}
 
-	sdesMidExtMap := getExtMapByURI(matchedSDPMap, sdp.SDESMidURI)
-	sdesStreamIDExtMap := getExtMapByURI(matchedSDPMap, sdp.SDESRTPStreamIDURI)
-	if sdesMidExtMap == nil || sdesStreamIDExtMap == nil {
-		return errPeerConnSimulcastMidAndRidRTPExtensionRequired
+	streamIDExtensionID, audioSupported, videoSupported := pc.api.mediaEngine.GetHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESRTPStreamIDURI})
+	if !audioSupported && !videoSupported {
+		return errPeerConnSimulcastStreamIDRTPExtensionRequired
 	}
 
 	b := make([]byte, receiveMTU)
@@ -1310,7 +1313,7 @@ func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc uint32)
 			return err
 		}
 
-		maybeMid, maybeRid, payloadType, err := handleUnknownRTPPacket(b[:i], sdesMidExtMap, sdesStreamIDExtMap)
+		maybeMid, maybeRid, payloadType, err := handleUnknownRTPPacket(b[:i], uint8(midExtensionID), uint8(streamIDExtensionID))
 		if err != nil {
 			return err
 		}
@@ -1326,7 +1329,7 @@ func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc uint32)
 			continue
 		}
 
-		codec, err := pc.api.mediaEngine.getCodec(payloadType)
+		codec, err := pc.api.mediaEngine.getCodecByPayload(payloadType)
 		if err != nil {
 			return err
 		}
@@ -1364,7 +1367,7 @@ func (pc *PeerConnection) undeclaredMediaProcessor() {
 				return
 			}
 
-			if err := pc.handleUndeclaredSSRC(stream, ssrc); err != nil {
+			if err := pc.handleUndeclaredSSRC(stream, SSRC(ssrc)); err != nil {
 				pc.log.Errorf("Incoming unhandled RTP ssrc(%d), OnTrack will not be fired. %v", ssrc, err)
 			}
 		}
@@ -1468,7 +1471,7 @@ func (pc *PeerConnection) GetTransceivers() []*RTPTransceiver {
 }
 
 // AddTrack adds a Track to the PeerConnection
-func (pc *PeerConnection) AddTrack(track *Track) (*RTPSender, error) {
+func (pc *PeerConnection) AddTrack(track TrackLocal) (*RTPSender, error) {
 	if pc.isClosed.get() {
 		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 	}
@@ -1503,12 +1506,6 @@ func (pc *PeerConnection) AddTrack(track *Track) (*RTPSender, error) {
 	return transceiver.Sender(), nil
 }
 
-// AddTransceiver Create a new RtpTransceiver and add it to the set of transceivers.
-// Deprecated: Use AddTrack, AddTransceiverFromKind or AddTransceiverFromTrack
-func (pc *PeerConnection) AddTransceiver(trackOrKind RTPCodecType, init ...RtpTransceiverInit) (*RTPTransceiver, error) {
-	return pc.AddTransceiverFromKind(trackOrKind, init...)
-}
-
 // RemoveTrack removes a Track from the PeerConnection
 func (pc *PeerConnection) RemoveTrack(sender *RTPSender) error {
 	if pc.isClosed.get() {
@@ -1553,18 +1550,17 @@ func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RtpT
 
 	switch direction {
 	case RTPTransceiverDirectionSendrecv:
-		codecs := pc.api.mediaEngine.GetCodecsByKind(kind)
+		codecs := pc.api.mediaEngine.getCodecsByKind(kind)
 		if len(codecs) == 0 {
-			return nil, fmt.Errorf("%w: %s", errPeerConnCodecsNotFound, kind.String())
+			return nil, ErrNoCodecsAvailable
 		}
 
-		track, err := pc.NewTrack(codecs[0].PayloadType, util.RandUint32(), util.MathRandAlpha(trackDefaultIDLength), util.MathRandAlpha(trackDefaultLabelLength))
+		track, err := NewTrackLocalStaticSample(codecs[0].RTPCodecCapability, util.MathRandAlpha(16), util.MathRandAlpha(16))
 		if err != nil {
 			return nil, err
 		}
 
 		return pc.AddTransceiverFromTrack(track, init...)
-
 	case RTPTransceiverDirectionRecvonly:
 		receiver, err := pc.api.NewRTPReceiver(kind, pc.dtlsTransport)
 		if err != nil {
@@ -1587,7 +1583,7 @@ func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RtpT
 }
 
 // AddTransceiverFromTrack Create a new RtpTransceiver(SendRecv or SendOnly) and add it to the set of transceivers.
-func (pc *PeerConnection) AddTransceiverFromTrack(track *Track, init ...RtpTransceiverInit) (*RTPTransceiver, error) {
+func (pc *PeerConnection) AddTransceiverFromTrack(track TrackLocal, init ...RtpTransceiverInit) (*RTPTransceiver, error) {
 	if pc.isClosed.get() {
 		return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
 	}
@@ -1639,7 +1635,7 @@ func (pc *PeerConnection) AddTransceiverFromTrack(track *Track, init ...RtpTrans
 
 		return t, nil
 	default:
-		return nil, errPeerConnAddTransceiverFromTrackOneTransceiver
+		return nil, errPeerConnAddTransceiverFromTrackSupport
 	}
 }
 
@@ -1806,18 +1802,6 @@ func (pc *PeerConnection) Close() error {
 	return util.FlattenErrs(closeErrs)
 }
 
-// NewTrack Creates a new Track
-func (pc *PeerConnection) NewTrack(payloadType uint8, ssrc uint32, id, label string) (*Track, error) {
-	codec, err := pc.api.mediaEngine.getCodec(payloadType)
-	if err != nil {
-		return nil, err
-	} else if codec.Payloader == nil {
-		return nil, errPeerConnCodecPayloaderNotSet
-	}
-
-	return NewTrack(payloadType, ssrc, id, label, codec)
-}
-
 func (pc *PeerConnection) newRTPTransceiver(
 	receiver *RTPReceiver,
 	sender *RTPSender,
@@ -2008,7 +1992,7 @@ func (pc *PeerConnection) startRTP(isRenegotiation bool, remoteDesc *SessionDesc
 
 			if details := trackDetailsForSSRC(trackDetails, ssrc); details != nil {
 				t.Receiver().Track().id = details.id
-				t.Receiver().Track().label = details.label
+				t.Receiver().Track().streamID = details.streamID
 				t.Receiver().Track().mu.Unlock()
 				continue
 			}
@@ -2040,11 +2024,6 @@ func (pc *PeerConnection) startRTP(isRenegotiation bool, remoteDesc *SessionDesc
 	}
 }
 
-// GetRegisteredRTPCodecs gets a list of registered RTPCodec from the underlying constructed MediaEngine
-func (pc *PeerConnection) GetRegisteredRTPCodecs(kind RTPCodecType) []*RTPCodec {
-	return pc.api.mediaEngine.GetCodecsByKind(kind)
-}
-
 // generateUnmatchedSDP generates an SDP that doesn't take remote state into account
 // This is used for the initial call for CreateOffer
 func (pc *PeerConnection) generateUnmatchedSDP(transceivers []*RTPTransceiver, useIdentity bool) (*sdp.SessionDescription, error) {
@@ -2109,7 +2088,7 @@ func (pc *PeerConnection) generateUnmatchedSDP(transceivers []*RTPTransceiver, u
 		return nil, err
 	}
 
-	return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState(), pc.api.settingEngine.getSDPExtensions())
+	return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState())
 }
 
 // generateMatchedSDP generates a SDP and takes the remote state into account
@@ -2227,12 +2206,7 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use
 		return nil, err
 	}
 
-	matchedSDPMap, err := matchedAnswerExt(pc.RemoteDescription().parsed, pc.api.settingEngine.getSDPExtensions())
-	if err != nil {
-		return nil, err
-	}
-
-	return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState(), matchedSDPMap)
+	return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState())
 }
 
 func (pc *PeerConnection) setGatherCompleteHandler(handler func()) {
diff --git a/pkg/media/media.go b/pkg/media/media.go
index a486390e..bcd7e338 100644
--- a/pkg/media/media.go
+++ b/pkg/media/media.go
@@ -7,17 +7,11 @@ import (
 	"github.com/pion/rtp"
 )
 
-// A Sample contains encoded media and the number of samples in that media (see NSamples).
+// A Sample contains encoded media and timing information
 type Sample struct {
-	Data    []byte
-	Samples uint32
-}
-
-// NSamples calculates the number of samples in media of length d with sampling frequency f.
-// For example, NSamples(20 * time.Millisecond, 48000) will return the number of samples
-// in a 20 millisecond segment of Opus audio recorded at 48000 samples per second.
-func NSamples(d time.Duration, freq int) uint32 {
-	return uint32(time.Duration(freq) * d / time.Second)
+	Data      []byte
+	Timestamp time.Time
+	Duration  time.Duration
 }
 
 // Writer defines an interface to handle
diff --git a/pkg/media/samplebuilder/samplebuilder.go b/pkg/media/samplebuilder/samplebuilder.go
index 97ef0182..9ddf71f0 100644
--- a/pkg/media/samplebuilder/samplebuilder.go
+++ b/pkg/media/samplebuilder/samplebuilder.go
@@ -2,6 +2,8 @@
 package samplebuilder
 
 import (
+	"time"
+
 	"github.com/pion/rtp"
 	"github.com/pion/webrtc/v3/pkg/media"
 )
@@ -14,6 +16,9 @@ type SampleBuilder struct {
 	// Interface that allows us to take RTP packets to samples
 	depacketizer rtp.Depacketizer
 
+	// sampleRate allows us to compute duration of media.SamplecA
+	sampleRate uint32
+
 	// Last seqnum that has been added to buffer
 	lastPush uint16
 
@@ -34,8 +39,8 @@ type SampleBuilder struct {
 // A large maxLate will result in less packet loss but higher latency.
 // The depacketizer extracts media samples from RTP packets.
 // Several depacketizers are available in package github.com/pion/rtp/codecs.
-func New(maxLate uint16, depacketizer rtp.Depacketizer, opts ...Option) *SampleBuilder {
-	s := &SampleBuilder{maxLate: maxLate, depacketizer: depacketizer}
+func New(maxLate uint16, depacketizer rtp.Depacketizer, sampleRate uint32, opts ...Option) *SampleBuilder {
+	s := &SampleBuilder{maxLate: maxLate, depacketizer: depacketizer, sampleRate: sampleRate}
 	for _, o := range opts {
 		o(s)
 	}
@@ -84,7 +89,8 @@ func (s *SampleBuilder) buildSample(firstBuffer uint16) (*media.Sample, uint32)
 			for j := firstBuffer; j < i; j++ {
 				s.buffer[j] = nil
 			}
-			return &media.Sample{Data: data, Samples: samples}, s.lastPopTimestamp
+
+			return &media.Sample{Data: data, Duration: time.Duration((samples/s.sampleRate)*1000) * time.Millisecond}, s.lastPopTimestamp
 		}
 
 		p, err := s.depacketizer.Unmarshal(s.buffer[i].Payload)
diff --git a/rtpcapabilities.go b/rtpcapabilities.go
new file mode 100644
index 00000000..6e018822
--- /dev/null
+++ b/rtpcapabilities.go
@@ -0,0 +1,7 @@
+package webrtc
+
+// RTPCapabilities represents the capabilities of a transceiver
+type RTPCapabilities struct {
+	Codecs           []RTPCodecCapability
+	HeaderExtensions []RTPHeaderExtensionCapability
+}
diff --git a/rtpcodec.go b/rtpcodec.go
new file mode 100644
index 00000000..c03abd13
--- /dev/null
+++ b/rtpcodec.go
@@ -0,0 +1,99 @@
+package webrtc
+
+import (
+	"strings"
+)
+
+// RTPCodecType determines the type of a codec
+type RTPCodecType int
+
+const (
+
+	// RTPCodecTypeAudio indicates this is an audio codec
+	RTPCodecTypeAudio RTPCodecType = iota + 1
+
+	// RTPCodecTypeVideo indicates this is a video codec
+	RTPCodecTypeVideo
+)
+
+func (t RTPCodecType) String() string {
+	switch t {
+	case RTPCodecTypeAudio:
+		return "audio"
+	case RTPCodecTypeVideo:
+		return "video" //nolint: goconst
+	default:
+		return ErrUnknownType.Error()
+	}
+}
+
+// NewRTPCodecType creates a RTPCodecType from a string
+func NewRTPCodecType(r string) RTPCodecType {
+	switch {
+	case strings.EqualFold(r, RTPCodecTypeAudio.String()):
+		return RTPCodecTypeAudio
+	case strings.EqualFold(r, RTPCodecTypeVideo.String()):
+		return RTPCodecTypeVideo
+	default:
+		return RTPCodecType(0)
+	}
+}
+
+// RTPCodecCapability provides information about codec capabilities.
+//
+// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpcodeccapability-members
+type RTPCodecCapability struct {
+	MimeType     string
+	ClockRate    uint32
+	Channels     uint16
+	SDPFmtpLine  string
+	RTCPFeedback []RTCPFeedback
+}
+
+// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec.
+//
+// https://w3c.github.io/webrtc-pc/#dom-rtcrtpcapabilities-headerextensions
+type RTPHeaderExtensionCapability struct {
+	URI string
+}
+
+// RTPCodecParameters is a sequence containing the media codecs that an RtpSender
+// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also
+// includes the PayloadType that has been negotiated
+//
+// https://w3c.github.io/webrtc-pc/#rtcrtpcodecparameters
+type RTPCodecParameters struct {
+	RTPCodecCapability
+	PayloadType PayloadType
+
+	statsID string
+}
+
+// RTCRtpCapabilities is a list of supported codecs and header extensions
+//
+// https://w3c.github.io/webrtc-pc/#rtcrtpcapabilities
+type RTCRtpCapabilities struct {
+	HeaderExtensions []RTPHeaderExtensionCapability
+	Codecs           []RTPCodecCapability
+}
+
+// Do a fuzzy find for a codec in the list of codecs
+// Used for lookup up a codec in an existing list to find a match
+func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecParameters) (RTPCodecParameters, error) {
+	// First attempt to match on MimeType + SDPFmtpLine
+	for _, c := range haystack {
+		if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
+			c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine {
+			return c, nil
+		}
+	}
+
+	// Fallback to just MimeType
+	for _, c := range haystack {
+		if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
+			return c, nil
+		}
+	}
+
+	return RTPCodecParameters{}, ErrCodecNotFound
+}
diff --git a/rtpcodingparameters.go b/rtpcodingparameters.go
index 1c296571..8a08c332 100644
--- a/rtpcodingparameters.go
+++ b/rtpcodingparameters.go
@@ -4,7 +4,7 @@ package webrtc
 // This is a subset of the RFC since Pion WebRTC doesn't implement encoding/decoding itself
 // http://draft.ortc.org/#dom-rtcrtpcodingparameters
 type RTPCodingParameters struct {
-	RID         string `json:"rid"`
-	SSRC        uint32 `json:"ssrc"`
-	PayloadType uint8  `json:"payloadType"`
+	RID         string      `json:"rid"`
+	SSRC        SSRC        `json:"ssrc"`
+	PayloadType PayloadType `json:"payloadType"`
 }
diff --git a/rtpreceiver.go b/rtpreceiver.go
index b5bfe96d..1b37f624 100644
--- a/rtpreceiver.go
+++ b/rtpreceiver.go
@@ -14,12 +14,12 @@ import (
 // trackStreams maintains a mapping of RTP/RTCP streams to a specific track
 // a RTPReceiver may contain multiple streams if we are dealing with Multicast
 type trackStreams struct {
-	track          *Track
+	track          *TrackRemote
 	rtpReadStream  *srtp.ReadStreamSRTP
 	rtcpReadStream *srtp.ReadStreamSRTCP
 }
 
-// RTPReceiver allows an application to inspect the receipt of a Track
+// RTPReceiver allows an application to inspect the receipt of a TrackRemote
 type RTPReceiver struct {
 	kind      RTPCodecType
 	transport *DTLSTransport
@@ -57,8 +57,8 @@ func (r *RTPReceiver) Transport() *DTLSTransport {
 	return r.transport
 }
 
-// Track returns the RtpTransceiver track
-func (r *RTPReceiver) Track() *Track {
+// Track returns the RtpTransceiver TrackRemote
+func (r *RTPReceiver) Track() *TrackRemote {
 	r.mu.RLock()
 	defer r.mu.RUnlock()
 
@@ -70,11 +70,11 @@ func (r *RTPReceiver) Track() *Track {
 
 // Tracks returns the RtpTransceiver tracks
 // A RTPReceiver to support Simulcast may now have multiple tracks
-func (r *RTPReceiver) Tracks() []*Track {
+func (r *RTPReceiver) Tracks() []*TrackRemote {
 	r.mu.RLock()
 	defer r.mu.RUnlock()
 
-	var tracks []*Track
+	var tracks []*TrackRemote
 	for i := range r.tracks {
 		tracks = append(tracks, r.tracks[i].track)
 	}
@@ -94,7 +94,7 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
 
 	if len(parameters.Encodings) == 1 && parameters.Encodings[0].SSRC != 0 {
 		t := trackStreams{
-			track: &Track{
+			track: &TrackRemote{
 				kind:     r.kind,
 				ssrc:     parameters.Encodings[0].SSRC,
 				receiver: r,
@@ -111,7 +111,7 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error {
 	} else {
 		for _, encoding := range parameters.Encodings {
 			r.tracks = append(r.tracks, trackStreams{
-				track: &Track{
+				track: &TrackRemote{
 					kind:     r.kind,
 					rid:      encoding.RID,
 					receiver: r,
@@ -211,7 +211,7 @@ func (r *RTPReceiver) Stop() error {
 	return nil
 }
 
-func (r *RTPReceiver) streamsForTrack(t *Track) *trackStreams {
+func (r *RTPReceiver) streamsForTrack(t *TrackRemote) *trackStreams {
 	for i := range r.tracks {
 		if r.tracks[i].track == t {
 			return &r.tracks[i]
@@ -221,7 +221,7 @@ func (r *RTPReceiver) streamsForTrack(t *Track) *trackStreams {
 }
 
 // readRTP should only be called by a track, this only exists so we can keep state in one place
-func (r *RTPReceiver) readRTP(b []byte, reader *Track) (n int, err error) {
+func (r *RTPReceiver) readRTP(b []byte, reader *TrackRemote) (n int, err error) {
 	<-r.received
 	if t := r.streamsForTrack(reader); t != nil {
 		return t.rtpReadStream.Read(b)
@@ -232,14 +232,14 @@ func (r *RTPReceiver) readRTP(b []byte, reader *Track) (n int, err error) {
 
 // receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs
 // It populates all the internal state for the given RID
-func (r *RTPReceiver) receiveForRid(rid string, codec *RTPCodec, ssrc uint32) (*Track, error) {
+func (r *RTPReceiver) receiveForRid(rid string, codec RTPCodecParameters, ssrc SSRC) (*TrackRemote, error) {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 
 	for i := range r.tracks {
 		if r.tracks[i].track.RID() == rid {
 			r.tracks[i].track.mu.Lock()
-			r.tracks[i].track.kind = codec.Type
+			r.tracks[i].track.kind = r.kind
 			r.tracks[i].track.codec = codec
 			r.tracks[i].track.ssrc = ssrc
 			r.tracks[i].track.mu.Unlock()
@@ -257,13 +257,13 @@ func (r *RTPReceiver) receiveForRid(rid string, codec *RTPCodec, ssrc uint32) (*
 	return nil, fmt.Errorf("%w: %d", errRTPReceiverForSSRCTrackStreamNotFound, ssrc)
 }
 
-func (r *RTPReceiver) streamsForSSRC(ssrc uint32) (*srtp.ReadStreamSRTP, *srtp.ReadStreamSRTCP, error) {
+func (r *RTPReceiver) streamsForSSRC(ssrc SSRC) (*srtp.ReadStreamSRTP, *srtp.ReadStreamSRTCP, error) {
 	srtpSession, err := r.transport.getSRTPSession()
 	if err != nil {
 		return nil, nil, err
 	}
 
-	rtpReadStream, err := srtpSession.OpenReadStream(ssrc)
+	rtpReadStream, err := srtpSession.OpenReadStream(uint32(ssrc))
 	if err != nil {
 		return nil, nil, err
 	}
@@ -273,7 +273,7 @@ func (r *RTPReceiver) streamsForSSRC(ssrc uint32) (*srtp.ReadStreamSRTP, *srtp.R
 		return nil, nil, err
 	}
 
-	rtcpReadStream, err := srtcpSession.OpenReadStream(ssrc)
+	rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(ssrc))
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/rtpsender.go b/rtpsender.go
index b07828ec..e8f0a2f1 100644
--- a/rtpsender.go
+++ b/rtpsender.go
@@ -6,18 +6,21 @@ import (
 	"io"
 	"sync"
 
+	"github.com/pion/randutil"
 	"github.com/pion/rtcp"
-	"github.com/pion/rtp"
 	"github.com/pion/srtp"
 )
 
 // RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
 type RTPSender struct {
-	track          *Track
+	track          TrackLocal
 	rtcpReadStream *srtp.ReadStreamSRTCP
 
 	transport *DTLSTransport
 
+	payloadType PayloadType
+	ssrc        SSRC
+
 	// nolint:godox
 	// TODO(sgotti) remove this when in future we'll avoid replacing
 	// a transceiver sender since we can just check the
@@ -26,25 +29,24 @@ type RTPSender struct {
 
 	// A reference to the associated api object
 	api *API
+	id  string
 
 	mu                     sync.RWMutex
 	sendCalled, stopCalled chan interface{}
 }
 
 // NewRTPSender constructs a new RTPSender
-func (api *API) NewRTPSender(track *Track, transport *DTLSTransport) (*RTPSender, error) {
+func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSender, error) {
 	if track == nil {
 		return nil, errRTPSenderTrackNil
 	} else if transport == nil {
 		return nil, errRTPSenderDTLSTransportNil
 	}
 
-	track.mu.Lock()
-	defer track.mu.Unlock()
-	if track.receiver != nil {
-		return nil, errRTPSenderCannotConstructRemoteTrack
+	id, err := randutil.GenerateCryptoRandomString(32, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+	if err != nil {
+		return nil, err
 	}
-	track.totalSenderCount++
 
 	return &RTPSender{
 		track:      track,
@@ -52,6 +54,8 @@ func (api *API) NewRTPSender(track *Track, transport *DTLSTransport) (*RTPSender
 		api:        api,
 		sendCalled: make(chan interface{}),
 		stopCalled: make(chan interface{}),
+		ssrc:       SSRC(randutil.NewMathRandomGenerator().Uint32()),
+		id:         id,
 	}, nil
 }
 
@@ -76,13 +80,13 @@ func (r *RTPSender) Transport() *DTLSTransport {
 }
 
 // Track returns the RTCRtpTransceiver track, or nil
-func (r *RTPSender) Track() *Track {
+func (r *RTPSender) Track() TrackLocal {
 	r.mu.RLock()
 	defer r.mu.RUnlock()
 	return r.track
 }
 
-func (r *RTPSender) setTrack(track *Track) {
+func (r *RTPSender) setTrack(track TrackLocal) {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 	r.track = track
@@ -102,14 +106,29 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
 		return err
 	}
 
-	r.rtcpReadStream, err = srtcpSession.OpenReadStream(parameters.Encodings.SSRC)
+	r.rtcpReadStream, err = srtcpSession.OpenReadStream(uint32(parameters.Encodings.SSRC))
+	if err != nil {
+		return err
+	}
+
+	srtpSession, err := r.transport.getSRTPSession()
 	if err != nil {
 		return err
 	}
 
-	r.track.mu.Lock()
-	r.track.activeSenders = append(r.track.activeSenders, r)
-	r.track.mu.Unlock()
+	rtpWriteStream, err := srtpSession.OpenWriteStream()
+	if err != nil {
+		return err
+	}
+
+	if err = r.track.Bind(TrackLocalContext{
+		id:          r.id,
+		codecs:      r.api.mediaEngine.getCodecsByKind(r.track.Kind()),
+		ssrc:        parameters.Encodings.SSRC,
+		writeStream: rtpWriteStream,
+	}); err != nil {
+		return err
+	}
 
 	close(r.sendCalled)
 	return nil
@@ -125,25 +144,21 @@ func (r *RTPSender) Stop() error {
 		return nil
 	default:
 	}
+	close(r.stopCalled)
 
-	r.track.mu.Lock()
-	defer r.track.mu.Unlock()
-	filtered := []*RTPSender{}
-	for _, s := range r.track.activeSenders {
-		if s != r {
-			filtered = append(filtered, s)
-		} else {
-			r.track.totalSenderCount--
-		}
+	if !r.hasSent() {
+		return nil
 	}
-	r.track.activeSenders = filtered
-	close(r.stopCalled)
 
-	if r.hasSent() {
-		return r.rtcpReadStream.Close()
+	if err := r.track.Unbind(TrackLocalContext{
+		id:     r.id,
+		codecs: r.api.mediaEngine.getCodecsByKind(r.track.Kind()),
+		ssrc:   r.ssrc,
+	}); err != nil {
+		return err
 	}
 
-	return nil
+	return r.rtcpReadStream.Close()
 }
 
 // Read reads incoming RTCP for this RTPReceiver
@@ -167,31 +182,6 @@ func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, error) {
 	return rtcp.Unmarshal(b[:i])
 }
 
-// SendRTP sends a RTP packet on this RTPSender
-//
-// You should use Track instead to send packets. This is exposed because pion/webrtc currently
-// provides no way for users to send RTP packets directly. This is makes users unable to send
-// retransmissions to a single RTPSender. in /v3 this will go away, only use this API if you really
-// need it.
-func (r *RTPSender) SendRTP(header *rtp.Header, payload []byte) (int, error) {
-	select {
-	case <-r.stopCalled:
-		return 0, errRTPSenderStopped
-	case <-r.sendCalled:
-		srtpSession, err := r.transport.getSRTPSession()
-		if err != nil {
-			return 0, err
-		}
-
-		writeStream, err := srtpSession.OpenWriteStream()
-		if err != nil {
-			return 0, err
-		}
-
-		return writeStream.WriteRTP(header, payload)
-	}
-}
-
 // hasSent tells if data has been ever sent for this instance
 func (r *RTPSender) hasSent() bool {
 	select {
diff --git a/rtptransceiver.go b/rtptransceiver.go
index 40da8d33..b5b8fd8e 100644
--- a/rtptransceiver.go
+++ b/rtptransceiver.go
@@ -7,7 +7,6 @@ import (
 	"sync/atomic"
 
 	"github.com/pion/rtp"
-	"github.com/pion/sdp/v3"
 )
 
 // RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
@@ -31,7 +30,7 @@ func (t *RTPTransceiver) Sender() *RTPSender {
 }
 
 // SetSender sets the RTPSender and Track to current transceiver
-func (t *RTPTransceiver) SetSender(s *RTPSender, track *Track) error {
+func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error {
 	t.setSender(s)
 	return t.setSendingTrack(track)
 }
@@ -101,7 +100,7 @@ func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) {
 	t.direction.Store(d)
 }
 
-func (t *RTPTransceiver) setSendingTrack(track *Track) error {
+func (t *RTPTransceiver) setSendingTrack(track TrackLocal) error {
 	t.Sender().setTrack(track)
 	if track == nil {
 		t.setSender(nil)
@@ -163,7 +162,7 @@ func satisfyTypeAndDirection(remoteKind RTPCodecType, remoteDirection RTPTransce
 
 // handleUnknownRTPPacket consumes a single RTP Packet and returns information that is helpful
 // for demuxing and handling an unknown SSRC (usually for Simulcast)
-func handleUnknownRTPPacket(buf []byte, sdesMidExtMap, sdesStreamIDExtMap *sdp.ExtMap) (mid, rid string, payloadType uint8, err error) {
+func handleUnknownRTPPacket(buf []byte, midExtensionID, streamIDExtensionID uint8) (mid, rid string, payloadType PayloadType, err error) {
 	rp := &rtp.Packet{}
 	if err = rp.Unmarshal(buf); err != nil {
 		return
@@ -173,12 +172,12 @@ func handleUnknownRTPPacket(buf []byte, sdesMidExtMap, sdesStreamIDExtMap *sdp.E
 		return
 	}
 
-	payloadType = rp.PayloadType
-	if payload := rp.GetExtension(uint8(sdesMidExtMap.Value)); payload != nil {
+	payloadType = PayloadType(rp.PayloadType)
+	if payload := rp.GetExtension(midExtensionID); payload != nil {
 		mid = string(payload)
 	}
 
-	if payload := rp.GetExtension(uint8(sdesStreamIDExtMap.Value)); payload != nil {
+	if payload := rp.GetExtension(streamIDExtensionID); payload != nil {
 		rid = string(payload)
 	}
 
diff --git a/rtptransceiverinit.go b/rtptransceiverinit.go
index b5083cf7..3c439b7f 100644
--- a/rtptransceiverinit.go
+++ b/rtptransceiverinit.go
@@ -7,4 +7,6 @@ type RTPTransceiverInit struct {
 	// Streams       []*Track
 }
 
+// RtpTransceiverInit is a temporary mapping while we fix case sensitivity
+// Deprecated: Use RTPTransceiverInit instead
 type RtpTransceiverInit = RTPTransceiverInit //nolint: stylecheck,golint
diff --git a/sdp.go b/sdp.go
index a299caf3..6972a2a7 100644
--- a/sdp.go
+++ b/sdp.go
@@ -4,6 +4,7 @@ package webrtc
 
 import (
 	"fmt"
+	"net/url"
 	"regexp"
 	"strconv"
 	"strings"
@@ -16,15 +17,15 @@ import (
 // trackDetails represents any media source that can be represented in a SDP
 // This isn't keyed by SSRC because it also needs to support rid based sources
 type trackDetails struct {
-	mid   string
-	kind  RTPCodecType
-	label string
-	id    string
-	ssrc  uint32
-	rids  []string
+	mid      string
+	kind     RTPCodecType
+	streamID string
+	id       string
+	ssrc     SSRC
+	rids     []string
 }
 
-func trackDetailsForSSRC(trackDetails []trackDetails, ssrc uint32) *trackDetails {
+func trackDetailsForSSRC(trackDetails []trackDetails, ssrc SSRC) *trackDetails {
 	for i := range trackDetails {
 		if trackDetails[i].ssrc == ssrc {
 			return &trackDetails[i]
@@ -33,7 +34,7 @@ func trackDetailsForSSRC(trackDetails []trackDetails, ssrc uint32) *trackDetails
 	return nil
 }
 
-func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc uint32) []trackDetails {
+func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc SSRC) []trackDetails {
 	filtered := []trackDetails{}
 	for i := range incomingTracks {
 		if incomingTracks[i].ssrc != ssrc {
@@ -43,16 +44,6 @@ func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc uint32) []trackDeta
 	return filtered
 }
 
-// SDPSectionType specifies media type sections
-type SDPSectionType string
-
-// Common SDP sections
-const (
-	SDPSectionGlobal = SDPSectionType("global")
-	SDPSectionVideo  = SDPSectionType("video")
-	SDPSectionAudio  = SDPSectionType("audio")
-)
-
 // extract all trackDetails from an SDP.
 func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) []trackDetails { // nolint:gocognit
 	incomingTracks := []trackDetails{}
@@ -60,7 +51,7 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 
 	for _, media := range s.MediaDescriptions {
 		// Plan B can have multiple tracks in a signle media section
-		trackLabel := ""
+		streamID := ""
 		trackID := ""
 
 		// If media section is recvonly or inactive skip
@@ -101,7 +92,7 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 							continue
 						}
 						rtxRepairFlows[uint32(rtxRepairFlow)] = true
-						incomingTracks = filterTrackWithSSRC(incomingTracks, uint32(rtxRepairFlow)) // Remove if rtx was added as track before
+						incomingTracks = filterTrackWithSSRC(incomingTracks, SSRC(rtxRepairFlow)) // Remove if rtx was added as track before
 					}
 				}
 
@@ -111,7 +102,7 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 			case sdp.AttrKeyMsid:
 				split := strings.Split(attr.Value, " ")
 				if len(split) == 2 {
-					trackLabel = split[0]
+					streamID = split[0]
 					trackID = split[1]
 				}
 
@@ -128,14 +119,14 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 				}
 
 				if len(split) == 3 && strings.HasPrefix(split[1], "msid:") {
-					trackLabel = split[1][len("msid:"):]
+					streamID = split[1][len("msid:"):]
 					trackID = split[2]
 				}
 
 				isNewTrack := true
 				trackDetails := &trackDetails{}
 				for i := range incomingTracks {
-					if incomingTracks[i].ssrc == uint32(ssrc) {
+					if incomingTracks[i].ssrc == SSRC(ssrc) {
 						trackDetails = &incomingTracks[i]
 						isNewTrack = false
 					}
@@ -143,9 +134,9 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 
 				trackDetails.mid = midValue
 				trackDetails.kind = codecType
-				trackDetails.label = trackLabel
+				trackDetails.streamID = streamID
 				trackDetails.id = trackID
-				trackDetails.ssrc = uint32(ssrc)
+				trackDetails.ssrc = SSRC(ssrc)
 
 				if isNewTrack {
 					incomingTracks = append(incomingTracks, *trackDetails)
@@ -153,13 +144,13 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) [
 			}
 		}
 
-		if rids := getRids(media); len(rids) != 0 && trackID != "" && trackLabel != "" {
+		if rids := getRids(media); len(rids) != 0 && trackID != "" && streamID != "" {
 			newTrack := trackDetails{
-				mid:   midValue,
-				kind:  codecType,
-				label: trackLabel,
-				id:    trackID,
-				rids:  []string{},
+				mid:      midValue,
+				kind:     codecType,
+				streamID: streamID,
+				id:       trackID,
+				rids:     []string{},
 			}
 			for rid := range rids {
 				newTrack.rids = append(newTrack.rids, rid)
@@ -285,7 +276,7 @@ func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGathe
 	}
 }
 
-func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, extMaps map[SDPSectionType][]sdp.ExtMap, mediaSection mediaSection) (bool, error) {
+func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, mediaSection mediaSection) (bool, error) {
 	transceivers := mediaSection.transceivers
 	if len(transceivers) < 1 {
 		return false, errSDPZeroTransceivers
@@ -299,9 +290,11 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates b
 		WithPropertyAttribute(sdp.AttrKeyRTCPMux).
 		WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
 
-	codecs := mediaEngine.GetCodecsByKind(t.kind)
+	codecs := mediaEngine.getCodecsByKind(t.kind)
 	for _, codec := range codecs {
-		media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
+		name := strings.TrimPrefix(codec.MimeType, "audio/")
+		name = strings.TrimPrefix(name, "video/")
+		media.WithCodec(uint8(codec.PayloadType), name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
 
 		for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
 			media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
@@ -320,11 +313,12 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates b
 		return false, nil
 	}
 
-	// Add extmaps
-	if maps, ok := extMaps[SDPSectionType(t.kind.String())]; ok {
-		for _, m := range maps {
-			media.WithExtMap(m)
+	for id, rtpExtension := range mediaEngine.negotiatedHeaderExtensionsForType(t.kind) {
+		extURL, err := url.Parse(rtpExtension.uri)
+		if err != nil {
+			return false, err
 		}
+		media.WithExtMap(sdp.ExtMap{Value: id, URI: extURL})
 	}
 
 	if len(mediaSection.ridMap) > 0 {
@@ -341,9 +335,9 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates b
 	for _, mt := range transceivers {
 		if mt.Sender() != nil && mt.Sender().Track() != nil {
 			track := mt.Sender().Track()
-			media = media.WithMediaSource(track.SSRC(), track.Label() /* cname */, track.Label() /* streamLabel */, track.ID())
+			media = media.WithMediaSource(uint32(mt.Sender().ssrc), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
 			if !isPlanB {
-				media = media.WithPropertyAttribute("msid:" + track.Label() + " " + track.ID())
+				media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
 				break
 			}
 		}
@@ -374,7 +368,7 @@ type mediaSection struct {
 }
 
 // populateSDP serializes a PeerConnections state into an SDP
-func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState, extMaps map[SDPSectionType][]sdp.ExtMap) (*sdp.SessionDescription, error) {
+func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState) (*sdp.SessionDescription, error) {
 	var err error
 	mediaDtlsFingerprints := []DTLSFingerprint{}
 
@@ -403,7 +397,7 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
 				return nil, err
 			}
 		} else {
-			shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCanidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, extMaps, m)
+			shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCanidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m)
 			if err != nil {
 				return nil, err
 			}
@@ -425,13 +419,6 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
 		d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite)
 	}
 
-	// Add global exts
-	if maps, ok := extMaps[SDPSectionGlobal]; ok {
-		for _, m := range maps {
-			d.WithPropertyAttribute(m.Marshal())
-		}
-	}
-
 	return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil
 }
 
@@ -565,109 +552,83 @@ func haveApplicationMediaSection(desc *sdp.SessionDescription) bool {
 	return false
 }
 
-func matchedAnswerExt(descriptions *sdp.SessionDescription, localMaps map[SDPSectionType][]sdp.ExtMap) (map[SDPSectionType][]sdp.ExtMap, error) {
-	remoteExtMaps, err := remoteExts(descriptions)
-	if err != nil {
-		return nil, err
+func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription {
+	for _, m := range desc.parsed.MediaDescriptions {
+		if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid {
+			return m
+		}
 	}
-	return answerExtMaps(remoteExtMaps, localMaps), nil
+	return nil
 }
 
-func answerExtMaps(remoteExtMaps map[SDPSectionType]map[int]sdp.ExtMap, localMaps map[SDPSectionType][]sdp.ExtMap) map[SDPSectionType][]sdp.ExtMap {
-	ret := map[SDPSectionType][]sdp.ExtMap{}
-	for mediaType, remoteExtMap := range remoteExtMaps {
-		if _, ok := ret[mediaType]; !ok {
-			ret[mediaType] = []sdp.ExtMap{}
-		}
-		for _, extItem := range remoteExtMap {
-			// add remote ext that match locally available ones
-			for _, extMap := range localMaps[mediaType] {
-				if extMap.URI.String() == extItem.URI.String() {
-					ret[mediaType] = append(ret[mediaType], extItem)
-				}
-			}
+// haveDataChannel return MediaDescription with MediaName equal application
+func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription {
+	for _, d := range desc.parsed.MediaDescriptions {
+		if d.MediaName.Media == mediaSectionApplication {
+			return d
 		}
 	}
-	return ret
+	return nil
 }
 
-func remoteExts(session *sdp.SessionDescription) (map[SDPSectionType]map[int]sdp.ExtMap, error) {
-	remoteExtMaps := map[SDPSectionType]map[int]sdp.ExtMap{}
+func codecsFromMediaDescription(m *sdp.MediaDescription) (out []RTPCodecParameters, err error) {
+	s := &sdp.SessionDescription{
+		MediaDescriptions: []*sdp.MediaDescription{m},
+	}
 
-	maybeAddExt := func(attr sdp.Attribute, mediaType SDPSectionType) error {
-		if attr.Key != "extmap" {
-			return nil
-		}
-		em := &sdp.ExtMap{}
-		if err := em.Unmarshal("extmap:" + attr.Value); err != nil {
-			return fmt.Errorf("%w: %v", errSDPParseExtMap, err)
+	for _, payloadStr := range m.MediaName.Formats {
+		payloadType, err := strconv.Atoi(payloadStr)
+		if err != nil {
+			return nil, err
 		}
-		if remoteExtMap, ok := remoteExtMaps[mediaType][em.Value]; ok {
-			if remoteExtMap.Value != em.Value {
-				return errSDPRemoteDescriptionChangedExtMap
+
+		codec, err := s.GetCodecForPayloadType(uint8(payloadType))
+		if err != nil {
+			if payloadType == 0 {
+				continue
 			}
-		} else {
-			remoteExtMaps[mediaType][em.Value] = *em
+			return nil, err
 		}
-		return nil
-	}
 
-	// populate the extmaps from the current remote description
-	for _, media := range session.MediaDescriptions {
-		mediaType := SDPSectionType(media.MediaName.Media)
-		// populate known remote extmap and handle conflicts.
-		if _, ok := remoteExtMaps[mediaType]; !ok {
-			remoteExtMaps[mediaType] = map[int]sdp.ExtMap{}
+		channels := uint16(0)
+		val, err := strconv.Atoi(codec.EncodingParameters)
+		if err == nil {
+			channels = uint16(val)
 		}
-		for _, attr := range media.Attributes {
-			if err := maybeAddExt(attr, mediaType); err != nil {
-				return nil, err
+
+		feedback := []RTCPFeedback{}
+		for _, raw := range codec.RTCPFeedback {
+			split := strings.Split(raw, " ")
+			entry := RTCPFeedback{Type: split[0]}
+			if len(split) == 2 {
+				entry.Parameter = split[1]
 			}
-		}
-	}
 
-	// Add global exts
-	for _, attr := range session.Attributes {
-		if err := maybeAddExt(attr, SDPSectionGlobal); err != nil {
-			return nil, err
+			feedback = append(feedback, entry)
 		}
+
+		out = append(out, RTPCodecParameters{
+			RTPCodecCapability: RTPCodecCapability{m.MediaName.Media + "/" + codec.Name, codec.ClockRate, channels, codec.Fmtp, feedback},
+			PayloadType:        PayloadType(payloadType),
+		})
 	}
-	return remoteExtMaps, nil
+
+	return out, nil
 }
 
-// GetExtMapByURI return a copy of the extmap matching the provided
-// URI. Note that the extmap value will change if not yet negotiated
-func getExtMapByURI(exts map[SDPSectionType][]sdp.ExtMap, uri string) *sdp.ExtMap {
-	for _, extList := range exts {
-		for _, extMap := range extList {
-			if extMap.URI.String() == uri {
-				return &sdp.ExtMap{
-					Value:     extMap.Value,
-					Direction: extMap.Direction,
-					URI:       extMap.URI,
-					ExtAttr:   extMap.ExtAttr,
-				}
+func rtpExtensionsFromMediaDescription(m *sdp.MediaDescription) (map[string]int, error) {
+	out := map[string]int{}
+
+	for _, a := range m.Attributes {
+		if a.Key == sdp.AttrKeyExtMap {
+			e := sdp.ExtMap{}
+			if err := e.Unmarshal(*a.String()); err != nil {
+				return nil, err
 			}
-		}
-	}
-	return nil
-}
 
-func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription {
-	for _, m := range desc.parsed.MediaDescriptions {
-		if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid {
-			return m
+			out[e.URI.String()] = e.Value
 		}
 	}
-	return nil
-}
 
-// haveDataChannel return MediaDescription with MediaName equal application
-func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription {
-	for _, d := range desc.parsed.MediaDescriptions {
-		if d.MediaName.Media == mediaSectionApplication {
-			return d
-		}
-	}
-	return nil
+	return out, nil
 }
diff --git a/settingengine.go b/settingengine.go
index 81230306..19c23f9e 100644
--- a/settingengine.go
+++ b/settingengine.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/pion/ice/v2"
 	"github.com/pion/logging"
-	"github.com/pion/sdp/v3"
 	"github.com/pion/transport/vnet"
 	"golang.org/x/net/proxy"
 )
@@ -49,7 +48,6 @@ type SettingEngine struct {
 		SRTCP *uint
 	}
 	sdpMediaLevelFingerprints                 bool
-	sdpExtensions                             map[SDPSectionType][]sdp.ExtMap
 	answeringDTLSRole                         DTLSRole
 	disableCertificateFingerprintVerification bool
 	disableSRTPReplayProtection               bool
@@ -250,68 +248,6 @@ func (e *SettingEngine) SetICETCPMux(tcpMux ice.TCPMux) {
 	e.iceTCPMux = tcpMux
 }
 
-// AddSDPExtensions adds available and offered extensions for media type.
-//
-// Ext IDs are optional and generated if you do not provide them
-// SDP answers will only include extensions supported by both sides
-func (e *SettingEngine) AddSDPExtensions(mediaType SDPSectionType, exts []sdp.ExtMap) {
-	if e.sdpExtensions == nil {
-		e.sdpExtensions = make(map[SDPSectionType][]sdp.ExtMap)
-	}
-	if _, ok := e.sdpExtensions[mediaType]; !ok {
-		e.sdpExtensions[mediaType] = []sdp.ExtMap{}
-	}
-	e.sdpExtensions[mediaType] = append(e.sdpExtensions[mediaType], exts...)
-}
-
-func (e *SettingEngine) getSDPExtensions() map[SDPSectionType][]sdp.ExtMap {
-	var lastID int
-	idMap := map[string]int{}
-
-	// Build provided ext id map
-	for _, extList := range e.sdpExtensions {
-		for _, ext := range extList {
-			if ext.Value != 0 {
-				idMap[ext.URI.String()] = ext.Value
-			}
-		}
-	}
-
-	// Find next available ID
-	nextID := func() {
-		var done bool
-		for !done {
-			lastID++
-			var found bool
-			for _, v := range idMap {
-				if lastID == v {
-					found = true
-					break
-				}
-			}
-			if !found {
-				done = true
-			}
-		}
-	}
-
-	// Assign missing IDs across all media types based on URI
-	for mType, extList := range e.sdpExtensions {
-		for i, ext := range extList {
-			if ext.Value == 0 {
-				if id, ok := idMap[ext.URI.String()]; ok {
-					e.sdpExtensions[mType][i].Value = id
-				} else {
-					nextID()
-					e.sdpExtensions[mType][i].Value = lastID
-					idMap[ext.URI.String()] = lastID
-				}
-			}
-		}
-	}
-	return e.sdpExtensions
-}
-
 // SetICEProxyDialer sets the proxy dialer interface based on golang.org/x/net/proxy.
 func (e *SettingEngine) SetICEProxyDialer(d proxy.Dialer) {
 	e.iceProxyDialer = d
diff --git a/stats.go b/stats.go
index 551c903f..a218d9e8 100644
--- a/stats.go
+++ b/stats.go
@@ -153,7 +153,7 @@ type CodecStats struct {
 	ID string `json:"id"`
 
 	// PayloadType as used in RTP encoding or decoding
-	PayloadType uint8 `json:"payloadType"`
+	PayloadType PayloadType `json:"payloadType"`
 
 	// CodecType of this CodecStats
 	CodecType CodecType `json:"codecType"`
@@ -196,7 +196,7 @@ type InboundRTPStreamStats struct {
 
 	// SSRC is the 32-bit unsigned integer value used to identify the source of the
 	// stream of RTP packets that this stats object concerns.
-	SSRC uint32 `json:"ssrc"`
+	SSRC SSRC `json:"ssrc"`
 
 	// Kind is either "audio" or "video"
 	Kind string `json:"kind"`
@@ -363,7 +363,7 @@ type OutboundRTPStreamStats struct {
 
 	// SSRC is the 32-bit unsigned integer value used to identify the source of the
 	// stream of RTP packets that this stats object concerns.
-	SSRC uint32 `json:"ssrc"`
+	SSRC SSRC `json:"ssrc"`
 
 	// Kind is either "audio" or "video"
 	Kind string `json:"kind"`
@@ -492,7 +492,7 @@ type RemoteInboundRTPStreamStats struct {
 
 	// SSRC is the 32-bit unsigned integer value used to identify the source of the
 	// stream of RTP packets that this stats object concerns.
-	SSRC uint32 `json:"ssrc"`
+	SSRC SSRC `json:"ssrc"`
 
 	// Kind is either "audio" or "video"
 	Kind string `json:"kind"`
@@ -600,7 +600,7 @@ type RemoteOutboundRTPStreamStats struct {
 
 	// SSRC is the 32-bit unsigned integer value used to identify the source of the
 	// stream of RTP packets that this stats object concerns.
-	SSRC uint32 `json:"ssrc"`
+	SSRC SSRC `json:"ssrc"`
 
 	// Kind is either "audio" or "video"
 	Kind string `json:"kind"`
@@ -685,7 +685,7 @@ type RTPContributingSourceStats struct {
 	// ContributorSSRC is the SSRC identifier of the contributing source represented
 	// by this stats object. It is a 32-bit unsigned integer that appears in the CSRC
 	// list of any packets the relevant source contributed to.
-	ContributorSSRC uint32 `json:"contributorSsrc"`
+	ContributorSSRC SSRC `json:"contributorSsrc"`
 
 	// InboundRTPStreamID is the ID of the InboundRTPStreamStats object representing
 	// the inbound RTP stream that this contributing source is contributing to.
diff --git a/stats_go.go b/stats_go.go
index 5dfc6be5..e1f622e5 100644
--- a/stats_go.go
+++ b/stats_go.go
@@ -78,7 +78,7 @@ func (r StatsReport) GetCertificateStats(c *Certificate) (CertificateStats, bool
 }
 
 // GetCodecStats is a helper method to return the associated stats for a given Codec
-func (r StatsReport) GetCodecStats(c *RTPCodec) (CodecStats, bool) {
+func (r StatsReport) GetCodecStats(c *RTPCodecParameters) (CodecStats, bool) {
 	statsID := c.statsID
 	stats, ok := r[statsID]
 	if !ok {
diff --git a/track.go b/track.go
deleted file mode 100644
index bd8641a3..00000000
--- a/track.go
+++ /dev/null
@@ -1,263 +0,0 @@
-// +build !js
-
-package webrtc
-
-import (
-	"io"
-	"sync"
-
-	"github.com/pion/rtp"
-	"github.com/pion/webrtc/v3/internal/util"
-	"github.com/pion/webrtc/v3/pkg/media"
-)
-
-const (
-	rtpOutboundMTU          = 1200
-	trackDefaultIDLength    = 16
-	trackDefaultLabelLength = 16
-)
-
-// Track represents a single media track
-type Track struct {
-	mu sync.RWMutex
-
-	id          string
-	payloadType uint8
-	kind        RTPCodecType
-	label       string
-	ssrc        uint32
-	codec       *RTPCodec
-	rid         string
-
-	packetizer rtp.Packetizer
-
-	receiver         *RTPReceiver
-	activeSenders    []*RTPSender
-	totalSenderCount int // count of all senders (accounts for senders that have not been started yet)
-	peeked           []byte
-}
-
-// ID gets the ID of the track
-func (t *Track) ID() string {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.id
-}
-
-// RID gets the RTP Stream ID of this Track
-// With Simulcast you will have multiple tracks with the same ID, but different RID values.
-// In many cases a Track will not have an RID, so it is important to assert it is non-zero
-func (t *Track) RID() string {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-
-	return t.rid
-}
-
-// PayloadType gets the PayloadType of the track
-func (t *Track) PayloadType() uint8 {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.payloadType
-}
-
-// Kind gets the Kind of the track
-func (t *Track) Kind() RTPCodecType {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.kind
-}
-
-// Label gets the Label of the track
-func (t *Track) Label() string {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.label
-}
-
-// SSRC gets the SSRC of the track
-func (t *Track) SSRC() uint32 {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.ssrc
-}
-
-// Msid gets the Msid of the track
-func (t *Track) Msid() string {
-	return t.Label() + " " + t.ID()
-}
-
-// Codec gets the Codec of the track
-func (t *Track) Codec() *RTPCodec {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.codec
-}
-
-// Packetizer gets the Packetizer of the track
-func (t *Track) Packetizer() rtp.Packetizer {
-	t.mu.RLock()
-	defer t.mu.RUnlock()
-	return t.packetizer
-}
-
-// Read reads data from the track. If this is a local track this will error
-func (t *Track) Read(b []byte) (n int, err error) {
-	t.mu.RLock()
-	r := t.receiver
-
-	if t.totalSenderCount != 0 || r == nil {
-		t.mu.RUnlock()
-		return 0, errTrackLocalTrackRead
-	}
-	peeked := t.peeked != nil
-	t.mu.RUnlock()
-
-	if peeked {
-		t.mu.Lock()
-		data := t.peeked
-		t.peeked = nil
-		t.mu.Unlock()
-		// someone else may have stolen our packet when we
-		// released the lock.  Deal with it.
-		if data != nil {
-			n = copy(b, data)
-			return
-		}
-	}
-
-	return r.readRTP(b, t)
-}
-
-// peek is like Read, but it doesn't discard the packet read
-func (t *Track) peek(b []byte) (n int, err error) {
-	n, err = t.Read(b)
-	if err != nil {
-		return
-	}
-
-	t.mu.Lock()
-	// this might overwrite data if somebody peeked between the Read
-	// and us getting the lock.  Oh well, we'll just drop a packet in
-	// that case.
-	data := make([]byte, n)
-	n = copy(data, b[:n])
-	t.peeked = data
-	t.mu.Unlock()
-	return
-}
-
-// ReadRTP is a convenience method that wraps Read and unmarshals for you
-func (t *Track) ReadRTP() (*rtp.Packet, error) {
-	b := make([]byte, receiveMTU)
-	i, err := t.Read(b)
-	if err != nil {
-		return nil, err
-	}
-
-	r := &rtp.Packet{}
-	if err := r.Unmarshal(b[:i]); err != nil {
-		return nil, err
-	}
-	return r, nil
-}
-
-// Write writes data to the track. If this is a remote track this will error
-func (t *Track) Write(b []byte) (n int, err error) {
-	packet := &rtp.Packet{}
-	err = packet.Unmarshal(b)
-	if err != nil {
-		return 0, err
-	}
-
-	err = t.WriteRTP(packet)
-	if err != nil {
-		return 0, err
-	}
-
-	return len(b), nil
-}
-
-// WriteSample packetizes and writes to the track
-func (t *Track) WriteSample(s media.Sample) error {
-	packets := t.packetizer.Packetize(s.Data, s.Samples)
-	for _, p := range packets {
-		err := t.WriteRTP(p)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// WriteRTP writes RTP packets to the track
-func (t *Track) WriteRTP(p *rtp.Packet) error {
-	t.mu.RLock()
-	if t.receiver != nil {
-		t.mu.RUnlock()
-		return errTrackLocalTrackWrite
-	}
-	senders := t.activeSenders
-	totalSenderCount := t.totalSenderCount
-	t.mu.RUnlock()
-
-	if totalSenderCount == 0 {
-		return io.ErrClosedPipe
-	}
-
-	writeErrs := []error{}
-	for _, s := range senders {
-		if _, err := s.SendRTP(&p.Header, p.Payload); err != nil {
-			writeErrs = append(writeErrs, err)
-		}
-	}
-
-	return util.FlattenErrs(writeErrs)
-}
-
-// NewTrack initializes a new *Track
-func NewTrack(payloadType uint8, ssrc uint32, id, label string, codec *RTPCodec) (*Track, error) {
-	if ssrc == 0 {
-		return nil, errTrackSSRCNewTrackZero
-	}
-
-	packetizer := rtp.NewPacketizer(
-		rtpOutboundMTU,
-		payloadType,
-		ssrc,
-		codec.Payloader,
-		rtp.NewRandomSequencer(),
-		codec.ClockRate,
-	)
-
-	return &Track{
-		id:          id,
-		payloadType: payloadType,
-		kind:        codec.Type,
-		label:       label,
-		ssrc:        ssrc,
-		codec:       codec,
-		packetizer:  packetizer,
-	}, nil
-}
-
-// determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track
-// this is useful if we are dealing with a remote track and we can't announce it to the user until we know the payloadType
-func (t *Track) determinePayloadType() error {
-	b := make([]byte, receiveMTU)
-	n, err := t.peek(b)
-	if err != nil {
-		return err
-	}
-	r := rtp.Packet{}
-	if err := r.Unmarshal(b[:n]); err != nil {
-		return err
-	}
-
-	t.mu.Lock()
-	t.payloadType = r.PayloadType
-	defer t.mu.Unlock()
-
-	return nil
-}
diff --git a/track_local.go b/track_local.go
new file mode 100644
index 00000000..53913370
--- /dev/null
+++ b/track_local.go
@@ -0,0 +1,68 @@
+package webrtc
+
+import "github.com/pion/rtp"
+
+// TrackLocalWriter is the Writer for outbound RTP Packets
+type TrackLocalWriter interface {
+	// WriteRTP encrypts a RTP packet and writes to the connection
+	WriteRTP(header *rtp.Header, payload []byte) (int, error)
+
+	// Write encrypts and writes a full RTP packet
+	Write(b []byte) (int, error)
+}
+
+// TrackLocalContext is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection
+type TrackLocalContext struct {
+	id          string
+	codecs      []RTPCodecParameters
+	ssrc        SSRC
+	writeStream TrackLocalWriter
+}
+
+// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
+// PeerConnections and the SSRC/PayloadTypes
+func (t *TrackLocalContext) CodecParameters() []RTPCodecParameters {
+	return t.codecs
+}
+
+// SSRC requires the negotiated SSRC of this track
+// This track may have multiple if RTX is enabled
+func (t *TrackLocalContext) SSRC() SSRC {
+	return t.ssrc
+}
+
+// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound
+// media packets to it
+func (t *TrackLocalContext) WriteStream() TrackLocalWriter {
+	return t.writeStream
+}
+
+// ID is a unique identifier that is used for both Bind/Unbind
+func (t *TrackLocalContext) ID() string {
+	return t.id
+}
+
+// TrackLocal is an interface that controls how the user can send media
+// The user can provide their own TrackLocal implementatiosn, or use
+// the implementations in pkg/media
+type TrackLocal interface {
+	// Bind should implement the way how the media data flows from the Track to the PeerConnection
+	// This will be called internally after signaling is complete and the list of available
+	// codecs has been determined
+	Bind(TrackLocalContext) error
+
+	// Unbind should implement the teardown logic when the track is no longer needed. This happens
+	// because a track has been stopped.
+	Unbind(TrackLocalContext) error
+
+	// ID is the unique identifier for this Track. This should be unique for the
+	// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
+	// and StreamID would be 'desktop' or 'webcam'
+	ID() string
+
+	// StreamID is the group this track belongs too. This must be unique
+	StreamID() string
+
+	// Kind controls if this TrackLocal is audio or video
+	Kind() RTPCodecType
+}
diff --git a/track_local_static.go b/track_local_static.go
new file mode 100644
index 00000000..0ed8fc67
--- /dev/null
+++ b/track_local_static.go
@@ -0,0 +1,235 @@
+// +build !js
+
+package webrtc
+
+import (
+	"strings"
+	"sync"
+
+	"github.com/pion/rtp"
+	"github.com/pion/webrtc/v3/internal/util"
+	"github.com/pion/webrtc/v3/pkg/media"
+)
+
+// trackBinding is a single bind for a Track
+// Bind can be called multiple times, this stores the
+// result for a single bind call so that it can be used when writing
+type trackBinding struct {
+	id          string
+	ssrc        SSRC
+	payloadType PayloadType
+	writeStream TrackLocalWriter
+}
+
+// TrackLocalStaticRTP  is a TrackLocal that has a pre-set codec and accepts RTP Packets.
+// If you wish to send a media.Sample use TrackLocalStaticSample
+type TrackLocalStaticRTP struct {
+	mu           sync.RWMutex
+	bindings     []trackBinding
+	codec        RTPCodecCapability
+	id, streamID string
+}
+
+// NewTrackLocalStaticRTP returns a TrackLocalStaticRTP.
+func NewTrackLocalStaticRTP(c RTPCodecCapability, id, streamID string) (*TrackLocalStaticRTP, error) {
+	return &TrackLocalStaticRTP{
+		codec:    c,
+		bindings: []trackBinding{},
+		id:       id,
+		streamID: streamID,
+	}, nil
+}
+
+// Bind is called by the PeerConnection after negotiation is complete
+// This asserts that the code requested is supported by the remote peer.
+// If so it setups all the state (SSRC and PayloadType) to have a call
+func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	parameters := RTPCodecParameters{RTPCodecCapability: s.codec}
+	if codec, err := codecParametersFuzzySearch(parameters, t.CodecParameters()); err == nil {
+		s.bindings = append(s.bindings, trackBinding{
+			ssrc:        t.SSRC(),
+			payloadType: codec.PayloadType,
+			writeStream: t.WriteStream(),
+			id:          t.ID(),
+		})
+		return nil
+	}
+
+	return ErrUnsupportedCodec
+}
+
+// Unbind implements the teardown logic when the track is no longer needed. This happens
+// because a track has been stopped.
+func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	for i := range s.bindings {
+		if s.bindings[i].id == t.ID() {
+			s.bindings[i] = s.bindings[len(s.bindings)-1]
+			s.bindings = s.bindings[:len(s.bindings)-1]
+			return nil
+		}
+	}
+
+	return ErrUnbindFailed
+}
+
+// ID is the unique identifier for this Track. This should be unique for the
+// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
+// and StreamID would be 'desktop' or 'webcam'
+func (s *TrackLocalStaticRTP) ID() string { return s.id }
+
+// StreamID is the group this track belongs too. This must be unique
+func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID }
+
+// Kind controls if this TrackLocal is audio or video
+func (s *TrackLocalStaticRTP) Kind() RTPCodecType {
+	switch {
+	case strings.HasPrefix(s.codec.MimeType, "audio/"):
+		return RTPCodecTypeAudio
+	case strings.HasPrefix(s.codec.MimeType, "video/"):
+		return RTPCodecTypeVideo
+	default:
+		return RTPCodecType(0)
+	}
+}
+
+// WriteRTP writes a RTP Packet to the TrackLocalStaticRTP
+// If one PeerConnection fails the packets will still be sent to
+// all PeerConnections. The error message will contain the ID of the failed
+// PeerConnections so you can remove them
+func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+
+	writeErrs := []error{}
+	for _, b := range s.bindings {
+		p.Header.SSRC = uint32(b.ssrc)
+		p.Header.PayloadType = uint8(b.payloadType)
+		if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil {
+			writeErrs = append(writeErrs, err)
+		}
+	}
+
+	return util.FlattenErrs(writeErrs)
+}
+
+// Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP
+// If one PeerConnection fails the packets will still be sent to
+// all PeerConnections. The error message will contain the ID of the failed
+// PeerConnections so you can remove them
+func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) {
+	packet := &rtp.Packet{}
+	if err = packet.Unmarshal(b); err != nil {
+		return 0, err
+	}
+
+	return len(b), s.WriteRTP(packet)
+}
+
+// TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples.
+// If you wish to send a RTP Packet use TrackLocalStaticRTP
+type TrackLocalStaticSample struct {
+	packetizer rtp.Packetizer
+	rtpTrack   *TrackLocalStaticRTP
+	clockRate  float64
+}
+
+// NewTrackLocalStaticSample returns a TrackLocalStaticSample
+func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string) (*TrackLocalStaticSample, error) {
+	rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID)
+	if err != nil {
+		return nil, err
+	}
+
+	return &TrackLocalStaticSample{
+		rtpTrack: rtpTrack,
+	}, nil
+}
+
+// ID is the unique identifier for this Track. This should be unique for the
+// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
+// and StreamID would be 'desktop' or 'webcam'
+func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() }
+
+// StreamID is the group this track belongs too. This must be unique
+func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() }
+
+// Kind controls if this TrackLocal is audio or video
+func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() }
+
+// Bind is called by the PeerConnection after negotiation is complete
+// This asserts that the code requested is supported by the remote peer.
+// If so it setups all the state (SSRC and PayloadType) to have a call
+func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) error {
+	if err := s.rtpTrack.Bind(t); err != nil {
+		return err
+	}
+
+	s.rtpTrack.mu.Lock()
+	defer s.rtpTrack.mu.Unlock()
+
+	// We only need one packetizer
+	if s.packetizer != nil {
+		return nil
+	}
+
+	parameters := RTPCodecParameters{RTPCodecCapability: s.rtpTrack.codec}
+	codec, err := codecParametersFuzzySearch(parameters, t.CodecParameters())
+	if err != nil {
+		return err
+	}
+
+	payloader, err := payloaderForCodec(s.rtpTrack.codec)
+	if err != nil {
+		return err
+	}
+
+	s.packetizer = rtp.NewPacketizer(
+		rtpOutboundMTU,
+		0, // Value is handled when writing
+		0, // Value is handled when writing
+		payloader,
+		rtp.NewRandomSequencer(),
+		codec.ClockRate,
+	)
+	s.clockRate = float64(codec.RTPCodecCapability.ClockRate)
+	return nil
+}
+
+// Unbind implements the teardown logic when the track is no longer needed. This happens
+// because a track has been stopped.
+func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error {
+	return s.rtpTrack.Unbind(t)
+}
+
+// WriteSample writes a Sample to the TrackLocalStaticSample
+// If one PeerConnection fails the packets will still be sent to
+// all PeerConnections. The error message will contain the ID of the failed
+// PeerConnections so you can remove them
+func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error {
+	s.rtpTrack.mu.RLock()
+	p := s.packetizer
+	clockRate := s.clockRate
+	s.rtpTrack.mu.RUnlock()
+
+	if p == nil {
+		return nil
+	}
+
+	samples := sample.Duration.Seconds() * clockRate
+	packets := p.(rtp.Packetizer).Packetize(sample.Data, uint32(samples))
+
+	writeErrs := []error{}
+	for _, p := range packets {
+		if err := s.rtpTrack.WriteRTP(p); err != nil {
+			writeErrs = append(writeErrs, err)
+		}
+	}
+
+	return util.FlattenErrs(writeErrs)
+}
diff --git a/track_remote.go b/track_remote.go
new file mode 100644
index 00000000..dd48e3a8
--- /dev/null
+++ b/track_remote.go
@@ -0,0 +1,161 @@
+// +build !js
+
+package webrtc
+
+import (
+	"sync"
+
+	"github.com/pion/rtp"
+)
+
+// TrackRemote represents a single inbound source of media
+type TrackRemote struct {
+	mu sync.RWMutex
+
+	id       string
+	streamID string
+
+	payloadType PayloadType
+	kind        RTPCodecType
+	ssrc        SSRC
+	codec       RTPCodecParameters
+	rid         string
+
+	receiver *RTPReceiver
+	peeked   []byte
+}
+
+// ID is the unique identifier for this Track. This should be unique for the
+// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
+// and StreamID would be 'desktop' or 'webcam'
+func (t *TrackRemote) ID() string {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.id
+}
+
+// RID gets the RTP Stream ID of this Track
+// With Simulcast you will have multiple tracks with the same ID, but different RID values.
+// In many cases a TrackRemote will not have an RID, so it is important to assert it is non-zero
+func (t *TrackRemote) RID() string {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+
+	return t.rid
+}
+
+// PayloadType gets the PayloadType of the track
+func (t *TrackRemote) PayloadType() PayloadType {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.payloadType
+}
+
+// Kind gets the Kind of the track
+func (t *TrackRemote) Kind() RTPCodecType {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.kind
+}
+
+// StreamID is the group this track belongs too. This must be unique
+func (t *TrackRemote) StreamID() string {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.streamID
+}
+
+// SSRC gets the SSRC of the track
+func (t *TrackRemote) SSRC() SSRC {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.ssrc
+}
+
+// Msid gets the Msid of the track
+func (t *TrackRemote) Msid() string {
+	return t.StreamID() + " " + t.ID()
+}
+
+// Codec gets the Codec of the track
+func (t *TrackRemote) Codec() RTPCodecParameters {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.codec
+}
+
+// Read reads data from the track.
+func (t *TrackRemote) Read(b []byte) (n int, err error) {
+	t.mu.RLock()
+	r := t.receiver
+	peeked := t.peeked != nil
+	t.mu.RUnlock()
+
+	if peeked {
+		t.mu.Lock()
+		data := t.peeked
+		t.peeked = nil
+		t.mu.Unlock()
+		// someone else may have stolen our packet when we
+		// released the lock.  Deal with it.
+		if data != nil {
+			n = copy(b, data)
+			return
+		}
+	}
+
+	return r.readRTP(b, t)
+}
+
+// peek is like Read, but it doesn't discard the packet read
+func (t *TrackRemote) peek(b []byte) (n int, err error) {
+	n, err = t.Read(b)
+	if err != nil {
+		return
+	}
+
+	t.mu.Lock()
+	// this might overwrite data if somebody peeked between the Read
+	// and us getting the lock.  Oh well, we'll just drop a packet in
+	// that case.
+	data := make([]byte, n)
+	n = copy(data, b[:n])
+	t.peeked = data
+	t.mu.Unlock()
+	return
+}
+
+// ReadRTP is a convenience method that wraps Read and unmarshals for you
+func (t *TrackRemote) ReadRTP() (*rtp.Packet, error) {
+	b := make([]byte, receiveMTU)
+	i, err := t.Read(b)
+	if err != nil {
+		return nil, err
+	}
+
+	r := &rtp.Packet{}
+	if err := r.Unmarshal(b[:i]); err != nil {
+		return nil, err
+	}
+	return r, nil
+}
+
+// determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track
+// this is useful because we can't announce it to the user until we know the payloadType
+func (t *TrackRemote) determinePayloadType() error {
+	b := make([]byte, receiveMTU)
+	n, err := t.peek(b)
+	if err != nil {
+		return err
+	}
+	r := rtp.Packet{}
+	if err := r.Unmarshal(b[:n]); err != nil {
+		return err
+	}
+
+	t.mu.Lock()
+	t.payloadType = PayloadType(r.PayloadType)
+	defer t.mu.Unlock()
+
+	return nil
+}
diff --git a/webrtc.go b/webrtc.go
new file mode 100644
index 00000000..c111bc94
--- /dev/null
+++ b/webrtc.go
@@ -0,0 +1,21 @@
+// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
+package webrtc
+
+// SSRC represents a synchronization source
+// A synchronization source is a randomly chosen
+// value meant to be globally unique within a particular
+// RTP session. Used to identify a single stream of media.
+//
+// https://tools.ietf.org/html/rfc3550#section-3
+type SSRC uint32
+
+// PayloadType identifies the format of the RTP payload and determines
+// its interpretation by the application. Each codec in a RTP Session
+// will have a different PayloadType
+//
+// https://tools.ietf.org/html/rfc3550#section-3
+type PayloadType uint8
+
+const (
+	rtpOutboundMTU = 1200
+)


Test patch:
diff --git a/api_test.go b/api_test.go
index c930671d..af7eaf79 100644
--- a/api_test.go
+++ b/api_test.go
@@ -4,6 +4,8 @@ package webrtc
 
 import (
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestNewAPI(t *testing.T) {
@@ -22,7 +24,7 @@ func TestNewAPI_Options(t *testing.T) {
 	s := SettingEngine{}
 	s.DetachDataChannels()
 	m := MediaEngine{}
-	m.RegisterDefaultCodecs()
+	assert.NoError(t, m.RegisterDefaultCodecs())
 
 	api := NewAPI(
 		WithSettingEngine(s),
@@ -33,7 +35,7 @@ func TestNewAPI_Options(t *testing.T) {
 		t.Error("Failed to set settings engine")
 	}
 
-	if len(api.mediaEngine.codecs) == 0 {
+	if len(api.mediaEngine.audioCodecs) == 0 || len(api.mediaEngine.videoCodecs) == 0 {
 		t.Error("Failed to set media engine")
 	}
 }
diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go
index 96688717..ba203baa 100644
--- a/e2e/e2e_test.go
+++ b/e2e/e2e_test.go
@@ -5,7 +5,6 @@ package main
 import (
 	"context"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"os"
 	"strconv"
@@ -13,11 +12,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/sclevine/agouti"
-
-	"github.com/pion/randutil"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/pkg/media"
+	"github.com/sclevine/agouti"
 )
 
 var silentOpusFrame = []byte{0xf8, 0xff, 0xfe} // 20ms, 8kHz, mono
@@ -103,7 +100,7 @@ func TestE2E_Audio(t *testing.T) {
 			go func() {
 				for {
 					if err := track.WriteSample(
-						media.Sample{Data: silentOpusFrame, Samples: 960},
+						media.Sample{Data: silentOpusFrame, Duration: time.Millisecond * 20},
 					); err != nil {
 						t.Errorf("Failed to WriteSample: %v", err)
 						return
@@ -330,28 +327,13 @@ func parseLog(log agouti.Log) (string, string, bool) {
 	return k, v, true
 }
 
-func createTrack(offer webrtc.SessionDescription) (*webrtc.PeerConnection, *webrtc.SessionDescription, *webrtc.Track, error) {
-	mediaEngine := webrtc.MediaEngine{}
-	if err := mediaEngine.PopulateFromSDP(offer); err != nil {
-		return nil, nil, nil, err
-	}
-	var payloadType uint8
-	for _, videoCodec := range mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeAudio) {
-		if videoCodec.Name == webrtc.Opus {
-			payloadType = videoCodec.PayloadType
-			break
-		}
-	}
-	if payloadType == 0 {
-		return nil, nil, nil, errors.New("Remote peer does not support VP8")
-	}
-	api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
-	pc, errPc := api.NewPeerConnection(webrtc.Configuration{})
+func createTrack(offer webrtc.SessionDescription) (*webrtc.PeerConnection, *webrtc.SessionDescription, *webrtc.TrackLocalStaticSample, error) {
+	pc, errPc := webrtc.NewPeerConnection(webrtc.Configuration{})
 	if errPc != nil {
 		return nil, nil, nil, errPc
 	}
 
-	track, errTrack := pc.NewTrack(payloadType, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	track, errTrack := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion")
 	if errTrack != nil {
 		return nil, nil, nil, errTrack
 	}
diff --git a/mediaengine_test.go b/mediaengine_test.go
index ec4c88f8..f3312e87 100644
--- a/mediaengine_test.go
+++ b/mediaengine_test.go
@@ -4,98 +4,12 @@ package webrtc
 
 import (
 	"regexp"
-	"strings"
 	"testing"
 
 	"github.com/pion/sdp/v3"
 	"github.com/stretchr/testify/assert"
 )
 
-const sdpValue = `v=0
-o=- 884433216 1576829404 IN IP4 0.0.0.0
-s=-
-t=0 0
-a=fingerprint:sha-256 1D:6B:6D:18:95:41:F9:BC:E4:AC:25:6A:26:A3:C8:09:D2:8C:EE:1B:7D:54:53:33:F7:E3:2C:0D:FE:7A:9D:6B
-a=group:BUNDLE 0 1 2
-m=audio 9 UDP/TLS/RTP/SAVPF 0 8 111 9
-c=IN IP4 0.0.0.0
-a=mid:0
-a=rtpmap:0 PCMU/8000
-a=rtpmap:8 PCMA/8000
-a=rtpmap:111 opus/48000/2
-a=fmtp:111 minptime=10;useinbandfec=1
-a=rtpmap:9 G722/8000
-a=ssrc:1823804162 cname:pion1
-a=ssrc:1823804162 msid:pion1 audio
-a=ssrc:1823804162 mslabel:pion1
-a=ssrc:1823804162 label:audio
-a=msid:pion1 audio
-m=video 9 UDP/TLS/RTP/SAVPF 105 115 135
-c=IN IP4 0.0.0.0
-a=mid:1
-a=rtpmap:105 VP8/90000
-a=rtpmap:115 H264/90000
-a=fmtp:115 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
-a=rtpmap:135 VP9/90000
-a=ssrc:2949882636 cname:pion2
-a=ssrc:2949882636 msid:pion2 video
-a=ssrc:2949882636 mslabel:pion2
-a=ssrc:2949882636 label:video
-a=msid:pion2 video
-m=application 9 DTLS/SCTP 5000
-c=IN IP4 0.0.0.0
-a=mid:2
-a=sctpmap:5000 webrtc-datachannel 1024
-`
-
-func TestCodecRegistration(t *testing.T) {
-	api := NewAPI()
-	const invalidPT = 255
-
-	api.mediaEngine.RegisterDefaultCodecs()
-
-	testCases := []struct {
-		c uint8
-		e error
-	}{
-		{DefaultPayloadTypePCMU, nil},
-		{DefaultPayloadTypePCMA, nil},
-		{DefaultPayloadTypeG722, nil},
-		{DefaultPayloadTypeOpus, nil},
-		{DefaultPayloadTypeVP8, nil},
-		{DefaultPayloadTypeVP9, nil},
-		{DefaultPayloadTypeH264, nil},
-		{invalidPT, ErrCodecNotFound},
-	}
-
-	for _, f := range testCases {
-		_, err := api.mediaEngine.getCodec(f.c)
-		assert.Equal(t, f.e, err)
-	}
-	_, err := api.mediaEngine.getCodecSDP(sdp.Codec{PayloadType: invalidPT})
-	assert.Equal(t, err, ErrCodecNotFound)
-}
-
-func TestPopulateFromSDP(t *testing.T) {
-	m := MediaEngine{}
-	assertCodecWithPayloadType := func(name string, payloadType uint8) {
-		for _, c := range m.codecs {
-			if c.PayloadType == payloadType && c.Name == name {
-				return
-			}
-		}
-		t.Fatalf("Failed to find codec(%s) with PayloadType(%d)", name, payloadType)
-	}
-
-	m.RegisterDefaultCodecs()
-	assert.NoError(t, m.PopulateFromSDP(SessionDescription{SDP: sdpValue}))
-
-	assertCodecWithPayloadType(Opus, 111)
-	assertCodecWithPayloadType(VP8, 105)
-	assertCodecWithPayloadType(H264, 115)
-	assertCodecWithPayloadType(VP9, 135)
-}
-
 // pion/webrtc#1078
 func TestOpusCase(t *testing.T) {
 	pc, err := NewPeerConnection(Configuration{})
@@ -111,61 +25,143 @@ func TestOpusCase(t *testing.T) {
 	assert.NoError(t, pc.Close())
 }
 
-// pion/webrtc#1442
-func TestCaseInsensitive(t *testing.T) {
-	m := MediaEngine{}
-	m.RegisterDefaultCodecs()
-
-	testCases := []struct {
-		nameUpperCase string
-		nameLowerCase string
-		clockrate     uint32
-		fmtp          string
-	}{
-		{strings.ToUpper(Opus), strings.ToLower(Opus), 48000, "minptime=10;useinbandfec=1"},
-		{strings.ToUpper(PCMU), strings.ToLower(PCMU), 8000, ""},
-		{strings.ToUpper(PCMA), strings.ToLower(PCMA), 8000, ""},
-		{strings.ToUpper(G722), strings.ToLower(G722), 8000, ""},
-		{strings.ToUpper(VP8), strings.ToLower(VP8), 90000, ""},
-		{strings.ToUpper(VP9), strings.ToLower(VP9), 90000, ""},
-		{strings.ToUpper(H264), strings.ToLower(H264), 90000, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"},
+func TestMediaEngineRemoteDescription(t *testing.T) {
+	mustParse := func(raw string) sdp.SessionDescription {
+		s := sdp.SessionDescription{}
+		assert.NoError(t, s.Unmarshal([]byte(raw)))
+		return s
 	}
 
-	for _, f := range testCases {
-		upperCase, err := m.getCodecSDP(sdp.Codec{
-			Name:      f.nameUpperCase,
-			ClockRate: f.clockrate,
-			Fmtp:      f.fmtp,
-		})
+	t.Run("No Media", func(t *testing.T) {
+		const noMedia = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+`
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(noMedia)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.False(t, m.negotiatedAudio)
+	})
+
+	t.Run("Enable Opus", func(t *testing.T) {
+		const opusSamePayload = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+a=rtpmap:111 opus/48000/2
+a=fmtp:111 minptime=10; useinbandfec=1
+`
+
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.True(t, m.negotiatedAudio)
+
+		opusCodec, err := m.getCodecByPayload(111)
+		assert.NoError(t, err)
+		assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
+	})
+
+	t.Run("Change Payload Type", func(t *testing.T) {
+		const opusSamePayload = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+m=audio 9 UDP/TLS/RTP/SAVPF 112
+a=rtpmap:112 opus/48000/2
+a=fmtp:112 minptime=10; useinbandfec=1
+`
+
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.True(t, m.negotiatedAudio)
+
+		_, err := m.getCodecByPayload(111)
+		assert.Error(t, err)
+
+		opusCodec, err := m.getCodecByPayload(112)
 		assert.NoError(t, err)
+		assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
+	})
 
-		lowerCase, err := m.getCodecSDP(sdp.Codec{
-			Name:      f.nameLowerCase,
-			ClockRate: f.clockrate,
-			Fmtp:      f.fmtp,
-		})
+	t.Run("Case Insensitive", func(t *testing.T) {
+		const opusUpcase = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+a=rtpmap:111 OPUS/48000/2
+a=fmtp:111 minptime=10; useinbandfec=1
+`
+
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusUpcase)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.True(t, m.negotiatedAudio)
+
+		opusCodec, err := m.getCodecByPayload(111)
 		assert.NoError(t, err)
+		assert.Equal(t, opusCodec.MimeType, "audio/OPUS")
+	})
 
-		assert.Equal(t, upperCase, lowerCase)
-	}
-}
+	t.Run("Handle different fmtp", func(t *testing.T) {
+		const opusNoFmtp = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+a=rtpmap:111 opus/48000/2
+`
 
-func TestGetCodecsByName(t *testing.T) {
-	var cdc *RTPCodec
-	m := MediaEngine{}
-	assert.NoError(t, m.PopulateFromSDP(SessionDescription{SDP: sdpValue}))
-
-	assertGetCodecsByName := func(name string) {
-		for _, cdc = range m.GetCodecsByName(name) {
-			if strings.EqualFold(cdc.Name, name) {
-				return
-			}
-		}
-		t.Fatalf("Failed to getting codec(%s) by name (%s)", cdc.Name, name)
-	}
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusNoFmtp)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.True(t, m.negotiatedAudio)
+
+		opusCodec, err := m.getCodecByPayload(111)
+		assert.NoError(t, err)
+		assert.Equal(t, opusCodec.MimeType, mimeTypeOpus)
+	})
+
+	t.Run("Header Extensions", func(t *testing.T) {
+		const headerExtensions = `v=0
+o=- 4596489990601351948 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+a=extmap:7 urn:ietf:params:rtp-hdrext:sdes:mid
+a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
+a=rtpmap:111 opus/48000/2
+`
+
+		m := MediaEngine{}
+		assert.NoError(t, m.RegisterDefaultCodecs())
+		assert.NoError(t, m.updateFromRemoteDescription(mustParse(headerExtensions)))
+
+		assert.False(t, m.negotiatedVideo)
+		assert.True(t, m.negotiatedAudio)
+
+		absID, absAudioEnabled, absVideoEnabled := m.GetHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
+		assert.Equal(t, absID, 0)
+		assert.False(t, absAudioEnabled)
+		assert.False(t, absVideoEnabled)
 
-	assertGetCodecsByName(VP8)
-	assertGetCodecsByName(H264)
-	assertGetCodecsByName(VP9)
-	assertGetCodecsByName(Opus)
+		midID, midAudioEnabled, midVideoEnabled := m.GetHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
+		assert.Equal(t, midID, 7)
+		assert.True(t, midAudioEnabled)
+		assert.False(t, midVideoEnabled)
+	})
 }
diff --git a/peerconnection_go_test.go b/peerconnection_go_test.go
index 6595748e..3ecc36d7 100644
--- a/peerconnection_go_test.go
+++ b/peerconnection_go_test.go
@@ -302,7 +302,7 @@ func TestPeerConnection_EventHandlers_Go(t *testing.T) {
 	assert.NotPanics(t, func() { pc.onTrack(nil, nil) })
 	assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) })
 
-	pc.OnTrack(func(t *Track, r *RTPReceiver) {
+	pc.OnTrack(func(t *TrackRemote, r *RTPReceiver) {
 		close(onTrackCalled)
 	})
 
@@ -324,7 +324,7 @@ func TestPeerConnection_EventHandlers_Go(t *testing.T) {
 	assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) })
 
 	// Verify that the set handlers are called
-	assert.NotPanics(t, func() { pc.onTrack(&Track{}, &RTPReceiver{}) })
+	assert.NotPanics(t, func() { pc.onTrack(&TrackRemote{}, &RTPReceiver{}) })
 	assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) })
 	assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) })
 
@@ -1036,13 +1036,14 @@ type trackRecords struct {
 	receivedTrackIDs map[string]struct{}
 }
 
-func (r *trackRecords) newTrackParameter() (uint8, uint32, string, string) {
+func (r *trackRecords) newTrack() (*TrackLocalStaticRTP, error) {
 	trackID := fmt.Sprintf("pion-track-%d", len(r.trackIDs))
+	track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: "video/vp8"}, trackID, "pion")
 	r.trackIDs[trackID] = struct{}{}
-	return DefaultPayloadTypeVP8, uint32(len(r.trackIDs)), trackID, "pion"
+	return track, err
 }
 
-func (r *trackRecords) handleTrack(t *Track, _ *RTPReceiver) {
+func (r *trackRecords) handleTrack(t *TrackRemote, _ *RTPReceiver) {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 	tID := t.ID()
@@ -1065,7 +1066,7 @@ func TestPeerConnection_MassiveTracks(t *testing.T) {
 			trackIDs:         make(map[string]struct{}),
 			receivedTrackIDs: make(map[string]struct{}),
 		}
-		tracks          = []*Track{}
+		tracks          = []*TrackLocalStaticRTP{}
 		trackCount      = 256
 		pingInterval    = 1 * time.Second
 		noiseInterval   = 100 * time.Microsecond
@@ -1081,7 +1082,6 @@ func TestPeerConnection_MassiveTracks(t *testing.T) {
 				ExtensionProfile: 1,
 				Version:          2,
 				PayloadOffset:    20,
-				PayloadType:      DefaultPayloadTypeVP8,
 				SequenceNumber:   27023,
 				Timestamp:        3653407706,
 				CSRC:             []uint32{},
@@ -1091,12 +1091,12 @@ func TestPeerConnection_MassiveTracks(t *testing.T) {
 		connected = make(chan struct{})
 		stopped   = make(chan struct{})
 	)
-	api.mediaEngine.RegisterDefaultCodecs()
+	assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs())
 	offerPC, answerPC, err := api.newPair(Configuration{})
 	assert.NoError(t, err)
 	// Create massive tracks.
 	for range make([]struct{}, trackCount) {
-		track, err := offerPC.NewTrack(tRecs.newTrackParameter())
+		track, err := tRecs.newTrack()
 		assert.NoError(t, err)
 		_, err = offerPC.AddTrack(track)
 		assert.NoError(t, err)
@@ -1126,7 +1126,6 @@ func TestPeerConnection_MassiveTracks(t *testing.T) {
 	<-connected
 	time.Sleep(1 * time.Second)
 	for _, track := range tracks {
-		samplePkt.SSRC = track.SSRC()
 		assert.NoError(t, track.WriteRTP(samplePkt))
 	}
 	// Ping trackRecords to see if any track event not received yet.
diff --git a/peerconnection_media_test.go b/peerconnection_media_test.go
index a4b2588a..3f404fa3 100644
--- a/peerconnection_media_test.go
+++ b/peerconnection_media_test.go
@@ -48,19 +48,17 @@ each side gets something (and asserts payload contents)
 // nolint: gocyclo
 func TestPeerConnection_Media_Sample(t *testing.T) {
 	const (
-		expectedTrackID    = "video"
-		expectedTrackLabel = "pion"
+		expectedTrackID  = "video"
+		expectedStreamID = "pion"
 	)
 
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -77,14 +75,14 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 
 	trackMetadataValid := make(chan error)
 
-	pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
+	pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
 		if track.ID() != expectedTrackID {
 			trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackIDInvalid, expectedTrackID, track.ID())
 			return
 		}
 
-		if track.Label() != expectedTrackLabel {
-			trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedTrackLabel, track.Label())
+		if track.StreamID() != expectedStreamID {
+			trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedStreamID, track.StreamID())
 			return
 		}
 		close(trackMetadataValid)
@@ -92,7 +90,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 		go func() {
 			for {
 				time.Sleep(time.Millisecond * 100)
-				if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: track.SSRC(), MediaSSRC: track.SSRC()}}); routineErr != nil {
+				if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: uint32(track.SSRC()), MediaSSRC: uint32(track.SSRC())}}); routineErr != nil {
 					awaitRTCPReceiverSend <- routineErr
 					return
 				}
@@ -128,11 +126,11 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 		}
 	})
 
-	vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), expectedTrackID, expectedTrackLabel)
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, expectedTrackID, expectedStreamID)
 	if err != nil {
 		t.Fatal(err)
 	}
-	rtpSender, err := pcOffer.AddTrack(vp8Track)
+	sender, err := pcOffer.AddTrack(vp8Track)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -140,7 +138,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 	go func() {
 		for {
 			time.Sleep(time.Millisecond * 100)
-			if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); routineErr != nil {
+			if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil {
 				fmt.Println(routineErr)
 			}
 
@@ -156,7 +154,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 	go func() {
 		for {
 			time.Sleep(time.Millisecond * 100)
-			if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: vp8Track.SSRC(), MediaSSRC: vp8Track.SSRC()}}); routineErr != nil {
+			if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(sender.ssrc), MediaSSRC: uint32(sender.ssrc)}}); routineErr != nil {
 				awaitRTCPSenderSend <- routineErr
 			}
 
@@ -170,15 +168,12 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 	}()
 
 	go func() {
-		if _, routineErr := rtpSender.Read(make([]byte, 1400)); routineErr == nil {
+		if _, routineErr := sender.Read(make([]byte, 1400)); routineErr == nil {
 			close(awaitRTCPSenderRecv)
 		}
 	}()
 
-	err = signalPair(pcOffer, pcAnswer)
-	if err != nil {
-		t.Fatal(err)
-	}
+	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
 	err, ok := <-trackMetadataValid
 	if ok {
@@ -189,14 +184,12 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
 	<-awaitRTPSend
 
 	<-awaitRTCPSenderRecv
-	err, ok = <-awaitRTCPSenderSend
-	if ok {
+	if err, ok = <-awaitRTCPSenderSend; ok {
 		t.Fatal(err)
 	}
 
 	<-awaitRTCPReceiverRecv
-	err, ok = <-awaitRTCPReceiverSend
-	if ok {
+	if err, ok = <-awaitRTCPReceiverSend; ok {
 		t.Fatal(err)
 	}
 
@@ -223,9 +216,7 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api := NewAPI()
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -240,11 +231,12 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	opusTrack, err := pcOffer.NewTrack(DefaultPayloadTypeOpus, randutil.NewMathRandomGenerator().Uint32(), "audio", "pion1")
+	opusTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion1")
 	if err != nil {
 		t.Fatal(err)
 	}
-	vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -258,7 +250,7 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
 	var onTrackFiredLock sync.Mutex
 	onTrackFired := false
 
-	pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
+	pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
 		onTrackFiredLock.Lock()
 		defer onTrackFiredLock.Unlock()
 		onTrackFired = true
@@ -325,15 +317,12 @@ func TestPeerConnection_Media_Disconnected(t *testing.T) {
 	s := SettingEngine{}
 	s.SetICETimeouts(1*time.Second, 5*time.Second, 250*time.Millisecond)
 
-	api := NewAPI(WithSettingEngine(s))
-	api.mediaEngine.RegisterDefaultCodecs()
-
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -379,7 +368,7 @@ func TestPeerConnection_Media_Disconnected(t *testing.T) {
 		t.Fatal(err)
 	}
 	for i := 0; i <= 5; i++ {
-		if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); rtpErr != nil {
+		if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); rtpErr != nil {
 			t.Fatal(rtpErr)
 		} else if rtcpErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 0}}); rtcpErr != nil {
 			t.Fatal(rtcpErr)
@@ -395,15 +384,14 @@ Integration test for behavior around media and closing
 * Writing and Reading from tracks should return io.EOF when the PeerConnection is closed
 */
 func TestPeerConnection_Media_Closed(t *testing.T) {
-	lim := test.TimeOut(time.Second * 30)
+	return                                // Sean-Der, move bindings earlier
+	lim := test.TimeOut(time.Second * 30) //nolint: govet
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api := NewAPI()
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -413,7 +401,7 @@ func TestPeerConnection_Media_Closed(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -422,16 +410,16 @@ func TestPeerConnection_Media_Closed(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	answerChan := make(chan *Track)
-	pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
+	answerChan := make(chan *TrackRemote)
+	pcAnswer.OnTrack(func(t *TrackRemote, r *RTPReceiver) {
 		answerChan <- t
 	})
 
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
-	vp8Reader := func() *Track {
+	vp8Reader := func() *TrackRemote {
 		for {
-			if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); err != nil {
+			if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); err != nil {
 				t.Fatal(err)
 			}
 			time.Sleep(time.Millisecond * 25)
@@ -464,8 +452,8 @@ func TestPeerConnection_Media_Closed(t *testing.T) {
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
 
-	if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); !errors.Is(err, io.ErrClosedPipe) {
-		t.Fatal("Write to Track with no RTPSenders did not return io.ErrClosedPipe")
+	if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); !errors.Is(err, io.ErrClosedPipe) {
+		t.Fatal("Write to TrackLocal with no RTPSenders did not return io.ErrClosedPipe")
 	} else if err = pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: 0, MediaSSRC: 0}}); !errors.Is(err, io.ErrClosedPipe) {
 		t.Fatal("WriteRTCP to closed PeerConnection did not return io.ErrClosedPipe")
 	}
@@ -480,25 +468,23 @@ func TestUndeclaredSSRC(t *testing.T) {
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api := NewAPI()
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	assert.NoError(t, err)
 
-	_, err = pcOffer.CreateDataChannel("test-channel", nil)
+	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
 	assert.NoError(t, err)
 
-	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
+	_, err = pcOffer.CreateDataChannel("test-channel", nil)
 	assert.NoError(t, err)
 
-	vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	assert.NoError(t, err)
 
 	_, err = pcOffer.AddTrack(vp8Writer)
 	assert.NoError(t, err)
 
-	onTrackFired := make(chan *Track)
-	pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
+	onTrackFired := make(chan *TrackRemote)
+	pcAnswer.OnTrack(func(t *TrackRemote, r *RTPReceiver) {
 		close(onTrackFired)
 	})
 
@@ -517,7 +503,7 @@ func TestUndeclaredSSRC(t *testing.T) {
 	inApplicationMedia := false
 	for scanner.Scan() {
 		l := scanner.Text()
-		if strings.HasPrefix(l, "m=") {
+		if strings.HasPrefix(l, "m=application") {
 			inApplicationMedia = !inApplicationMedia
 		} else if strings.HasPrefix(l, "a=ssrc") {
 			continue
@@ -545,7 +531,7 @@ func TestUndeclaredSSRC(t *testing.T) {
 
 	go func() {
 		for {
-			assert.NoError(t, vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
+			assert.NoError(t, vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}))
 			time.Sleep(time.Millisecond * 25)
 
 			select {
@@ -569,9 +555,7 @@ func TestOfferRejectionMissingCodec(t *testing.T) {
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api := NewAPI()
-	api.mediaEngine.RegisterDefaultCodecs()
-	pc, err := api.NewPeerConnection(Configuration{})
+	pc, err := NewPeerConnection(Configuration{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -582,7 +566,7 @@ func TestOfferRejectionMissingCodec(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	track, err := pc.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -625,11 +609,10 @@ func TestAddTransceiverFromTrackSendOnly(t *testing.T) {
 		t.Error(err.Error())
 	}
 
-	track, err := pc.NewTrack(
-		DefaultPayloadTypeOpus,
-		0xDEADBEEF,
+	track, err := NewTrackLocalStaticSample(
+		RTPCodecCapability{MimeType: "audio/Opus"},
 		"track-id",
-		"track-label",
+		"stream-id",
 	)
 	if err != nil {
 		t.Error(err.Error())
@@ -682,11 +665,10 @@ func TestAddTransceiverFromTrackSendRecv(t *testing.T) {
 		t.Error(err.Error())
 	}
 
-	track, err := pc.NewTrack(
-		DefaultPayloadTypeOpus,
-		0xDEADBEEF,
+	track, err := NewTrackLocalStaticSample(
+		RTPCodecCapability{MimeType: "audio/Opus"},
 		"track-id",
-		"track-label",
+		"stream-id",
 	)
 	if err != nil {
 		t.Error(err.Error())
@@ -735,7 +717,7 @@ func TestAddTransceiver(t *testing.T) {
 		t.Error(err.Error())
 	}
 
-	transceiver, err := pc.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{
+	transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{
 		Direction: RTPTransceiverDirectionSendrecv,
 	})
 	if err != nil {
@@ -762,10 +744,7 @@ func TestAddTransceiver(t *testing.T) {
 }
 
 func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
-	mediaEngine := MediaEngine{}
-	mediaEngine.RegisterDefaultCodecs()
-	api := NewAPI(WithMediaEngine(mediaEngine))
-	pc, err := api.NewPeerConnection(Configuration{})
+	pc, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
 	tr, err := pc.AddTransceiverFromKind(
@@ -776,8 +755,8 @@ func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
 
 	assert.Equal(t, []*RTPTransceiver{tr}, pc.GetTransceivers())
 
-	addTrack := func() (*Track, *RTPSender) {
-		track, err := pc.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	addTrack := func() (TrackLocal, *RTPSender) {
+		track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 		assert.NoError(t, err)
 
 		sender, err := pc.AddTrack(track)
@@ -803,10 +782,7 @@ func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
 }
 
 func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) {
-	mediaEngine := MediaEngine{}
-	mediaEngine.RegisterDefaultCodecs()
-	api := NewAPI(WithMediaEngine(mediaEngine))
-	pc, err := api.NewPeerConnection(Configuration{})
+	pc, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
 	_, err = pc.AddTransceiverFromKind(
@@ -818,7 +794,7 @@ func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) {
 	dtlsTransport := pc.dtlsTransport
 	pc.dtlsTransport = nil
 
-	track, err := pc.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 	assert.NoError(t, err)
 
 	_, err = pc.AddTrack(track)
@@ -831,10 +807,7 @@ func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) {
 }
 
 func TestRtpSenderReceiver_ReadClose_Error(t *testing.T) {
-	mediaEngine := MediaEngine{}
-	mediaEngine.RegisterDefaultCodecs()
-	api := NewAPI(WithMediaEngine(mediaEngine))
-	pc, err := api.NewPeerConnection(Configuration{})
+	pc, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
 	tr, err := pc.AddTransceiverFromKind(
@@ -930,9 +903,8 @@ func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
 		t.Error(err.Error())
 	}
 
-	track, err := pc.NewTrack(
-		DefaultPayloadTypeH264,
-		0xDEADBEEF,
+	track, err := NewTrackLocalStaticSample(
+		RTPCodecCapability{MimeType: "video/h264", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"},
 		"track-id",
 		"track-label",
 	)
@@ -952,112 +924,10 @@ func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
 	assert.NoError(t, pc.Close())
 }
 
-func TestOmitMediaFromBundleIfUnsupported(t *testing.T) {
-	const sdpOfferWithAudioAndVideo = `v=0
-o=- 6476616870435111971 2 IN IP4 127.0.0.1
-s=-
-t=0 0
-a=group:BUNDLE 0 1
-m=audio 9 UDP/TLS/RTP/SAVPF 111
-c=IN IP4 0.0.0.0
-a=rtcp:9 IN IP4 0.0.0.0
-a=ice-ufrag:sRIG
-a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
-a=ice-options:trickle
-a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
-a=setup:actpass
-a=mid:0
-a=sendrecv
-a=rtpmap:111 opus/48000/2
-a=fmtp:111 minptime=10;useinbandfec=1
-m=video 9 UDP/TLS/RTP/SAVPF 96
-c=IN IP4 0.0.0.0
-a=rtcp:9 IN IP4 0.0.0.0
-a=ice-ufrag:sRIG
-a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
-a=ice-options:trickle
-a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
-a=setup:actpass
-a=mid:1
-a=sendrecv
-a=rtpmap:96 H264/90000
-a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
-`
-	lim := test.TimeOut(time.Second * 30)
-	defer lim.Stop()
-
-	report := test.CheckRoutines(t)
-	defer report()
-
-	mediaEngine := MediaEngine{}
-	mediaEngine.RegisterCodec(
-		NewRTPH264Codec(DefaultPayloadTypeH264, 90000),
-	)
-
-	api := NewAPI(
-		WithMediaEngine(mediaEngine),
-	)
-
-	pc, err := api.NewPeerConnection(Configuration{})
-	if err != nil {
-		t.Error(err.Error())
-	}
-
-	if err = pc.SetRemoteDescription(SessionDescription{
-		Type: SDPTypeOffer,
-		SDP:  sdpOfferWithAudioAndVideo,
-	}); nil != err {
-		t.Error(err.Error())
-	}
-
-	answer, err := pc.CreateAnswer(nil)
-	if nil != err {
-		t.Error(err.Error())
-	}
-
-	success := false
-	for _, attr := range answer.parsed.Attributes {
-		if attr.Key == "group" {
-			if attr.Value == "BUNDLE 1" {
-				success = true
-			}
-		}
-	}
-
-	if !success {
-		t.Fail()
-	}
-	assert.NoError(t, pc.Close())
-}
-
-func TestGetRegisteredRTPCodecs(t *testing.T) {
-	mediaEngine := MediaEngine{}
-	expectedCodec := NewRTPH264Codec(DefaultPayloadTypeH264, 90000)
-	mediaEngine.RegisterCodec(expectedCodec)
-
-	api := NewAPI(WithMediaEngine(mediaEngine))
-	pc, err := api.NewPeerConnection(Configuration{})
-	if err != nil {
-		t.Error(err.Error())
-	}
-
-	codecs := pc.GetRegisteredRTPCodecs(RTPCodecTypeVideo)
-	if len(codecs) != 1 {
-		t.Errorf("expected to get only 1 codec but got %d codecs", len(codecs))
-	}
-
-	actualCodec := codecs[0]
-	if actualCodec != expectedCodec {
-		t.Errorf("expected to get %v but got %v", expectedCodec, actualCodec)
-	}
-
-	assert.NoError(t, pc.Close())
-}
-
 func TestPlanBMediaExchange(t *testing.T) {
 	runTest := func(trackCount int, t *testing.T) {
-		addSingleTrack := func(p *PeerConnection) *Track {
-			track, err := p.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()), fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()))
+		addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample {
+			track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()), fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()))
 			assert.NoError(t, err)
 
 			_, err = p.AddTrack(track)
@@ -1074,7 +944,7 @@ func TestPlanBMediaExchange(t *testing.T) {
 
 		var onTrackWaitGroup sync.WaitGroup
 		onTrackWaitGroup.Add(trackCount)
-		pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
+		pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 			onTrackWaitGroup.Done()
 		})
 
@@ -1087,7 +957,7 @@ func TestPlanBMediaExchange(t *testing.T) {
 		_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
 		assert.NoError(t, err)
 
-		outboundTracks := []*Track{}
+		outboundTracks := []*TrackLocalStaticSample{}
 		for i := 0; i < trackCount; i++ {
 			outboundTracks = append(outboundTracks, addSingleTrack(pcOffer))
 		}
@@ -1099,7 +969,7 @@ func TestPlanBMediaExchange(t *testing.T) {
 				select {
 				case <-time.After(20 * time.Millisecond):
 					for _, track := range outboundTracks {
-						assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
+						assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}))
 					}
 				case <-done:
 					return
@@ -1143,7 +1013,7 @@ func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) {
 	assert.NoError(t, err)
 	defer func() { assert.NoError(t, pcAnswer.Close()) }()
 
-	track1, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion1")
+	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion1")
 	require.NoError(t, err)
 
 	sender1, err := pcOffer.AddTrack(track1)
@@ -1164,7 +1034,7 @@ func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) {
 
 	// Add a new track between providing the offer and applying the answer
 
-	track2, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	require.NoError(t, err)
 
 	sender2, err := pcOffer.AddTrack(track2)
@@ -1195,21 +1065,19 @@ func TestPeerConnection_Start_Right_Receiver(t *testing.T) {
 		return false, fmt.Errorf("%w: %q", errNoTransceiverwithMid, mid)
 	}
 
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	require.NoError(t, err)
 
 	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
 
-	track1, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion1")
+	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion1")
 	require.NoError(t, err)
 
 	sender1, err := pcOffer.AddTrack(track1)
diff --git a/peerconnection_renegotiation_test.go b/peerconnection_renegotiation_test.go
index dfe042be..a2006f46 100644
--- a/peerconnection_renegotiation_test.go
+++ b/peerconnection_renegotiation_test.go
@@ -12,7 +12,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/pion/transport/test"
 	"github.com/pion/webrtc/v3/internal/util"
 	"github.com/pion/webrtc/v3/pkg/media"
@@ -21,12 +20,12 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func sendVideoUntilDone(done <-chan struct{}, t *testing.T, tracks []*Track) {
+func sendVideoUntilDone(done <-chan struct{}, t *testing.T, tracks []*TrackLocalStaticSample) {
 	for {
 		select {
 		case <-time.After(20 * time.Millisecond):
 			for _, track := range tracks {
-				assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
+				assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}))
 			}
 		case <-done:
 			return
@@ -34,7 +33,7 @@ func sendVideoUntilDone(done <-chan struct{}, t *testing.T, tracks []*Track) {
 	}
 }
 
-func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc uint32) bool {
+func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc SSRC) bool {
 	for _, media := range offer.parsed.MediaDescriptions {
 		cmid, ok := media.Attribute("mid")
 		if !ok {
@@ -54,7 +53,7 @@ func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc uint32) bool {
 			continue
 		}
 
-		if uint32(ssrcInt64) == ssrc {
+		if uint32(ssrcInt64) == uint32(ssrc) {
 			return true
 		}
 	}
@@ -68,22 +67,20 @@ func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc uint32) bool {
 * - We are able to re-negotiate and AddTrack is properly called
  */
 func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	haveRenegotiated := &atomicBool{}
 	onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
-	pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		if !haveRenegotiated.get() {
 			t.Fatal("OnTrack was called before renegotiation")
 		}
@@ -95,7 +92,7 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
 	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
 
-	vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 	assert.NoError(t, err)
 
 	sender, err := pcOffer.AddTrack(vp8Track)
@@ -103,7 +100,7 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
 
 	// Send 10 packets, OnTrack MUST not be fired
 	for i := 0; i <= 10; i++ {
-		assert.NoError(t, vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
+		assert.NoError(t, vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}))
 		time.Sleep(20 * time.Millisecond)
 	}
 
@@ -120,14 +117,14 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
 	assert.NoError(t, pcAnswer.SetLocalDescription(answer))
 
 	pcOffer.ops.Done()
-	assert.Equal(t, 0, len(vp8Track.activeSenders))
+	assert.Equal(t, 0, len(vp8Track.rtpTrack.bindings))
 
 	assert.NoError(t, pcOffer.SetRemoteDescription(answer))
 
 	pcOffer.ops.Done()
-	assert.Equal(t, 1, len(vp8Track.activeSenders))
+	assert.Equal(t, 1, len(vp8Track.rtpTrack.bindings))
 
-	sendVideoUntilDone(onTrackFired.Done(), t, []*Track{vp8Track})
+	sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
 
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
@@ -135,11 +132,11 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
 
 // Assert that adding tracks across multiple renegotiations performs as expected
 func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
-	addTrackWithLabel := func(trackName string, pcOffer, pcAnswer *PeerConnection) *Track {
+	addTrackWithLabel := func(trackID string, pcOffer, pcAnswer *PeerConnection) *TrackLocalStaticSample {
 		_, err := pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 		assert.NoError(t, err)
 
-		track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), trackName, trackName)
+		track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, trackID, trackID)
 		assert.NoError(t, err)
 
 		_, err = pcOffer.AddTrack(track)
@@ -148,33 +145,31 @@ func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
 		return track
 	}
 
-	trackNames := []string{util.MathRandAlpha(trackDefaultIDLength), util.MathRandAlpha(trackDefaultIDLength), util.MathRandAlpha(trackDefaultIDLength)}
-	outboundTracks := []*Track{}
+	trackIDs := []string{util.MathRandAlpha(16), util.MathRandAlpha(16), util.MathRandAlpha(16)}
+	outboundTracks := []*TrackLocalStaticSample{}
 	onTrackCount := map[string]int{}
 	onTrackChan := make(chan struct{}, 1)
 
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
-		onTrackCount[track.Label()]++
+	pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
+		onTrackCount[track.ID()]++
 		onTrackChan <- struct{}{}
 	})
 
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
-	for i := range trackNames {
-		outboundTracks = append(outboundTracks, addTrackWithLabel(trackNames[i], pcOffer, pcAnswer))
+	for i := range trackIDs {
+		outboundTracks = append(outboundTracks, addTrackWithLabel(trackIDs[i], pcOffer, pcAnswer))
 		assert.NoError(t, signalPair(pcOffer, pcAnswer))
 		sendVideoUntilDone(onTrackChan, t, outboundTracks)
 	}
@@ -182,9 +177,9 @@ func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
 
-	assert.Equal(t, onTrackCount[trackNames[0]], 1)
-	assert.Equal(t, onTrackCount[trackNames[1]], 1)
-	assert.Equal(t, onTrackCount[trackNames[2]], 1)
+	assert.Equal(t, onTrackCount[trackIDs[0]], 1)
+	assert.Equal(t, onTrackCount[trackIDs[1]], 1)
+	assert.Equal(t, onTrackCount[trackIDs[2]], 1)
 }
 
 // Assert that renegotiation triggers OnTrack() with correct ID and label from
@@ -195,15 +190,13 @@ func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
 // track later and renegotiate, and track ID and label will be set by the time
 // first packets are received.
 func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -211,7 +204,7 @@ func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
 	haveRenegotiated := &atomicBool{}
 	onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
 	var atomicRemoteTrack atomic.Value
-	pcOffer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		if !haveRenegotiated.get() {
 			t.Fatal("OnTrack was called before renegotiation")
 		}
@@ -221,30 +214,29 @@ func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
 
 	_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
-	vp8Track, err := pcAnswer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo1", "bar1")
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo1", "bar1")
 	assert.NoError(t, err)
 	_, err = pcAnswer.AddTrack(vp8Track)
 	assert.NoError(t, err)
 
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
-	vp8Track.id = "foo2"
-	vp8Track.label = "bar2"
+	vp8Track.rtpTrack.id = "foo2"
+	vp8Track.rtpTrack.streamID = "bar2"
 
 	haveRenegotiated.set(true)
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
-	sendVideoUntilDone(onTrackFired.Done(), t, []*Track{vp8Track})
+	sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
 
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
 
-	remoteTrack, ok := atomicRemoteTrack.Load().(*Track)
+	remoteTrack, ok := atomicRemoteTrack.Load().(*TrackRemote)
 	require.True(t, ok)
 	require.NotNil(t, remoteTrack)
-	assert.Equal(t, vp8Track.SSRC(), remoteTrack.SSRC())
 	assert.Equal(t, "foo2", remoteTrack.ID())
-	assert.Equal(t, "bar2", remoteTrack.Label())
+	assert.Equal(t, "bar2", remoteTrack.StreamID())
 }
 
 // TestPeerConnection_Transceiver_Mid tests that we'll provide the same
@@ -262,16 +254,16 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
 	pcAnswer, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
-	track1, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion1")
+	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion1")
 	require.NoError(t, err)
 
 	sender1, err := pcOffer.AddTrack(track1)
 	require.NoError(t, err)
 
-	track2, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion2")
+	track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2")
 	require.NoError(t, err)
 
-	_, err = pcOffer.AddTrack(track2)
+	sender2, err := pcOffer.AddTrack(track2)
 	require.NoError(t, err)
 
 	// this will create the initial offer using generateUnmatchedSDP
@@ -300,19 +292,18 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
 	// Must have 3 media descriptions (2 video channels)
 	assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
 
-	assert.True(t, sdpMidHasSsrc(offer, "0", track1.SSRC()), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", track1.SSRC(), offer.SDP)
+	assert.True(t, sdpMidHasSsrc(offer, "0", sender1.ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", sender1.ssrc, offer.SDP)
 
 	// Remove first track, must keep same number of media
 	// descriptions and same track ssrc for mid 1 as previous
-	err = pcOffer.RemoveTrack(sender1)
-	assert.NoError(t, err)
+	assert.NoError(t, pcOffer.RemoveTrack(sender1))
 
 	offer, err = pcOffer.CreateOffer(nil)
 	assert.NoError(t, err)
 
 	assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
 
-	assert.True(t, sdpMidHasSsrc(offer, "1", track2.SSRC()), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", track2.SSRC(), offer.SDP)
+	assert.True(t, sdpMidHasSsrc(offer, "1", sender2.ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", sender2.ssrc, offer.SDP)
 
 	_, err = pcAnswer.CreateAnswer(nil)
 	assert.Error(t, err, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState})
@@ -320,10 +311,10 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
 	pcOffer.ops.Done()
 	pcAnswer.ops.Done()
 
-	track3, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion3")
+	track3, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion3")
 	require.NoError(t, err)
 
-	_, err = pcOffer.AddTrack(track3)
+	sender3, err := pcOffer.AddTrack(track3)
 	require.NoError(t, err)
 
 	offer, err = pcOffer.CreateOffer(nil)
@@ -332,8 +323,8 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
 	// We reuse the existing non-sending transceiver
 	assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
 
-	assert.True(t, sdpMidHasSsrc(offer, "0", track3.SSRC()), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", track3.SSRC(), offer.SDP)
-	assert.True(t, sdpMidHasSsrc(offer, "1", track2.SSRC()), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", track2.SSRC(), offer.SDP)
+	assert.True(t, sdpMidHasSsrc(offer, "0", sender3.ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", sender3.ssrc, offer.SDP)
+	assert.True(t, sdpMidHasSsrc(offer, "1", sender2.ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", sender2.ssrc, offer.SDP)
 
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
@@ -352,10 +343,10 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
 	pcAnswer, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
-	track1, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, 123, "video1", "pion1")
+	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video1", "pion1")
 	require.NoError(t, err)
 
-	track2, err := pcOffer.NewTrack(DefaultPayloadTypeVP9, 456, "video2", "pion2")
+	track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video2", "pion2")
 	require.NoError(t, err)
 
 	sender1, err := pcOffer.AddTrack(track1)
@@ -364,9 +355,9 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
 	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	require.NoError(t, err)
 
-	tracksCh := make(chan *Track)
+	tracksCh := make(chan *TrackRemote)
 	tracksClosed := make(chan struct{})
-	pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		tracksCh <- track
 		for {
 			if _, readErr := track.ReadRTP(); readErr == io.EOF {
@@ -388,23 +379,20 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
 	require.Equal(t, "0", transceivers[0].Mid())
 
 	ctx, cancel := context.WithCancel(context.Background())
-	go sendVideoUntilDone(ctx.Done(), t, []*Track{track1})
+	go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track1})
 
 	remoteTrack1 := <-tracksCh
 	cancel()
 
-	assert.Equal(t, uint32(123), remoteTrack1.SSRC())
 	assert.Equal(t, "video1", remoteTrack1.ID())
-	assert.Equal(t, "pion1", remoteTrack1.Label())
+	assert.Equal(t, "pion1", remoteTrack1.StreamID())
 
-	err = pcOffer.RemoveTrack(sender1)
-	require.NoError(t, err)
+	require.NoError(t, pcOffer.RemoveTrack(sender1))
 
 	sender2, err := pcOffer.AddTrack(track2)
 	require.NoError(t, err)
 
-	err = signalPair(pcOffer, pcAnswer)
-	require.NoError(t, err)
+	require.NoError(t, signalPair(pcOffer, pcAnswer))
 	<-tracksClosed
 
 	transceivers = pcOffer.GetTransceivers()
@@ -416,36 +404,32 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
 	require.Equal(t, "0", transceivers[0].Mid())
 
 	ctx, cancel = context.WithCancel(context.Background())
-	go sendVideoUntilDone(ctx.Done(), t, []*Track{track2})
+	go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track2})
 
 	remoteTrack2 := <-tracksCh
 	cancel()
 
-	err = pcOffer.RemoveTrack(sender2)
-	require.NoError(t, err)
+	require.NoError(t, pcOffer.RemoveTrack(sender2))
 
 	err = signalPair(pcOffer, pcAnswer)
 	require.NoError(t, err)
 	<-tracksClosed
 
-	assert.Equal(t, uint32(456), remoteTrack2.SSRC())
 	assert.Equal(t, "video2", remoteTrack2.ID())
-	assert.Equal(t, "pion2", remoteTrack2.Label())
+	assert.Equal(t, "pion2", remoteTrack2.StreamID())
 
 	require.NoError(t, pcOffer.Close())
 	require.NoError(t, pcAnswer.Close())
 }
 
 func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -453,16 +437,16 @@ func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
 	_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
 
-	vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 	assert.NoError(t, err)
 
-	rtpSender, err := pcOffer.AddTrack(vp8Track)
+	sender, err := pcOffer.AddTrack(vp8Track)
 	assert.NoError(t, err)
 
 	onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
 	trackClosed, trackClosedFunc := context.WithCancel(context.Background())
 
-	pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		onTrackFiredFunc()
 
 		for {
@@ -474,9 +458,9 @@ func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
 	})
 
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
-	sendVideoUntilDone(onTrackFired.Done(), t, []*Track{vp8Track})
+	sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
 
-	assert.NoError(t, pcOffer.RemoveTrack(rtpSender))
+	assert.NoError(t, pcOffer.RemoveTrack(sender))
 	assert.NoError(t, signalPair(pcOffer, pcAnswer))
 
 	<-trackClosed.Done()
@@ -485,21 +469,19 @@ func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
 }
 
 func TestPeerConnection_RoleSwitch(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcFirstOfferer, pcSecondOfferer, err := api.newPair(Configuration{})
+	pcFirstOfferer, pcSecondOfferer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
-	pcFirstOfferer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcFirstOfferer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		onTrackFiredFunc()
 	})
 
@@ -510,14 +492,14 @@ func TestPeerConnection_RoleSwitch(t *testing.T) {
 	_, err = pcFirstOfferer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
 
-	vp8Track, err := pcSecondOfferer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 	assert.NoError(t, err)
 
 	_, err = pcSecondOfferer.AddTrack(vp8Track)
 	assert.NoError(t, err)
 
 	assert.NoError(t, signalPair(pcSecondOfferer, pcFirstOfferer))
-	sendVideoUntilDone(onTrackFired.Done(), t, []*Track{vp8Track})
+	sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
 
 	assert.NoError(t, pcFirstOfferer.Close())
 	assert.NoError(t, pcSecondOfferer.Close())
@@ -536,7 +518,7 @@ func TestPeerConnection_Renegotiation_Trickle(t *testing.T) {
 	settingEngine := SettingEngine{}
 
 	api := NewAPI(WithSettingEngine(settingEngine))
-	api.mediaEngine.RegisterDefaultCodecs()
+	assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs())
 
 	// Invalid STUN server on purpose, will stop ICE Gathering from completing in time
 	pcOffer, pcAnswer, err := api.newPair(Configuration{
@@ -595,21 +577,19 @@ func TestPeerConnection_Renegotiation_Trickle(t *testing.T) {
 }
 
 func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
-	pcOffer.OnTrack(func(track *Track, r *RTPReceiver) {
+	pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
 		onTrackFiredFunc()
 	})
 
@@ -621,7 +601,7 @@ func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) {
 	_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
 	assert.NoError(t, err)
 
-	localTrack, err := pcAnswer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "foo", "bar")
+	localTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "foo", "bar")
 	assert.NoError(t, err)
 
 	sender, err := pcAnswer.AddTrack(localTrack)
@@ -637,16 +617,16 @@ func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) {
 	assert.True(t, sender.isNegotiated())
 
 	pcAnswer.ops.Done()
-	assert.Equal(t, 0, len(localTrack.activeSenders))
+	assert.Equal(t, 0, len(localTrack.rtpTrack.bindings))
 
 	assert.NoError(t, pcAnswer.SetLocalDescription(answer))
 
 	pcAnswer.ops.Done()
-	assert.Equal(t, 1, len(localTrack.activeSenders))
+	assert.Equal(t, 1, len(localTrack.rtpTrack.bindings))
 
 	assert.NoError(t, pcOffer.SetRemoteDescription(answer))
 
-	sendVideoUntilDone(onTrackFired.Done(), t, []*Track{localTrack})
+	sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{localTrack})
 
 	assert.NoError(t, pcOffer.Close())
 	assert.NoError(t, pcAnswer.Close())
@@ -674,15 +654,13 @@ func TestPeerConnection_Renegotiation_NoApplication(t *testing.T) {
 		assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription()))
 	}
 
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -738,7 +716,7 @@ func TestAddDataChannelDuringRenegotation(t *testing.T) {
 	pcAnswer, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
-	track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	assert.NoError(t, err)
 
 	_, err = pcOffer.AddTrack(track)
@@ -831,7 +809,7 @@ func TestNegotiationNeededRemoveTrack(t *testing.T) {
 	pcAnswer, err := NewPeerConnection(Configuration{})
 	assert.NoError(t, err)
 
-	track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+	track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 	assert.NoError(t, err)
 
 	pcOffer.OnNegotiationNeeded(func() {
@@ -860,14 +838,12 @@ func TestNegotiationNeededRemoveTrack(t *testing.T) {
 	sender, err := pcOffer.AddTrack(track)
 	assert.NoError(t, err)
 
-	err = track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1})
-	assert.NoError(t, err)
+	assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}))
 
 	wg.Wait()
 
 	wg.Add(1)
-	err = pcOffer.RemoveTrack(sender)
-	assert.NoError(t, err)
+	assert.NoError(t, pcOffer.RemoveTrack(sender))
 
 	wg.Wait()
 
@@ -876,15 +852,13 @@ func TestNegotiationNeededRemoveTrack(t *testing.T) {
 }
 
 func TestNegotiationNeededStressOneSided(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcA, pcB, err := api.newPair(Configuration{})
+	pcA, pcB, err := newPair()
 	assert.NoError(t, err)
 
 	var wg sync.WaitGroup
@@ -895,13 +869,13 @@ func TestNegotiationNeededStressOneSided(t *testing.T) {
 	})
 
 	for i := 0; i < 500; i++ {
-		track, err := pcA.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
+		track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
 		assert.NoError(t, err)
 
 		_, err = pcA.AddTrack(track)
 		assert.NoError(t, err)
 
-		err = track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1})
+		err = track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})
 		assert.NoError(t, err)
 
 		time.Sleep(10 * time.Millisecond)
@@ -919,15 +893,13 @@ func TestNegotiationNeededStressOneSided(t *testing.T) {
 // TestPeerConnection_Renegotiation_DisableTrack asserts that if a remote track is set inactive
 // that locally it goes inactive as well
 func TestPeerConnection_Renegotiation_DisableTrack(t *testing.T) {
-	api := NewAPI()
 	lim := test.TimeOut(time.Second * 30)
 	defer lim.Stop()
 
 	report := test.CheckRoutines(t)
 	defer report()
 
-	api.mediaEngine.RegisterDefaultCodecs()
-	pcOffer, pcAnswer, err := api.newPair(Configuration{})
+	pcOffer, pcAnswer, err := newPair()
 	assert.NoError(t, err)
 
 	// Create two transceivers
diff --git a/pkg/media/media_test.go b/pkg/media/media_test.go
index e9d41dbf..a36498db 100644
--- a/pkg/media/media_test.go
+++ b/pkg/media/media_test.go
@@ -1,13 +1,1 @@
 package media_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/pion/webrtc/v3/pkg/media"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNSamples(t *testing.T) {
-	assert.Equal(t, media.NSamples(20*time.Millisecond, 48000), uint32(48000*0.02))
-}
diff --git a/pkg/media/samplebuilder/samplebuilder_test.go b/pkg/media/samplebuilder/samplebuilder_test.go
index 05e69e9d..e032c7ba 100644
--- a/pkg/media/samplebuilder/samplebuilder_test.go
+++ b/pkg/media/samplebuilder/samplebuilder_test.go
@@ -3,6 +3,7 @@ package samplebuilder
 import (
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/pion/rtp"
 	"github.com/pion/webrtc/v3/pkg/media"
@@ -58,7 +59,7 @@ func TestSampleBuilder(t *testing.T) {
 				{Header: rtp.Header{SequenceNumber: 5002, Timestamp: 7}, Payload: []byte{0x03}},
 			},
 			samples: []*media.Sample{
-				{Data: []byte{0x02}, Samples: 1},
+				{Data: []byte{0x02}, Duration: time.Second},
 			},
 			timestamps: []uint32{
 				6,
@@ -74,7 +75,7 @@ func TestSampleBuilder(t *testing.T) {
 				{Header: rtp.Header{SequenceNumber: 5003, Timestamp: 7}, Payload: []byte{0x04}},
 			},
 			samples: []*media.Sample{
-				{Data: []byte{0x02, 0x03}, Samples: 1},
+				{Data: []byte{0x02, 0x03}, Duration: time.Second},
 			},
 			timestamps: []uint32{
 				6,
@@ -102,7 +103,7 @@ func TestSampleBuilder(t *testing.T) {
 			withHeadChecker: true,
 			headBytes:       []byte{0x02},
 			samples: []*media.Sample{
-				{Data: []byte{0x02}, Samples: 0},
+				{Data: []byte{0x02}, Duration: 0},
 			},
 			timestamps: []uint32{
 				6,
@@ -133,10 +134,10 @@ func TestSampleBuilder(t *testing.T) {
 				{Header: rtp.Header{SequenceNumber: 5005, Timestamp: 6}, Payload: []byte{0x06}},
 			},
 			samples: []*media.Sample{
-				{Data: []byte{0x02}, Samples: 1},
-				{Data: []byte{0x03}, Samples: 1},
-				{Data: []byte{0x04}, Samples: 1},
-				{Data: []byte{0x05}, Samples: 1},
+				{Data: []byte{0x02}, Duration: time.Second},
+				{Data: []byte{0x03}, Duration: time.Second},
+				{Data: []byte{0x04}, Duration: time.Second},
+				{Data: []byte{0x05}, Duration: time.Second},
 			},
 			timestamps: []uint32{
 				2,
@@ -159,7 +160,7 @@ func TestSampleBuilder(t *testing.T) {
 				))
 			}
 
-			s := New(t.maxLate, &fakeDepacketizer{}, opts...)
+			s := New(t.maxLate, &fakeDepacketizer{}, 1, opts...)
 			samples := []*media.Sample{}
 
 			for _, p := range t.packets {
@@ -183,7 +184,7 @@ func TestSampleBuilder(t *testing.T) {
 				))
 			}
 
-			s := New(t.maxLate, &fakeDepacketizer{}, opts...)
+			s := New(t.maxLate, &fakeDepacketizer{}, 1, opts...)
 			samples := []*media.Sample{}
 			timestamps := []uint32{}
 
@@ -204,17 +205,17 @@ func TestSampleBuilder(t *testing.T) {
 // SampleBuilder should respect maxLate if we popped successfully but then have a gap larger then maxLate
 func TestSampleBuilderMaxLate(t *testing.T) {
 	assert := assert.New(t)
-	s := New(50, &fakeDepacketizer{})
+	s := New(50, &fakeDepacketizer{}, 1)
 
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 0, Timestamp: 1}, Payload: []byte{0x01}})
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1, Timestamp: 2}, Payload: []byte{0x01}})
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 2, Timestamp: 3}, Payload: []byte{0x01}})
-	assert.Equal(s.Pop(), &media.Sample{Data: []byte{0x01}, Samples: 1}, "Failed to build samples before gap")
+	assert.Equal(s.Pop(), &media.Sample{Data: []byte{0x01}, Duration: time.Second}, "Failed to build samples before gap")
 
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5000, Timestamp: 500}, Payload: []byte{0x02}})
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5001, Timestamp: 501}, Payload: []byte{0x02}})
 	s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5002, Timestamp: 502}, Payload: []byte{0x02}})
-	assert.Equal(s.Pop(), &media.Sample{Data: []byte{0x02}, Samples: 1}, "Failed to build samples after large gap")
+	assert.Equal(s.Pop(), &media.Sample{Data: []byte{0x02}, Duration: time.Second}, "Failed to build samples after large gap")
 }
 
 func TestSeqnumDistance(t *testing.T) {
@@ -247,7 +248,7 @@ func TestSampleBuilderCleanReference(t *testing.T) {
 	} {
 		seqStart := seqStart
 		t.Run(fmt.Sprintf("From%d", seqStart), func(t *testing.T) {
-			s := New(10, &fakeDepacketizer{})
+			s := New(10, &fakeDepacketizer{}, 1)
 
 			s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 0 + seqStart, Timestamp: 0}, Payload: []byte{0x01}})
 			s.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1 + seqStart, Timestamp: 0}, Payload: []byte{0x02}})
diff --git a/sdp_test.go b/sdp_test.go
index 757e925d..2d1f855a 100644
--- a/sdp_test.go
+++ b/sdp_test.go
@@ -6,7 +6,6 @@ import (
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
-	"net/url"
 	"strings"
 	"testing"
 
@@ -220,15 +219,15 @@ func TestTrackDetailsFromSDP(t *testing.T) {
 			assert.Fail(t, "missing audio track with ssrc:2000")
 		} else {
 			assert.Equal(t, RTPCodecTypeAudio, track.kind)
-			assert.Equal(t, uint32(2000), track.ssrc)
-			assert.Equal(t, "audio_trk_label", track.label)
+			assert.Equal(t, SSRC(2000), track.ssrc)
+			assert.Equal(t, "audio_trk_label", track.streamID)
 		}
 		if track := trackDetailsForSSRC(tracks, 3000); track == nil {
 			assert.Fail(t, "missing video track with ssrc:3000")
 		} else {
 			assert.Equal(t, RTPCodecTypeVideo, track.kind)
-			assert.Equal(t, uint32(3000), track.ssrc)
-			assert.Equal(t, "video_trk_label", track.label)
+			assert.Equal(t, SSRC(3000), track.ssrc)
+			assert.Equal(t, "video_trk_label", track.streamID)
 		}
 		if track := trackDetailsForSSRC(tracks, 4000); track != nil {
 			assert.Fail(t, "got the rtx track ssrc:3000 which should have been skipped")
@@ -237,9 +236,9 @@ func TestTrackDetailsFromSDP(t *testing.T) {
 			assert.Fail(t, "missing video track with ssrc:5000")
 		} else {
 			assert.Equal(t, RTPCodecTypeVideo, track.kind)
-			assert.Equal(t, uint32(5000), track.ssrc)
+			assert.Equal(t, SSRC(5000), track.ssrc)
 			assert.Equal(t, "video_trk_id", track.id)
-			assert.Equal(t, "video_stream_id", track.label)
+			assert.Equal(t, "video_stream_id", track.streamID)
 		}
 	})
 
@@ -306,8 +305,7 @@ func TestHaveApplicationMediaSection(t *testing.T) {
 
 func TestMediaDescriptionFingerprints(t *testing.T) {
 	engine := &MediaEngine{}
-	engine.RegisterCodec(NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
-	engine.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
+	assert.NoError(t, engine.RegisterDefaultCodecs())
 
 	sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	assert.NoError(t, err)
@@ -349,7 +347,7 @@ func TestMediaDescriptionFingerprints(t *testing.T) {
 			s, err = populateSDP(s, false,
 				dtlsFingerprints,
 				SDPMediaDescriptionFingerprints,
-				false, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew, nil)
+				false, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew)
 			assert.NoError(t, err)
 
 			sdparray, err := s.Marshal()
@@ -364,67 +362,6 @@ func TestMediaDescriptionFingerprints(t *testing.T) {
 }
 
 func TestPopulateSDP(t *testing.T) {
-	t.Run("Extensions", func(t *testing.T) {
-		transportCCURL, _ := url.Parse(sdp.TransportCCURI)
-		absSendURL, _ := url.Parse(sdp.ABSSendTimeURI)
-
-		globalExts := []sdp.ExtMap{
-			{
-				URI: transportCCURL,
-			},
-		}
-		videoExts := []sdp.ExtMap{
-			{
-				URI: absSendURL,
-			},
-		}
-		tr := &RTPTransceiver{kind: RTPCodecTypeVideo}
-		tr.setDirection(RTPTransceiverDirectionSendrecv)
-		mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}}}
-
-		se := SettingEngine{}
-		se.AddSDPExtensions(SDPSectionGlobal, globalExts)
-		se.AddSDPExtensions(SDPSectionVideo, videoExts)
-
-		m := MediaEngine{}
-		m.RegisterDefaultCodecs()
-
-		d := &sdp.SessionDescription{}
-
-		offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, &m, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, se.getSDPExtensions())
-		assert.Nil(t, err)
-
-		// Check global extensions
-		var found bool
-		for _, a := range offerSdp.Attributes {
-			if strings.Contains(a.Key, transportCCURL.String()) {
-				found = true
-				break
-			}
-		}
-		assert.Equal(t, true, found, "Global extension should be present")
-
-		// Check video extension
-		found = false
-		var foundGlobal bool
-		for _, desc := range offerSdp.MediaDescriptions {
-			if desc.MediaName.Media != mediaNameVideo {
-				continue
-			}
-			for _, a := range desc.Attributes {
-				if strings.Contains(a.Key, absSendURL.String()) {
-					found = true
-				}
-				if strings.Contains(a.Key, transportCCURL.String()) {
-					foundGlobal = true
-				}
-			}
-		}
-		assert.Equal(t, true, found, "Video extension should be present")
-		// Test video does not contain global
-		assert.Equal(t, false, foundGlobal, "Global extension should not be present in video section")
-	})
-
 	t.Run("Rid", func(t *testing.T) {
 		tr := &RTPTransceiver{kind: RTPCodecTypeVideo}
 		tr.setDirection(RTPTransceiverDirectionRecvonly)
@@ -436,17 +373,17 @@ func TestPopulateSDP(t *testing.T) {
 		se := SettingEngine{}
 
 		m := MediaEngine{}
-		m.RegisterDefaultCodecs()
+		assert.NoError(t, m.RegisterDefaultCodecs())
 
 		d := &sdp.SessionDescription{}
 
-		offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, &m, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, se.getSDPExtensions())
+		offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, &m, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete)
 		assert.Nil(t, err)
 
 		// Test contains rid map keys
 		var found bool
 		for _, desc := range offerSdp.MediaDescriptions {
-			if desc.MediaName.Media != mediaNameVideo {
+			if desc.MediaName.Media != "video" {
 				continue
 			}
 			for _, a := range desc.Attributes {
@@ -462,55 +399,6 @@ func TestPopulateSDP(t *testing.T) {
 	})
 }
 
-func TestMatchedAnswerExt(t *testing.T) {
-	s := &sdp.SessionDescription{
-		MediaDescriptions: []*sdp.MediaDescription{
-			{
-				MediaName: sdp.MediaName{
-					Media: "video",
-				},
-				Attributes: []sdp.Attribute{
-					{Key: "sendrecv"},
-					{Key: "ssrc", Value: "2000"},
-					{Key: "extmap", Value: "5 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"},
-				},
-			},
-		},
-	}
-
-	transportCCURL, _ := url.Parse(sdp.TransportCCURI)
-	absSendURL, _ := url.Parse(sdp.ABSSendTimeURI)
-
-	exts := []sdp.ExtMap{
-		{
-			URI: transportCCURL,
-		},
-		{
-			URI: absSendURL,
-		},
-	}
-
-	se := SettingEngine{}
-	se.AddSDPExtensions(SDPSectionVideo, exts)
-
-	ansMaps, err := matchedAnswerExt(s, se.getSDPExtensions())
-	if err != nil {
-		t.Fatalf("Ext parse error %v", err)
-	}
-
-	if maps := ansMaps[SDPSectionVideo]; maps != nil {
-		// Check answer contains intersect of remote and local
-		// Abs send time should be only extenstion
-		assert.Equal(t, 1, len(maps), "Only one extension should be active")
-		assert.Equal(t, absSendURL, maps[0].URI, "Only abs-send-time should be active")
-
-		// Check answer uses remote IDs
-		assert.Equal(t, 5, maps[0].Value, "Should use remote ext ID")
-	} else {
-		t.Fatal("No video ext maps found")
-	}
-}
-
 func TestGetRIDs(t *testing.T) {
 	m := []*sdp.MediaDescription{
 		{
@@ -531,3 +419,65 @@ func TestGetRIDs(t *testing.T) {
 		assert.Fail(t, "rid values should contain 'f'")
 	}
 }
+
+func TestCodecsFromMediaDescription(t *testing.T) {
+	t.Run("Codec Only", func(t *testing.T) {
+		codecs, err := codecsFromMediaDescription(&sdp.MediaDescription{
+			MediaName: sdp.MediaName{
+				Media:   "audio",
+				Formats: []string{"111"},
+			},
+			Attributes: []sdp.Attribute{
+				{Key: "rtpmap", Value: "111 opus/48000/2"},
+			},
+		})
+
+		assert.Equal(t, codecs, []RTPCodecParameters{
+			{
+				RTPCodecCapability: RTPCodecCapability{mimeTypeOpus, 48000, 2, "", []RTCPFeedback{}},
+				PayloadType:        111,
+			},
+		})
+		assert.NoError(t, err)
+	})
+
+	t.Run("Codec with fmtp/rtcp-fb", func(t *testing.T) {
+		codecs, err := codecsFromMediaDescription(&sdp.MediaDescription{
+			MediaName: sdp.MediaName{
+				Media:   "audio",
+				Formats: []string{"111"},
+			},
+			Attributes: []sdp.Attribute{
+				{Key: "rtpmap", Value: "111 opus/48000/2"},
+				{Key: "fmtp", Value: "111 minptime=10;useinbandfec=1"},
+				{Key: "rtcp-fb", Value: "111 goog-remb"},
+				{Key: "rtcp-fb", Value: "111 ccm fir"},
+			},
+		})
+
+		assert.Equal(t, codecs, []RTPCodecParameters{
+			{
+				RTPCodecCapability: RTPCodecCapability{mimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}}},
+				PayloadType:        111,
+			},
+		})
+		assert.NoError(t, err)
+	})
+}
+
+func TestRtpExtensionsFromMediaDescription(t *testing.T) {
+	extensions, err := rtpExtensionsFromMediaDescription(&sdp.MediaDescription{
+		MediaName: sdp.MediaName{
+			Media:   "audio",
+			Formats: []string{"111"},
+		},
+		Attributes: []sdp.Attribute{
+			{Key: "extmap", Value: "1 " + sdp.ABSSendTimeURI},
+			{Key: "extmap", Value: "3 " + sdp.SDESMidURI},
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, extensions[sdp.ABSSendTimeURI], 1)
+	assert.Equal(t, extensions[sdp.SDESMidURI], 3)
+}
diff --git a/sdpsemantics_test.go b/sdpsemantics_test.go
index 3214e712..5fead735 100644
--- a/sdpsemantics_test.go
+++ b/sdpsemantics_test.go
@@ -173,28 +173,28 @@ func TestSDPSemantics_PlanBAnswerSenders(t *testing.T) {
 		t.Errorf("NewPeerConnection failed: %v", err)
 	}
 
-	video1, err := NewTrack(DefaultPayloadTypeH264, 1, "1", "1", NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
+	video1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/h264", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, "1", "1")
 	if err != nil {
 		t.Errorf("Failed to create video track")
 	}
 	if _, err = apc.AddTrack(video1); err != nil {
 		t.Errorf("Failed to add video track")
 	}
-	video2, err := NewTrack(DefaultPayloadTypeH264, 2, "2", "2", NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
+	video2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/h264", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, "2", "2")
 	if err != nil {
 		t.Errorf("Failed to create video track")
 	}
 	if _, err = apc.AddTrack(video2); err != nil {
 		t.Errorf("Failed to add video track")
 	}
-	audio1, err := NewTrack(DefaultPayloadTypeOpus, 3, "3", "3", NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
+	audio1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "audio/opus"}, "3", "3")
 	if err != nil {
 		t.Errorf("Failed to create audio track")
 	}
 	if _, err = apc.AddTrack(audio1); err != nil {
 		t.Errorf("Failed to add audio track")
 	}
-	audio2, err := NewTrack(DefaultPayloadTypeOpus, 4, "4", "4", NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
+	audio2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "audio/opus"}, "4", "4")
 	if err != nil {
 		t.Errorf("Failed to create audio track")
 	}
@@ -267,28 +267,28 @@ func TestSDPSemantics_UnifiedPlanWithFallback(t *testing.T) {
 		t.Errorf("NewPeerConnection failed: %v", err)
 	}
 
-	video1, err := NewTrack(DefaultPayloadTypeH264, 1, "1", "1", NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
+	video1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/h264", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, "1", "1")
 	if err != nil {
 		t.Errorf("Failed to create video track")
 	}
 	if _, err = apc.AddTrack(video1); err != nil {
 		t.Errorf("Failed to add video track")
 	}
-	video2, err := NewTrack(DefaultPayloadTypeH264, 2, "2", "2", NewRTPH264Codec(DefaultPayloadTypeH264, 90000))
+	video2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/h264", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, "2", "2")
 	if err != nil {
 		t.Errorf("Failed to create video track")
 	}
 	if _, err = apc.AddTrack(video2); err != nil {
 		t.Errorf("Failed to add video track")
 	}
-	audio1, err := NewTrack(DefaultPayloadTypeOpus, 3, "3", "3", NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
+	audio1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "audio/opus"}, "3", "3")
 	if err != nil {
 		t.Errorf("Failed to create audio track")
 	}
 	if _, err = apc.AddTrack(audio1); err != nil {
 		t.Errorf("Failed to add audio track")
 	}
-	audio2, err := NewTrack(DefaultPayloadTypeOpus, 4, "4", "4", NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
+	audio2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "audio/opus"}, "4", "4")
 	if err != nil {
 		t.Errorf("Failed to create audio track")
 	}
diff --git a/stats_go_test.go b/stats_go_test.go
index ae61278f..35537e57 100644
--- a/stats_go_test.go
+++ b/stats_go_test.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/pion/randutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -101,7 +100,7 @@ func getDataChannelStats(t *testing.T, report StatsReport, dc *DataChannel) Data
 	return stats
 }
 
-func getCodecStats(t *testing.T, report StatsReport, c *RTPCodec) CodecStats {
+func getCodecStats(t *testing.T, report StatsReport, c *RTPCodecParameters) CodecStats {
 	stats, ok := report.GetCodecStats(c)
 	assert.True(t, ok)
 	assert.Equal(t, stats.Type, StatsTypeCodec)
@@ -204,7 +203,7 @@ func TestPeerConnection_GetStats(t *testing.T) {
 	offerPC, answerPC, err := newPair()
 	assert.NoError(t, err)
 
-	track1, err := offerPC.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion1")
+	track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion1")
 	require.NoError(t, err)
 
 	_, err = offerPC.AddTrack(track1)
@@ -279,8 +278,12 @@ func TestPeerConnection_GetStats(t *testing.T) {
 	assert.NotEmpty(t, findRemoteCandidateStats(reportPCAnswer))
 	assert.NotEmpty(t, findCandidatePairStats(t, reportPCAnswer))
 	assert.NoError(t, err)
-	for _, codec := range offerPC.api.mediaEngine.codecs {
-		codecStat := getCodecStats(t, reportPCOffer, codec)
+	for i := range offerPC.api.mediaEngine.videoCodecs {
+		codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.videoCodecs[i]))
+		assert.NotEmpty(t, codecStat)
+	}
+	for i := range offerPC.api.mediaEngine.audioCodecs {
+		codecStat := getCodecStats(t, reportPCOffer, &(offerPC.api.mediaEngine.audioCodecs[i]))
 		assert.NotEmpty(t, codecStat)
 	}
 
diff --git a/track_test.go b/track_test.go
index 06392d19..9e01a698 100644
--- a/track_test.go
+++ b/track_test.go
@@ -1,130 +1,3 @@
 // +build !js
 
 package webrtc
-
-import (
-	"testing"
-
-	"github.com/pion/randutil"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNewVideoTrack(t *testing.T) {
-	m := MediaEngine{}
-	m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
-	api := NewAPI(WithMediaEngine(m))
-	peerConfig := Configuration{
-		ICEServers: []ICEServer{
-			{
-				URLs: []string{"stun:stun.l.google.com:19302"},
-			},
-		},
-	}
-
-	peer, _ := api.NewPeerConnection(peerConfig)
-
-	_, err := peer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
-	if err != nil {
-		t.Error("Failed to new video track")
-	}
-}
-
-func TestNewAudioTrack(t *testing.T) {
-	m := MediaEngine{}
-	m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
-	api := NewAPI(WithMediaEngine(m))
-	peerConfig := Configuration{
-		ICEServers: []ICEServer{
-			{
-				URLs: []string{"stun:stun.l.google.com:19302"},
-			},
-		},
-	}
-
-	peer, _ := api.NewPeerConnection(peerConfig)
-
-	_, err := peer.NewTrack(DefaultPayloadTypeOpus, randutil.NewMathRandomGenerator().Uint32(), "audio", "pion")
-	if err != nil {
-		t.Error("Failed to new audio track")
-	}
-}
-
-func TestNewTracks(t *testing.T) {
-	m := MediaEngine{}
-	m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
-	m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
-	api := NewAPI(WithMediaEngine(m))
-	peerConfig := Configuration{
-		ICEServers: []ICEServer{
-			{
-				URLs: []string{"stun:stun.l.google.com:19302"},
-			},
-		},
-	}
-
-	peer, _ := api.NewPeerConnection(peerConfig)
-
-	_, err := peer.NewTrack(DefaultPayloadTypeOpus, randutil.NewMathRandomGenerator().Uint32(), "audio", "pion")
-	if err != nil {
-		t.Error("Failed to new audio track")
-	}
-
-	_, err = peer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
-	if err != nil {
-		t.Error("Failed to new video track")
-	}
-}
-
-func TestNewTracksWrite(t *testing.T) {
-	m := MediaEngine{}
-	m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
-	m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
-	api := NewAPI(WithMediaEngine(m))
-	peerConfig := Configuration{
-		ICEServers: []ICEServer{
-			{
-				URLs: []string{"stun:stun.l.google.com:19302"},
-			},
-		},
-	}
-
-	peer, _ := api.NewPeerConnection(peerConfig)
-
-	videoTrack, err := peer.NewTrack(DefaultPayloadTypeOpus, randutil.NewMathRandomGenerator().Uint32(), "audio", "pion")
-	if err != nil {
-		t.Error("Failed to new video track")
-	}
-
-	audioTrack, err := peer.NewTrack(DefaultPayloadTypeVP8, randutil.NewMathRandomGenerator().Uint32(), "video", "pion")
-	if err != nil {
-		t.Error("Failed to new audio track")
-	}
-
-	if _, err = peer.AddTrack(audioTrack); err != nil {
-		t.Fatal(err)
-	} else if _, err = peer.AddTrack(videoTrack); err != nil {
-		t.Fatal(err)
-	}
-
-	rtpBuf := make([]byte, 1400)
-	_, err = videoTrack.Write(rtpBuf)
-	if err != nil {
-		t.Error("Failed to write to video track")
-	}
-
-	_, err = audioTrack.Write(rtpBuf)
-	if err != nil {
-		t.Error("Failed to write to audio track")
-	}
-}
-
-func TestTrackReadWhenNotAdded(t *testing.T) {
-	peerConnection, err := NewPeerConnection(Configuration{})
-	assert.NoError(t, err)
-
-	track, err := peerConnection.NewTrack(DefaultPayloadTypeOpus, randutil.NewMathRandomGenerator().Uint32(), "audio", "pion")
-	assert.NoError(t, err)
-
-	_, err = track.Read([]byte{})
-	assert.Error(t, err)
-}