Giter Site home page Giter Site logo

sdp-transform's Introduction

SDP Transform

npm status CI codecov

A simple parser and writer of SDP. Defines internal grammar based on RFC4566 - SDP, RFC5245 - ICE, and many more.

For simplicity it will force values that are integers to integers and leave everything else as strings when parsing. The module should be simple to extend or build upon, and is constructed rigorously.

Installation

$ npm install sdp-transform

TypeScript Definitions

Available in the @types/sdp-transform package:

$ npm install -D @types/sdp-transform

Usage

Load using CommonJS syntax or ES6 syntax:

// CommonJS
const sdpTransform = require('sdp-transform');

// ES6
import * as sdpTransform from 'sdp-transform';

Usage - Parser

Pass it an unprocessed SDP string.

const sdpStr = "v=0\r\n\
o=- 20518 0 IN IP4 203.0.113.1\r\n\
s= \r\n\
t=0 0\r\n\
c=IN IP4 203.0.113.1\r\n\
a=ice-ufrag:F7gI\r\n\
a=ice-pwd:x9cml/YzichV2+XlhiMu8g\r\n\
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7\r\n\
m=audio 54400 RTP/SAVPF 0 96\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=rtpmap:96 opus/48000\r\n\
a=ptime:20\r\n\
a=sendrecv\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n\
a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host\r\n\
m=video 55400 RTP/SAVPF 97 98\r\n\
a=rtpmap:97 H264/90000\r\n\
a=fmtp:97 profile-level-id=4d0028;packetization-mode=1\r\n\
a=rtpmap:98 VP8/90000\r\n\
a=sendrecv\r\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host\r\n\
a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host\r\n\
";

const res = sdpTransform.parse(sdpStr);
// =>
{ version: 0,
  origin:
   { username: '-',
     sessionId: 20518,
     sessionVersion: 0,
     netType: 'IN',
     ipVer: 4,
     address: '203.0.113.1' },
  name: '',
  timing: { start: 0, stop: 0 },
  connection: { version: 4, ip: '203.0.113.1' },
  iceUfrag: 'F7gI',
  icePwd: 'x9cml/YzichV2+XlhiMu8g',
  fingerprint:
   { type: 'sha-1',
     hash: '42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7' },
  media:
   [ { rtp: [Object],
       fmtp: [],
       type: 'audio',
       port: 54400,
       protocol: 'RTP/SAVPF',
       payloads: '0 96',
       ptime: 20,
       direction: 'sendrecv',
       candidates: [Object] },
     { rtp: [Object],
       fmtp: [Object],
       type: 'video',
       port: 55400,
       protocol: 'RTP/SAVPF',
       payloads: '97 98',
       direction: 'sendrecv',
       candidates: [Object] } ] }


// each media line is parsed into the following format
res.media[1];
// =>
{ rtp:
   [ { payload: 97,
       codec: 'H264',
       rate: 90000 },
     { payload: 98,
       codec: 'VP8',
       rate: 90000 } ],
  fmtp:
   [ { payload: 97,
       config: 'profile-level-id=4d0028;packetization-mode=1' } ],
  type: 'video',
  port: 55400,
  protocol: 'RTP/SAVPF',
  payloads: '97 98',
  direction: 'sendrecv',
  candidates:
   [ { foundation: 0,
       component: 1,
       transport: 'UDP',
       priority: 2113667327,
       ip: '203.0.113.1',
       port: 55400,
       type: 'host' },
     { foundation: 1,
       component: 2,
       transport: 'UDP',
       priority: 2113667326,
       ip: '203.0.113.1',
       port: 55401,
       type: 'host' } ] }

In this example, only slightly dodgy string coercion case here is for candidates[i].foundation, which can be a string, but in this case can be equally parsed as an integer.

Parser Postprocessing

No excess parsing is done to the raw strings apart from maybe coercing to ints, because the writer is built to be the inverse of the parser. That said, a few helpers have been built in:

parseParams()

Parses fmtp.config and others such as rid.params and returns an object with all the params in a key/value fashion.

// to parse the fmtp.config from the previous example
sdpTransform.parseParams(res.media[1].fmtp[0].config);
// =>
{ 'profile-level-id': '4d0028',
  'packetization-mode': 1 }

parsePayloads()

Returns an array with all the payload advertised in the main m-line.

// what payloads where actually advertised in the main m-line ?
sdpTransform.parsePayloads(res.media[1].payloads);
// =>
[97, 98]

parseImageAttributes()

Parses Generic Image Attributes. Must be provided with the attrs1 or attrs2 string of a a=imageattr line. Returns an array of key/value objects.

// a=imageattr:97 send [x=1280,y=720] recv [x=1280,y=720] [x=320,y=180]
sdpTransform.parseImageAttributes(res.media[1].imageattrs[0].attrs2)
// =>
[ {'x': 1280, 'y': 720}, {'x': 320, 'y': 180} ]

parseSimulcastStreamList()

Parses simulcast streams/formats. Must be provided with the list1 or list2 string of the a=simulcast line.

Returns an array of simulcast streams. Each entry is an array of alternative simulcast formats, which are objects with two keys:

  • scid: Simulcast identifier
  • paused: Whether the simulcast format is paused
// a=simulcast:send 1,~4;2;3 recv c
sdpTransform.parseSimulcastStreamList(res.media[1].simulcast.list1);
// =>
[
  // First simulcast stream (two alternative formats)
  [ {scid: 1, paused: false}, {scid: 4, paused: true} ],
  // Second simulcast stream
  [ {scid: 2, paused: false} ],
  // Third simulcast stream
  [ {scid: 3, paused: false} ]
]

Usage - Writer

The writer is the inverse of the parser, and will need a struct equivalent to the one returned by it.

sdpTransform.write(res).split('\r\n'); // res parsed above
// =>
[ 'v=0',
  'o=- 20518 0 IN IP4 203.0.113.1',
  's= ',
  'c=IN IP4 203.0.113.1',
  't=0 0',
  'a=ice-ufrag:F7gI',
  'a=ice-pwd:x9cml/YzichV2+XlhiMu8g',
  'a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7',
  'm=audio 54400 RTP/SAVPF 0 96',
  'a=rtpmap:0 PCMU/8000',
  'a=rtpmap:96 opus/48000',
  'a=ptime:20',
  'a=sendrecv',
  'a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host',
  'a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host',
  'm=video 55400 RTP/SAVPF 97 98',
  'a=rtpmap:97 H264/90000',
  'a=rtpmap:98 VP8/90000',
  'a=fmtp:97 profile-level-id=4d0028;packetization-mode=1',
  'a=sendrecv',
  'a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host',
  'a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host' ]

