Claude
Skills
Sign in
Back

sip-media-negotiation

Included with Lifetime
$97 forever

Use when handling SDP offer/answer, codec negotiation, media capabilities, and RTP session setup in SIP applications.

General

What this skill does


# SIP Media Negotiation

Master Session Description Protocol (SDP) offer/answer model, codec negotiation, and media session establishment for building robust VoIP applications with optimal media handling.

## Understanding SDP and Media Negotiation

SDP (RFC 4566) is used in SIP to describe multimedia sessions. The SDP offer/answer model (RFC 3264) enables endpoints to negotiate media capabilities, codecs, and transport parameters.

## SDP Structure and Syntax

### Basic SDP Message

```
v=0
o=alice 2890844526 2890844527 IN IP4 atlanta.example.com
s=VoIP Call
c=IN IP4 192.0.2.1
t=0 0
m=audio 49170 RTP/AVP 0 8 97
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:97 iLBC/8000
a=ptime:20
a=maxptime:150
a=sendrecv
```

### SDP Line Meanings

```
v=  Protocol version (always 0)
o=  Origin (username, session-id, session-version, network-type, address-type, address)
s=  Session name
c=  Connection information
t=  Timing (start-time stop-time, 0 0 means permanent)
m=  Media description (media, port, protocol, formats)
a=  Attribute (codec mappings, parameters, direction)
```

## SDP Parser Implementation

### Complete SDP Parser