The only thing different from the original input is we follow the order specified by the SDP RFC, and we will always do so.

License

MIT-Licensed. See LICENSE file for details.

sdp-transform's People

Contributors

486 avatar 6uliver avatar alexanderklintstrom avatar antonshwarts avatar bgrozev avatar clux avatar cptlemming avatar damonoehlman avatar dubistkomisch avatar egzonzeneli avatar gpolitis avatar gregpabian avatar ibc avatar jeremy-j-ackso avatar kekkokk avatar legastero avatar lmoj avatar manuc66 avatar murillo128 avatar njh avatar saghul avatar virtuacoplenny avatar zxcpoiu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sdp-transform's Issues

Unable to parse large sessionId values

Due to the maximum integer representation in JS currently being 2^53 (9007199254740992) large session values are being rounded. An example of this was captured in my latest testing with chrome:

INPUT:

v=0
o=- 3026724848284511757 2 IN IP4 127.0.0.1

OUTPUT:

v=0
o=- 3026724848284511700 2 IN IP4 127.0.0.1

Notice that 3026724848284511757 has been converted to 3026724848284511700. This is the same value you will get if you run the following code:

parseInt('3026724848284511757', 10);
// => 3026724848284511700

Not exactly sure what the best way to handle this is. You could try and use one of the JS bignum implementations, but most of those use system level libraries and will limit portability to the browser.

An alternative is not to parse into a numeric representation, but I'm not sure how you feel about that...

use proper token definition in regexes

In many places we use (\w*) or less commonly (.*) as a shorthand for what rfc4566's definition of a token:

   token-char =          %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39
                         / %x41-5A / %x5E-7E

   token =               1*(token-char)

worth going through and making this conform to the standard where it references token as it would make the library more resilient against future updates to various identifiers undoubtedly introduced in the future.

First seen in #53 ( pr #54 )

"a=ice-lite" session level attribute is ignored

Hi!

The following is the original SDP:

v=0
o=- 3622532974 3622532974 IN IP4 192.168.100.100
s=-
c=IN IP4 192.168.100.100
t=0 0
a=ice-lite
m=audio 10018 RTP/SAVPF 8 0 101
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=direction:both
a=sendrecv
a=rtcp-mux
a=setup:actpass
a=fingerprint:sha-256 CE:17:02:86:E2:E8:B0:EF:F9:F3:3F:82:8A:A6:F0:EF:30:73:1D:5D:B3:5A:60:D7:AC:FE:F0:E3:DF:D5:D9:7B
a=ice-ufrag:nXET
a=ice-pwd:d0iwx/Qam8JnuvL+wkcXee
a=candidate:X 1 UDP 659136 192.168.100.100 10018 typ host
a=candidate:X 2 UDP 659134 192.168.100.100 10019 typ host

And this is the SDP once parsed and written back:

v=0
o=- 3622532974 3622532974 IN IP4 192.168.100.100
s=-
c=IN IP4 192.168.100.100
t=0 0
m=audio 10018 RTP/SAVPF 8 0 101
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=setup:actpass
a=sendrecv
a=ice-ufrag:nXET
a=ice-pwd:d0iwx/Qam8JnuvL+wkcXee
a=fingerprint:sha-256 CE:17:02:86:E2:E8:B0:EF:F9:F3:3F:82:8A:A6:F0:EF:30:73:1D:5D:B3:5A:60:D7:AC:FE:F0:E3:DF:D5:D9:7B
a=candidate:X 1 UDP 659136 192.168.100.100 10018 typ host
a=candidate:X 2 UDP 659134 192.168.100.100 10019 typ host
a=rtcp-mux

"a=ice-lite" line is not present anymore

sdp transform parse webrtc sdp error

Error handling SDP offer: TypeError: sdp.split is not a function
at Object.exports.parse (/Users/xiang/Work/soupserver/node_modules/sdp-transform/lib/parser.js:46:7)

i did not dig yet, just report here.

Wrong syntax for Opus codec line

The proper way of indicating Opus codec in the SDP is:

a=rtpmap:NNN opus/48000/2

and that is what Chrome generates. But when sdp-transform handles the SDP it converts such a line into:

a=rtpmap:NNN opus/48000

(which is the old syntax for Opus codec)

Missing fields in ICE candidates

Original ICE candidates generated by Chrome 31:

a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60808 typ host generation 0
a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60808 typ host generation 0
a=candidate:3289912957 1 udp 1845501695 XXX.84.77.194 60808 typ srflx raddr 192.168.34.75 rport 60808 generation 0
a=candidate:3289912957 2 udp 1845501695 XXX.84.77.194 60808 typ srflx raddr 192.168.34.75 rport 60808 generation 0
a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host generation 0
a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host generation 0

Candidates when calling "write" in sdp-transform:

a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60808 typ host
a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60808 typ host
a=candidate:3289912957 1 udp 1845501695 XXX.84.77.194 60808 typ srflx
a=candidate:3289912957 2 udp 1845501695 XXX.84.77.194 60808 typ srflx
a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host
a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host

sdp-transform ignores some fields. Those fields are optional but... why to ignore them?

Valid encoding names

Hi there!
We have this line in our sdp data:
a=rtpmap:99 image.png.data/90000
This lib uses a regex that only parses image as the encoding name (and fails to parse the clock rate).
Do you have any specification regarding if image.png.data is a valid encoding name, or if the regex should be updated to allow dots in the encoding name?

tcptype is not being set on write()

After doing a .parse() on a sdp with a candidate that specifies tcptype, doing a write() will cause the tcptype to be blank.

Before:
a=candidate:229815620 1 tcp 1518280447 0.0.0.0 0 typ host tcptype active generation 0

After:
a=candidate:229815620 1 tcp 1518280447 0.0.0.0 0 typ host tcptype generation 0

direction "sendrecv" should be default

Sorry, I am in a bit of hurry but I wanted to report this. I try to do a PR later.

If the offerer wishes to both send and
receive media with its peer, it MAY include an "a=sendrecv"
attribute, or it MAY omit it, since sendrecv is the default.

media.push({rtp: [], fmtp: [], direction: 'sendrecv'});

FEC-FR source group fro Flex FEC produces invalid attribute

When parsing an sdp with a Flex-FEC source group, the attribute is parsed as invalid (I assume it is due to the "-")