```typescript
interface SdpOrigin {
  username: string;
  sessionId: string;
  sessionVersion: string;
  netType: string;
  addrType: string;
  address: string;
}

interface SdpConnection {
  netType: string;
  addrType: string;
  address: string;
  ttl?: number;
  addressCount?: number;
}

interface SdpMedia {
  media: string;
  port: number;
  portCount?: number;
  protocol: string;
  formats: string[];
  attributes: Map<string, string[]>;
  connection?: SdpConnection;
  bandwidth?: Map<string, number>;
}

interface SdpSession {
  version: number;
  origin: SdpOrigin;
  sessionName: string;
  sessionInfo?: string;
  uri?: string;
  email?: string;
  phone?: string;
  connection?: SdpConnection;
  bandwidth?: Map<string, number>;
  timing: { start: number; stop: number }[];
  attributes: Map<string, string[]>;
  media: SdpMedia[];
}

class SdpParser {
  static parse(sdp: string): SdpSession {
    const lines = sdp.trim().split(/\r?\n/);
    const session: Partial<SdpSession> = {
      attributes: new Map(),
      timing: [],
      media: []
    };

    let currentMedia: SdpMedia | null = null;

    for (const line of lines) {
      const type = line.charAt(0);
      const value = line.substring(2);

      switch (type) {
        case 'v':
          session.version = parseInt(value);
          break;

        case 'o':
          session.origin = this.parseOrigin(value);
          break;

        case 's':
          session.sessionName = value;
          break;

        case 'i':
          if (currentMedia) {
            currentMedia.attributes.set('title', [value]);
          } else {
            session.sessionInfo = value;
          }
          break;

        case 'u':
          session.uri = value;
          break;

        case 'e':
          session.email = value;
          break;

        case 'p':
          session.phone = value;
          break;

        case 'c':
          const connection = this.parseConnection(value);
          if (currentMedia) {
            currentMedia.connection = connection;
          } else {
            session.connection = connection;
          }
          break;

        case 'b':
          const [bwType, bandwidth] = value.split(':');
          const bwValue = parseInt(bandwidth);
          if (currentMedia) {
            if (!currentMedia.bandwidth) {
              currentMedia.bandwidth = new Map();
            }
            currentMedia.bandwidth.set(bwType, bwValue);
          } else {
            if (!session.bandwidth) {
              session.bandwidth = new Map();
            }
            session.bandwidth.set(bwType, bwValue);
          }
          break;

        case 't':
          const [start, stop] = value.split(' ').map(Number);
          session.timing!.push({ start, stop });
          break;

        case 'm':
          if (currentMedia) {
            session.media!.push(currentMedia);
          }
          currentMedia = this.parseMedia(value);
          break;

        case 'a':
          const [attrName, attrValue] = this.parseAttribute(value);
          if (currentMedia) {
            if (!currentMedia.attributes.has(attrName)) {
              currentMedia.attributes.set(attrName, []);
            }
            currentMedia.attributes.get(attrName)!.push(attrValue || '');
          } else {
            if (!session.attributes!.has(attrName)) {
              session.attributes!.set(attrName, []);
            }
            session.attributes!.get(attrName)!.push(attrValue || '');
          }
          break;
      }
    }

    if (currentMedia) {
      session.media!.push(currentMedia);
    }

    return session as SdpSession;
  }

  private static parseOrigin(value: string): SdpOrigin {
    const parts = value.split(' ');
    return {
      username: parts[0],
      sessionId: parts[1],
      sessionVersion: parts[2],
      netType: parts[3],
      addrType: parts[4],
      address: parts[5]
    };
  }

  private static parseConnection(value: string): SdpConnection {
    const parts = value.split(' ');
    const addressParts = parts[2].split('/');

    return {
      netType: parts[0],
      addrType: parts[1],
      address: addressParts[0],
      ttl: addressParts[1] ? parseInt(addressParts[1]) : undefined,
      addressCount: addressParts[2] ? parseInt(addressParts[2]) : undefined
    };
  }

  private static parseMedia(value: string): SdpMedia {
    const parts = value.split(' ');
    const portParts = parts[1].split('/');

    return {
      media: parts[0],
      port: parseInt(portParts[0]),
      portCount: portParts[1] ? parseInt(portParts[1]) : undefined,
      protocol: parts[2],
      formats: parts.slice(3),
      attributes: new Map()
    };
  }

  private static parseAttribute(value: string): [string, string] {
    const colonIndex = value.indexOf(':');
    if (colonIndex === -1) {
      return [value, ''];
    }
    return [value.substring(0, colonIndex), value.substring(colonIndex + 1)];
  }

  static stringify(session: SdpSession): string {
    let sdp = '';

    // Version
    sdp += `v=${session.version}\r\n`;

    // Origin
    const o = session.origin;
    sdp += `o=${o.username} ${o.sessionId} ${o.sessionVersion} ${o.netType} ${o.addrType} ${o.address}\r\n`;

    // Session name
    sdp += `s=${session.sessionName}\r\n`;

    // Session information
    if (session.sessionInfo) {
      sdp += `i=${session.sessionInfo}\r\n`;
    }

    // URI
    if (session.uri) {
      sdp += `u=${session.uri}\r\n`;
    }

    // Email
    if (session.email) {
      sdp += `e=${session.email}\r\n`;
    }

    // Phone
    if (session.phone) {
      sdp += `p=${session.phone}\r\n`;
    }

    // Connection
    if (session.connection) {
      sdp += this.stringifyConnection(session.connection);
    }

    // Bandwidth
    if (session.bandwidth) {
      for (const [type, value] of session.bandwidth) {
        sdp += `b=${type}:${value}\r\n`;
      }
    }

    // Timing
    for (const timing of session.timing) {
      sdp += `t=${timing.start} ${timing.stop}\r\n`;
    }

    // Session attributes
    for (const [name, values] of session.attributes) {
      for (const value of values) {
        sdp += value ? `a=${name}:${value}\r\n` : `a=${name}\r\n`;
      }
    }

    // Media
    for (const media of session.media) {
      sdp += this.stringifyMedia(media);
    }

    return sdp;
  }

  private static stringifyConnection(conn: SdpConnection): string {
    let line = `c=${conn.netType} ${conn.addrType} ${conn.address}`;
    if (conn.ttl !== undefined) {
      line += `/${conn.ttl}`;
      if (conn.addressCount !== undefined) {
        line += `/${conn.addressCount}`;
      }
    }
    return line + '\r\n';
  }

  private static stringifyMedia(media: SdpMedia): string {
    let sdp = `m=${media.media} ${media.port}`;
    if (media.portCount) {
      sdp += `/${media.p

Related in General