v=0
o=- 4327261771880257373 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:ez5G
a=ice-pwd:1F1qS++jzWLSQi0qQDZkX/QV
a=fingerprint:sha-256 D2:FA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:3510681183 cname:loqPWNg7JMmrFUnr
a=ssrc:3510681183 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj 7ea47500-22eb-4815-a899-c74ef321b6ee
a=ssrc:3510681183 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj
a=ssrc:3510681183 label:7ea47500-22eb-4815-a899-c74ef321b6ee
m=video 9 UDP/TLS/RTP/SAVPF 96 98 100 102 127 125 97 99 101 124
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:ez5G
a=ice-pwd:1F1qS++jzWLSQi0qQDZkX/QV
a=fingerprint:sha-256 D2:FA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtpmap:100 H264/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:102 red/90000
a=rtpmap:127 ulpfec/90000
a=rtpmap:125 flexfec-03/90000
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=fmtp:125 repair-window=10000000
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=102
a=ssrc-group:FID 3004364195 1126032854
a=ssrc-group:FEC-FR 3004364195 1080772241
a=ssrc:3004364195 cname:loqPWNg7JMmrFUnr
a=ssrc:3004364195 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25
a=ssrc:3004364195 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj
a=ssrc:3004364195 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25
a=ssrc:1126032854 cname:loqPWNg7JMmrFUnr
a=ssrc:1126032854 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25
a=ssrc:1126032854 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj
a=ssrc:1126032854 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25
a=ssrc:1080772241 cname:loqPWNg7JMmrFUnr
a=ssrc:1080772241 msid:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25
a=ssrc:1080772241 mslabel:xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj
a=ssrc:1080772241 label:cf093ab0-0b28-4930-8fe1-7ca8d529be25

the parsed media sdp.media[1] is as follows:

{ rtp:
   [ { payload: 96, codec: 'VP8', rate: 90000 },
     { payload: 98, codec: 'VP9', rate: 90000 },
     { payload: 100, codec: 'H264', rate: 90000 },
     { payload: 102, codec: 'red', rate: 90000 },
     { payload: 127, codec: 'ulpfec', rate: 90000 },
     { payload: 125, codec: 'flexfec-03', rate: 90000 },
     { payload: 97, codec: 'rtx', rate: 90000 },
     { payload: 99, codec: 'rtx', rate: 90000 },
     { payload: 101, codec: 'rtx', rate: 90000 },
     { payload: 124, codec: 'rtx', rate: 90000 } ],
  fmtp:
   [ { payload: 100,
       config: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f' },
     { payload: 125, config: 'repair-window=10000000' },
     { payload: 97, config: 'apt=96' },
     { payload: 99, config: 'apt=98' },
     { payload: 101, config: 'apt=100' },
     { payload: 124, config: 'apt=102' } ],
  type: 'video',
  port: 9,
  protocol: 'UDP/TLS/RTP/SAVPF',
  payloads: '96 98 100 102 127 125 97 99 101 124',
  connection: { version: 4, ip: '0.0.0.0' },
  rtcp: { port: 9, netType: 'IN', ipVer: 4, address: '0.0.0.0' },
  iceUfrag: 'ez5G',
  icePwd: '1F1qS++jzWLSQi0qQDZkX/QV',
  fingerprint:
   { type: 'sha-256',
     hash: 'D2:FA:0E:C3:22:59:5E:14:95:69:92:3D:13:B4:84:24:2C:C2:A2:C0:3E:FD:34:8E:5E:EA:6F:AF:52:CE:E6:0F' },
  setup: 'actpass',
  mid: 'video',
  ext:
   [ { value: 2, uri: 'urn:ietf:params:rtp-hdrext:toffset' },
     { value: 3,
       uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' },
     { value: 4, uri: 'urn:3gpp:video-orientation' },
     { value: 5,
       uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' },
     { value: 6,
       uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay' } ],
  direction: 'sendrecv',
  rtcpMux: 'rtcp-mux',
  rtcpRsize: 'rtcp-rsize',
  rtcpFb:
   [ { payload: 96, type: 'ccm', subtype: 'fir' },
     { payload: 96, type: 'nack' },
     { payload: 96, type: 'nack', subtype: 'pli' },
     { payload: 96, type: 'goog-remb' },
     { payload: 96, type: 'transport-cc' },
     { payload: 98, type: 'ccm', subtype: 'fir' },
     { payload: 98, type: 'nack' },
     { payload: 98, type: 'nack', subtype: 'pli' },
     { payload: 98, type: 'goog-remb' },
     { payload: 98, type: 'transport-cc' },
     { payload: 100, type: 'ccm', subtype: 'fir' },
     { payload: 100, type: 'nack' },
     { payload: 100, type: 'nack', subtype: 'pli' },
     { payload: 100, type: 'goog-remb' },
     { payload: 100, type: 'transport-cc' },
     { payload: 125, type: 'ccm', subtype: 'fir' },
     { payload: 125, type: 'nack' },
     { payload: 125, type: 'nack', subtype: 'pli' },
     { payload: 125, type: 'goog-remb' },
     { payload: 125, type: 'transport-cc' } ],
  ssrcGroups: [ { semantics: 'FID', ssrcs: '3004364195 1126032854' } ],
  invalid: [ { value: 'ssrc-group:FEC-FR 3004364195 1080772241' } ],
  ssrcs:
   [ { id: 3004364195, attribute: 'cname', value: 'loqPWNg7JMmrFUnr' },
     { id: 3004364195,
       attribute: 'msid',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25' },
     { id: 3004364195,
       attribute: 'mslabel',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj' },
     { id: 3004364195,
       attribute: 'label',
       value: 'cf093ab0-0b28-4930-8fe1-7ca8d529be25' },
     { id: 1126032854, attribute: 'cname', value: 'loqPWNg7JMmrFUnr' },
     { id: 1126032854,
       attribute: 'msid',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25' },
     { id: 1126032854,
       attribute: 'mslabel',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj' },
     { id: 1126032854,
       attribute: 'label',
       value: 'cf093ab0-0b28-4930-8fe1-7ca8d529be25' },
     { id: 1080772241, attribute: 'cname', value: 'loqPWNg7JMmrFUnr' },
     { id: 1080772241,
       attribute: 'msid',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj cf093ab0-0b28-4930-8fe1-7ca8d529be25' },
     { id: 1080772241,
       attribute: 'mslabel',
       value: 'xIKmAwWv4ft4ULxNJGhkHzvPaCkc8EKo4SGj' },
     { id: 1080772241,
       attribute: 'label',
       value: 'cf093ab0-0b28-4930-8fe1-7ca8d529be25' } ] }

parseFmtpConfig() produces non enumerable keys

var params = transform.parseFmtpConfig(res.media[1].fmtp[0].config);

// => { 'profile-level-id': '4d0028',  'packetization-mode': 1 }

The problem is that, somehow, those keys within params are not enumerable so params.hasOwnProperty('profile-level-id') returns false, which is not very good IMHO.

RFC 4574 support for the `label` attribute.

RFC 4574 defines the label attribute.

Currently, a=label:{{X}} attributes are being parsed into the invalid array.

Sample SDP:

v=0
o=someone 1276 1 IN IP4 10.10.10.10
s=-
c=IN IP4 10.10.10.11
t=0 0
m=audio 10494 RTP/AVP 0
a=ptime:20
a=label:1
m=audio 10496 RTP/AVP 0
a=ptime:20
a=label:2

Reproduce:

var { parse: parseSdp } = require('sdp-transform')

var sdp = 'v=0\r\no=someone 1276 1 IN IP4 10.10.10.10\r\ns=-\r\nc=IN IP4 10.10.10.11\r\nt=0 0\r\nm=audio 10494 RTP/AVP 0\r\na=ptime:20\r\na=label:1\r\nm=audio 10496 RTP/AVP 0\r\na=ptime:20\r\na=label:2\r\n'

var parsed = parseSdp(sdp)

console.log(JSON.stringify(parsed, null, 2))

Proposing that the label attribute be upgraded to a recognized attribute (equivalent to ptime, etc.) since it does have an RFC.

Missing lines when writing a parsed SDP

Some lines are missing when writing a parsed SDP. Here I compare an original SDP with the output of the writer, after parsing:

Missing SDP lines

  • a=rtcp:65179 IN IP4 193.84.77.194
  • a=rtpmap:126 telephone-event/8000

Original SDP

var sdpStr = "v=0\r\n\
o=- 1116403644098867264 2 IN IP4 127.0.0.1\r\n\
s=-\r\n\
t=0 0\r\n\
a=group:BUNDLE audio\r\n\
a=msid-semantic: WMS IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag\r\n\
m=audio 65179 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n\
c=IN IP4 193.84.77.194\r\n\
a=rtcp:65179 IN IP4 193.84.77.194\r\n\
a=candidate:1162875081 1 udp 2113937151 192.168.34.75 65179 typ host generation 0\r\n\
a=candidate:1162875081 2 udp 2113937151 192.168.34.75 65179 typ host generation 0\r\n\
a=candidate:3289912957 1 udp 1845501695 193.84.77.194 65179 typ srflx raddr 192.168.34.75 rport 65179 generation 0\r\n\
a=candidate:3289912957 2 udp 1845501695 193.84.77.194 65179 typ srflx raddr 192.168.34.75 rport 65179 generation 0\r\n\
a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host generation 0\r\n\
a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host generation 0\r\n\
a=ice-ufrag:8l7kVjUxmVKEtABZ\r\n\
a=ice-pwd:qHzO/M5U4dShiFVl2pcMo48f\r\n\
a=ice-options:google-ice\r\n\
a=fingerprint:sha-256 F5:68:BF:DC:E9:44:26:33:AC:D3:A0:18:69:1F:BF:28:75:4D:DC:93:31:B9:79:14:89:64:7A:DB:BA:1E:C8:92\r\n\
a=setup:actpass\r\n\
a=mid:audio\r\n\
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n\
a=sendrecv\r\n\
a=rtcp-mux\r\n\
a=crypto:0 AES_CM_128_HMAC_SHA1_32 inline:JZelNX4epoXln6TtTpIEXYpra9YPsQScYIKnoRX0\r\n\
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:AQxyoIJ+/Nsp0nf7ARU0SJrSd6Id/+fJObOCtvRw\r\n\
a=rtpmap:111 opus/48000/2\r\n\
a=fmtp:111 minptime=10\r\n\
a=rtpmap:103 ISAC/16000\r\n\
a=rtpmap:104 ISAC/32000\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=rtpmap:8 PCMA/8000\r\n\
a=rtpmap:106 CN/32000\r\n\
a=rtpmap:105 CN/16000\r\n\
a=rtpmap:13 CN/8000\r\n\
a=rtpmap:126 telephone-event/8000\r\n\
a=maxptime:60\r\n\
a=ssrc:1101659856 cname:DOx6v65Rc6O1hpe3\r\n\
a=ssrc:1101659856 msid:IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag 7721d58d-ccc5-4e36-a927-1e7144bdcf64\r\n\
a=ssrc:1101659856 mslabel:IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag\r\n\
a=ssrc:1101659856 label:7721d58d-ccc5-4e36-a927-1e7144bdcf64\r\n\
";

Resulting SDP from parsing and writing the original

[ 'v=0',
  'o=- 1116403644098867264 2 IN IP4 127.0.0.1',
  's=-',
  't=0 0',
  'a=msid-semantic: WMS IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag',
  'a=group:BUNDLE audio',
  'm=audio 65179 RTP/SAVPF 111 103 104 0 8 106 105 13 126',
  'c=IN IP4 193.84.77.194',
  'a=rtpmap:111 opus/48000/2',
  'a=rtpmap:103 ISAC/16000',
  'a=rtpmap:104 ISAC/32000',
  'a=rtpmap:0 PCMU/8000',
  'a=rtpmap:8 PCMA/8000',
  'a=rtpmap:106 CN/32000',
  'a=rtpmap:105 CN/16000',
  'a=rtpmap:13 CN/8000',
  'a=fmtp:111 minptime=10',
  'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level ',
  'a=crypto:0 AES_CM_128_HMAC_SHA1_32 inline:JZelNX4epoXln6TtTpIEXYpra9YPsQScYIKnoRX0 ',
  'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:AQxyoIJ+/Nsp0nf7ARU0SJrSd6Id/+fJObOCtvRw ',
  'a=setup:actpass',
  'a=mid:audio',
  'a=maxptime:60',
  'a=sendrecv',
  'a=ice-ufrag:8l7kVjUxmVKEtABZ',
  'a=ice-pwd:qHzO/M5U4dShiFVl2pcMo48f',
  'a=fingerprint:sha-256 F5:68:BF:DC:E9:44:26:33:AC:D3:A0:18:69:1F:BF:28:75:4D:DC:93:31:B9:79:14:89:64:7A:DB:BA:1E:C8:92',
  'a=candidate:1162875081 1 udp 2113937151 192.168.34.75 65179 typ host',
  'a=candidate:1162875081 2 udp 2113937151 192.168.34.75 65179 typ host',
  'a=candidate:3289912957 1 udp 1845501695 193.84.77.194 65179 typ srflx',
  'a=candidate:3289912957 2 udp 1845501695 193.84.77.194 65179 typ srflx',
  'a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host',
  'a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host',
  'a=ice-options:google-ice',
  'a=ssrc:1101659856 cname:DOx6v65Rc6O1hpe3',
  'a=ssrc:1101659856 msid:IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag 7721d58d-ccc5-4e36-a927-1e7144bdcf64',
  'a=ssrc:1101659856 mslabel:IdUedrTU3c4oMzy8JRbRsUfeWLGhcyav1mag',
  'a=ssrc:1101659856 label:7721d58d-ccc5-4e36-a927-1e7144bdcf64',
  'a=rtcp-mux',
  '' ]

extend grammar to parse into types

by having a types: [String, Number, Object] array we can more accurately parse the types found in the SDP.

String and Number would allow us not to use toIntIfInt, and Object would allow us to not do stuff like parseFmtpConfig externally.

esm support

Would very much like to just simply use (async) import from a CDN without bundling the code

use of sdp-transform from the browser

Hi clux,

Congratulations for your 'sdp-transform'! It's light and precise! I would like to use it in https://github.com/versatica/JsSIP, which is a client side SIP stack.

Is there any way your module can be used from the browser? If not, I would be happy to contribute to your project in order to make it happen.

Thanks a lot.

a=rtcp-mux-only

There is a draft defining a new SDP media attribute: a=rtcp-mux-only.

I don't request that this library implements all the SDP attributes defined in ongoing drafts but, is there any way to append such an attribute or any unknown/custom one?:

objMedia.rtcpMux = 'rtcp-mux';
objMedia.rtcpMuxOnly = 'rtcp-mux-only';  // This is ignored

Number of audio channels

Hi!

Thanks for writing sap-transform ๐Ÿ™‚

I am trying to use it for parsing Audio over IP (AES67) SDP descriptions - which may include multiple channels of audio as Linear PCM (L16 / L24).

Here is a cut-down example SDP file:

v=0
o=- 1423986 1423994 IN IP4 169.254.98.63
s=AOIP44-serial-1614 : 2
c=IN IP4 239.65.125.63/32
t=0 0
m=audio 5004 RTP/AVP 97
a=recvonly
a=rtpmap:97 L24/48000/2

On parsing the SDP, the result is:

{
  "version": 0,
  "origin": {
    "username": "-",
    "sessionId": 1423986,
    "sessionVersion": 1423994,
    "netType": "IN",
    "ipVer": 4,
    "address": "169.254.98.63"
  },
  "name": "AOIP44-serial-1614 : 2",
  "connection": {
    "version": 4,
    "ip": "239.65.125.63/32"
  },
  "timing": {
    "start": 0,
    "stop": 0
  },
  "media": [{
    "rtp": [{
      "payload": 97,
      "codec": "L24",
      "rate": 48000,
      "encoding": 2
    }],
    "fmtp": [],
    "type": "audio",
    "port": 5004,
    "protocol": "RTP/AVP",
    "payloads": 97,
    "direction": "recvonly"
  }]
}

Initially I was a bit puzzled by how to get the number of audio channels and then I realised that it ended up in encoding.

RFC4566 says:

a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
...
For audio streams, <encoding parameters> indicates the number
of audio channels.  This parameter is OPTIONAL and may be
omitted if the number of channels is one, provided that no
additional parameters are needed.

Do you think it would make sense to set a channels property if the media type is audio?
I guess the disadvantage is that it would require some code and may be a breaking API change?

nick.

Grammar not properly coveraged

Should get notifications about untested rules in grammar.js. Currently, coveralls stay quiet in PRs because the grammar object is always loaded once, and whether their inner rules are used is hidden to jscoverage.

parseFmtpConfig doesn't support base64 encoded strings

H264 media items almost always have a base64-encoded sprop-parameter-sets property:

fmtp:96 packetization-mode=1;profile-level-id=42001F;sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM48gA==

and the parser fails because of the multiple equal signs.

Chrome Parsing Error: Can't find valid SDP line

I'm currently working on a patch that will allow me to specify a custom SDP order to work around #4. I have done this (was trivial) but I'm now hitting a point where chrome is rejecting the following SDP:

v=0
o=- 1275830365995132200 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=group:BUNDLE audio video
m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtpmap:111 opus/48000
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=fmtp:111 minptime=10
 =extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
 =crypto:1 AES_CM_128_HMAC_SHA1_80 inline:vs3EETCyTvSv7ikI0sX5XaBgVHupTBwPEczXMGIe
a=setup:actpass
a=mid:audio
a=maxptime:60
a=sendrecv
a=ice-ufrag:0rc3mwF0TClk0zNg
a=ice-pwd:2CMI6iLFAKA7prtTEhimq2mT
a=fingerprint:sha-1 5D:22:E5:FB:5E:DF:CF:93:88:FD:A7:BB:7B:D6:BE:D8:94:27:9C:60
a=ice-options:google-ice
a=ssrc:3700584440 cname:ciTIotw+HWFexKo8
a=ssrc:3700584440 msid:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYa0
a=ssrc:3700584440 mslabel:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=ssrc:3700584440 label:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYa0
a=rtcp-mux rtcp-mux
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtpmap:100 VP8/90000
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack 
a=rtcp-fb:100 goog 
 =extmap:2 urn:ietf:params:rtp-hdrext:toffset
 =extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
 =crypto:1 AES_CM_128_HMAC_SHA1_80 inline:vs3EETCyTvSv7ikI0sX5XaBgVHupTBwPEczXMGIe
a=setup:actpass
a=mid:video
a=sendrecv
a=ice-ufrag:0rc3mwF0TClk0zNg
a=ice-pwd:2CMI6iLFAKA7prtTEhimq2mT
a=fingerprint:sha-1 5D:22:E5:FB:5E:DF:CF:93:88:FD:A7:BB:7B:D6:BE:D8:94:27:9C:60
a=ice-options:google-ice
a=ssrc:2974251723 cname:ciTIotw+HWFexKo8
a=ssrc:2974251723 msid:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYv0
a=ssrc:2974251723 mslabel:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=ssrc:2974251723 label:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYv0
a=rtcp-mux rtcp-mux
[7:7:1019/162252:ERROR:rtc_peer_connection_handler.cc(431)] Failed to parse SessionDescription. v=0
o=- 1275830365995132200 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=group:BUNDLE audio video
m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtpmap:111 opus/48000
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=fmtp:111 minptime=10
 =extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
 =crypto:1 AES_CM_128_HMAC_SHA1_80 inline:vs3EETCyTvSv7ikI0sX5XaBgVHupTBwPEczXMGIe
a=setup:actpass
a=mid:audio
a=maxptime:60
a=sendrecv
a=ice-ufrag:0rc3mwF0TClk0zNg
a=ice-pwd:2CMI6iLFAKA7prtTEhimq2mT
a=fingerprint:sha-1 5D:22:E5:FB:5E:DF:CF:93:88:FD:A7:BB:7B:D6:BE:D8:94:27:9C:60
a=ice-options:google-ice
a=ssrc:3700584440 cname:ciTIotw+HWFexKo8
a=ssrc:3700584440 msid:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYa0
a=ssrc:3700584440 mslabel:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=ssrc:3700584440 label:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYa0
a=rtcp-mux rtcp-mux
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtpmap:100 VP8/90000
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack 
a=rtcp-fb:100 goog 
 =extmap:2 urn:ietf:params:rtp-hdrext:toffset
 =extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
 =crypto:1 AES_CM_128_HMAC_SHA1_80 inline:vs3EETCyTvSv7ikI0sX5XaBgVHupTBwPEczXMGIe
a=setup:actpass
a=mid:video
a=sendrecv
a=ice-ufrag:0rc3mwF0TClk0zNg
a=ice-pwd:2CMI6iLFAKA7prtTEhimq2mT
a=fingerprint:sha-1 5D:22:E5:FB:5E:DF:CF:93:88:FD:A7:BB:7B:D6:BE:D8:94:27:9C:60
a=ice-options:google-ice
a=ssrc:2974251723 cname:ciTIotw+HWFexKo8
a=ssrc:2974251723 msid:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY 0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYv0
a=ssrc:2974251723 mslabel:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGY
a=ssrc:2974251723 label:0sQ3enfsKYZP00bAd3BTlC0ubpZDBpHVhjGYv0
a=rtcp-mux rtcp-mux

The following code in the webrtc source is rejecting it:

static bool GetLine(const std::string& message,
                    size_t* pos,
                    std::string* line) {
  size_t line_begin = *pos;
  size_t line_end = message.find(kNewLine, line_begin);
  if (line_end == std::string::npos) {
    return false;
  }
  // Update the new start position
  *pos = line_end + 1;
  if (line_end > 0 && (message.at(line_end - 1) == kReturn)) {
    --line_end;
  }
  *line = message.substr(line_begin, (line_end - line_begin));
  const char* cline = line->c_str();
  // RFC 4566
  // An SDP session description consists of a number of lines of text of
  // the form:
  // <type>=<value>
  // where <type> MUST be exactly one case-significant character and
  // <value> is structured text whose format depends on <type>.
  // Whitespace MUST NOT be used on either side of the "=" sign.
  if (cline[0] == kSdpDelimiterSpace ||
      cline[1] != kSdpDelimiterEqual ||
      cline[2] == kSdpDelimiterSpace) {
    *pos = line_begin;
    return false;
  }
  return true;
}

see: https://code.google.com/p/libjingle/source/browse/trunk/talk/app/webrtc/webrtcsdp.cc?r=354&spec=svn361#423

From what I can tell, this is caused by the a lines that are missing a header, e.g:

a=rtcp-fb:100 goog 
 =extmap:2 urn:ietf:params:rtp-hdrext:toffset
 =extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
 =crypto:1 AES_CM_128_HMAC_SHA1_80 

I haven't dig into the RFC to see if this is permitted this time. I'm currently looking at the rtc-transform code now to see where it can be modified to generate the sdp header field in all cases regardless...

Need a way to write custom attributes

Coming to my mind:

I understand that adding the ability to write custom attributes while not being able to read/parse them makes no much sense. However, which is the way to move on with missing fields? Is it sending a PR for each new field the way to go?

How to set a=ice-lite?

I want to add a=ice-lite into an SDP generated from scratch, which is the API to add it?

write does not add \r\n in all the lines

I parse a valid SDP with all the lines ending in \r\n as the RFC mandates. Convert it back to string with "write" method into a "sdp_out" variable.

Most of the lines are separated by just \n, and just three of them by \r\n (a=extmap and a=crypto lines):

var transform = require('sdp-transform');

var sdp = "v=0\r\n" +
"o=- 3264234524087092707 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\n" +
"t=0 0\r\n" +
"a=group:BUNDLE audio\r\n" +
"a=msid-semantic: WMS 7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn\r\n" +
"m=audio 60808 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n" +
"c=IN IP4 193.84.77.194\r\n" +
"a=rtcp:60808 IN IP4 193.84.77.194\r\n" +
"a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60808 typ host generation 0\r\n" +
"a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60808 typ host generation 0\r\n" +
"a=candidate:3289912957 1 udp 1845501695 193.84.77.194 60808 typ srflx raddr 192.168.34.75 rport 60808 generation 0\r\n" +
"a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60808 typ srflx raddr 192.168.34.75 rport 60808 generation 0\r\n" +
"a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host generation 0\r\n" +
"a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host generation 0\r\n" +
"a=ice-ufrag:qbeO2z5chIqNQ+Lt\r\n" +
"a=ice-pwd:IqJ1t0SjErP04UtUHyWaSjFY\r\n" +
"a=ice-options:google-ice\r\n" +
"a=fingerprint:sha-256 DC:F5:F0:18:D8:F3:F4:17:3A:5B:F3:42:A5:97:A4:BA:1E:8F:F9:0F:F7:42:86:E5:0F:67:D6:09:39:1A:ED:48\r\n" +
"a=setup:actpass\r\n" +
"a=mid:audio\r\n" +
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
"a=sendrecv\r\n" +
"a=rtcp-mux\r\n" +
"a=crypto:0 AES_CM_128_HMAC_SHA1_32 inline:1234\r\n" +
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:5678\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" +
"a=fmtp:111 minptime=10\r\n" +
"a=rtpmap:103 ISAC/16000\r\n" +
"a=rtpmap:104 ISAC/32000\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" +
"a=rtpmap:106 CN/32000\r\n" +
"a=rtpmap:105 CN/16000\r\n" +
"a=rtpmap:13 CN/8000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" +
"a=maxptime:60\r\n" +
"a=ssrc:1816449750 cname:ZHxhwS+0chogKQXY\r\n" +
"a=ssrc:1816449750 msid:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn 7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnna0\r\n" +
"a=ssrc:1816449750 mslabel:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn\r\n" +
"a=ssrc:1816449750 label:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnna0\r\n"

var parsed_sdp = transform.parse(sdp);

var sdp_out = transform.write(parsed_sdp);

sdp_out.replace(/\r/g, "***** R *****")
"v=0
o=- 3264234524087092707 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS 7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn
a=group:BUNDLE audio
m=audio 60808 RTP/SAVPF 111 103 104 0 8 106 105 13 126
c=IN IP4 193.84.77.194
a=rtpmap:111 opus/48000
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=fmtp:111 minptime=10
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level***** R ***** 
a=crypto:0 AES_CM_128_HMAC_SHA1_32 inline:1234***** R ***** 
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:5678***** R ***** 
a=setup:actpass
a=mid:audio
a=maxptime:60
a=sendrecv
a=ice-ufrag:qbeO2z5chIqNQ+Lt
a=ice-pwd:IqJ1t0SjErP04UtUHyWaSjFY
a=fingerprint:sha-256 DC:F5:F0:18:D8:F3:F4:17:3A:5B:F3:42:A5:97:A4:BA:1E:8F:F9:0F:F7:42:86:E5:0F:67:D6:09:39:1A:ED:48
a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60808 typ host
a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60808 typ host
a=candidate:3289912957 1 udp 1845501695 193.84.77.194 60808 typ srflx
a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60808 typ srflx
a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host
a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host
a=ice-options:google-ice
a=ssrc:1816449750 cname:ZHxhwS+0chogKQXY
a=ssrc:1816449750 msid:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn 7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnna0
a=ssrc:1816449750 mslabel:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnn
a=ssrc:1816449750 label:7GGGqNHeujXtCLcLt7Q0BFiYQj9o7nRbTPnna0
a=rtcp-mux
"

rtcp-fb invalid order after deserialization.

The parser does not keep the order of elements in SDP. So, if the source SDP looks like this:
a=rtpmap:100 H264/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f

After the deserialization, processing and serialization back it will look like this:

a=rtpmap:100 H264/90000
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f

a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli

FF/Chrome does not accept such offer/answers when rtcp-fb does not follow right after a=rtpmap

ssrc must admit no value

let objSsrc = {
    id: 12345678,
    attribute: 'foo'
};

obj.media[0].ssrcs.push(objSsrc);

This produces: a=ssrc:12345678 foo:undefined, which is wrong given that attribute value is not mandatory.

RFC 5576 refers to RFC 4566 when it comes to define the attribute syntax (which is generic):

   ssrc-attr = "ssrc:" ssrc-id SP attribute
   ; The base definition of "attribute" is in RFC 4566.
   ; (It is the content of "a=" lines.)

rtcp-fb inconsistent

trr-int is currently labelled as a push attribute, meaning it can be more than one per media stream, but this seems unlikely from reading rfc4585.

also, trr-int basically pass the normal rtcp-fb regex, maybe just join them?

Support for MRCP related parameters

Hi,
currently this parser marks MRCP related parameters as invalid:

SDP in SIP INVITE:

$ cat sdp_mrcp_request.js 
const sdpTransform = require('sdp-transform');

const sdpStr = "v=0\r\n\
o=FreeSWITCH 5772550679930491611 4608916746797952899 IN IP4 192.168.88.74\r\n\
s=-\r\n\
c=IN IP4 192.168.88.74\r\n\
t=0 0\r\n\
m=application 9 TCP/MRCPv2 1\r\n\
a=setup:active\r\n\
a=connection:new\r\n\
a=resource:speechsynth\r\n\
a=cmid:1\r\n\
m=audio 14238 RTP/AVP 0 8 96\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=rtpmap:8 PCMA/8000\r\n\
a=rtpmap:96 L16/8000\r\n\
a=recvonly\r\n\
a=mid:1\r\n\
";
 
const res = sdpTransform.parse(sdpStr);
console.log(res)

console.log("media[0].invalid:")
console.log(res.media[0].invalid)

$ node sdp_mrcp_request.js 
{ version: 0,
  origin:
   { username: 'FreeSWITCH',
     sessionId: '5772550679930491611',
     sessionVersion: '4608916746797952899',
     netType: 'IN',
     ipVer: 4,
     address: '192.168.88.74' },
  name: '-',
  connection: { version: 4, ip: '192.168.88.74' },
  timing: { start: 0, stop: 0 },
  media:
   [ { rtp: [],
       fmtp: [],
       type: 'application',
       port: 9,
       protocol: 'TCP/MRCPv2',
       payloads: 1,
       setup: 'active',
       connectionType: 'new',
       invalid: [Array] },
     { rtp: [Array],
       fmtp: [],
       type: 'audio',
       port: 14238,
       protocol: 'RTP/AVP',
       payloads: '0 8 96',
       direction: 'recvonly',
       mid: 1 } ] }
media[0].invalid:
[ { value: 'resource:speechsynth' }, { value: 'cmid:1' } ]

SDP in SIP Response:

$ cat sdp_mrcp_response.js 
const sdpTransform = require('sdp-transform');

const sdpStr = "v=0\r\n\
o=UniMRCPServer 1212606071011504954 4868540303632141964 IN IP4 192.168.88.136\r\n\
s=-\r\n\
c=IN IP4 192.168.88.136\r\n\
t=0 0\r\n\
m=application 1544 TCP/MRCPv2 1\r\n\
a=setup:passive\r\n\
a=connection:new\r\n\
a=channel:1228fd00945a4963@speechsynth\r\n\
a=cmid:1\r\n\
m=audio 5860 RTP/AVP 0\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=sendonly\r\n\
a=mid:1\r\n\
";
 
const res = sdpTransform.parse(sdpStr);
console.log(res)

console.log("media[0].invalid:")
console.log(res.media[0].invalid)


$ node sdp_mrcp_response.js 
{ version: 0,
  origin:
   { username: 'UniMRCPServer',
     sessionId: '1212606071011504954',
     sessionVersion: '4868540303632141964',
     netType: 'IN',
     ipVer: 4,
     address: '192.168.88.136' },
  name: '-',
  connection: { version: 4, ip: '192.168.88.136' },
  timing: { start: 0, stop: 0 },
  media:
   [ { rtp: [],
       fmtp: [],
       type: 'application',
       port: 1544,
       protocol: 'TCP/MRCPv2',
       payloads: 1,
       setup: 'passive',
       connectionType: 'new',
       invalid: [Array] },
     { rtp: [Array],
       fmtp: [],
       type: 'audio',
       port: 5860,
       protocol: 'RTP/AVP',
       payloads: 0,
       direction: 'sendonly',
       mid: 1 } ] }
media[0].invalid:
[ { value: 'channel:1228fd00945a4963@speechsynth' },
  { value: 'cmid:1' } ]

Would it be possible to add support for parameters 'resource', 'channel' and 'cmid' ?

clean ups

Firstly,

-regexes should never contain the (.) selector, at best (\S)
-regexes should be consistent on use of : vs. \:

Ideally, we should be able to infer format parameters from names and reg, but it is not really feasible yet.

Secondly, the extra format functions now stick out a bit. They should either be reversible and auto-applied or perhaps removed?

Generated SDP not accepted in Chrome

Just had a go at using the library, which is exactly what I was looking for (npm package control, browserify friendly, etc, etc). Unfortunately in early testing it has proven to have trouble with Chrome.

After hitting some problems, I did a simple test to compare some SDP input with the generated output of the module, by running the following:

transform.write(transform.parse(input));

The resulting output and corresponding input files are available in a branch I've created:

https://github.com/DamonOehlman/sdp-transform/tree/datafiles/test/data

Just wondering if you were looking to extend the SDP parsing and generating capabilities to work in browser environments, or whether I should be having a look at @legastero's sdp library instead?

Thanks heaps,
Damon.

Remove parseXXX helpers from module

Quick searches on github reveals that despite this module being in use in a lot of places, nobody currently uses these helper functions. This makes sense, because the module is most useful for quick modifications of other stuff before handing on modified values to the RTC API.

In my opinion. Having these functions in the library only inflates the payload code that is often sent to the browser. It's not essential to the library and should be removed.

New helpers are not going to be added to this library, so the helpers are kind of awkwardly standing on their own anyway. Killing them makes the library more focused on what is actually important.

If anyone have any objections, voice them here. Otherwise this will likely be the only real big change in 2.0.

Doesn't parse AirTunes AppleLossless a=rtpmap lines

An example is available here (the first example under ANNOUNCE).

Specifically this line:
a=rtpmap:96 AppleLossless
goes into media[i].invalid as-is. Tested in version 1.4.0 and 1.4.1.

Does the SDP spec require it to be followed by /<rate>? Even if so, it'd be nice to have this parsed and set media[i].rtp[j].rate to undefined if it's not present.

parsePayloads() returns a wrong order

The function parsePayloads should return an object that preserve the order in the m= line since this is used to munge and set the codec preference of the client.

instead it returns an array which is automatically ordered based on incremental payloads number.

How to set global a=msid-semantic attribute?

I want to set a=msid-semantic: WMS 5kAAIomHNlen3snVvaSMUzojvdRtLpKBO8o3 into the global section of the SDP.

Doing this does not work:

var sdp = {};
// [...]

sdp['msid-semantic'] = 'WMS 5kAAIomHNlen3snVvaSMUzojvdRtLpKBO8o3';

sdpTransform.write(sdp);

The generated SDP string does not contain global 'msid-semantic' attribute.

Is there a way to add custom (or non natively supported by this library) global attributes into a SDP?

transform.write() puts 't' lines after 'a' lines

According to RFC4566, t lines must appear before any a line:

Session description
         v=  (protocol version)
         o=  (originator and session identifier)
         s=  (session name)
         i=* (session information)
         u=* (URI of description)
         e=* (email address)
         p=* (phone number)
         c=* (connection information -- not required if included in
              all media)
         b=* (zero or more bandwidth information lines)
         One or more time descriptions ("t=" and "r=" lines; see below)
         z=* (time zone adjustments)
         k=* (encryption key)
         a=* (zero or more session attribute lines)
         Zero or more media descriptions

Beware of One or more time descriptions ("t=" and "r=" lines; see below)

You can see how write() sets the 't' line after 'a' lines in this example:

// Original SDP

var sdpStr = "v=0\n\
o=- 20518 0 IN IP4 203.0.113.1\n\
s= \n\
t=0 0\n\
c=IN IP4 203.0.113.1\n\
a=ice-ufrag:F7gI\n\
a=ice-pwd:x9cml/YzichV2+XlhiMu8g\n\
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7\n\
m=audio 54400 RTP/SAVPF 0 96\n\
a=rtpmap:0 PCMU/8000\n\
a=rtpmap:96 opus/48000\n\
a=ptime:20\n\
a=sendrecv\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\n\
a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host\n\
m=video 55400 RTP/SAVPF 97 98\n\
a=rtpmap:97 H264/90000\n\
a=fmtp:97 profile-level-id=4d0028;packetization-mode=1\n\
a=rtpmap:98 VP8/90000\n\
a=sendrecv\n\
a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host\n\
a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host\n\
";

// Parse sdpStr
sdp = transform.parse(sdpStr);

// Generate a new SDP from the parsed one
transform.write(sdp);

// Result
"v=0
o=- 20518 0 IN IP4 203.0.113.1
s= 
c=IN IP4 203.0.113.1
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7
t=0 0
m=audio 54400 RTP/SAVPF 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 opus/48000
a=ptime:20
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host
m=video 55400 RTP/SAVPF 97 98
a=rtpmap:97 H264/90000
a=rtpmap:98 VP8/90000
a=fmtp:97 profile-level-id=4d0028;packetization-mode=1
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host
"

TTL for multicast addresses

Hi!

Thanks for writing sap-transform ๐Ÿ™‚

I am trying to use it for parsing Audio over IP (AES67) SDP descriptions - which typically uses IP multicast.

Here is an example Connection Data line:

c=IN IP4 239.65.125.63/32

Which results in:

  "connection": {
    "version": 4,
    "ip": "239.65.125.63/32"
  },

RFC4566 says for a multicast address the format is:

<base multicast address>[/<ttl>]/<number of addresses>
...

So to get just the IP address, I have been doing:

ipAddress = sdp.connection.ip.split('/')[0]

Do you think it might be worth parsing out the TTL and number of addresses within the sap-transform library? This might be a breaking change for people already using addresses with TTL values - as seen in the SMPTE ST2022 and ST2110 example files.

nick.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.