1/**
2 * Copyright 2016 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7// This file was copied from:
8// https://cs.chromium.org/chromium/src/chrome/test/data/webrtc/munge_sdp.js.
9
10/**
11 * See |setSdpDefaultCodec|.
12 */
13function setSdpDefaultVideoCodec(sdp, codec) {
14  return setSdpDefaultCodec(sdp, 'video', codec);
15}
16
17/**
18 * Returns a modified version of |sdp| where the |codec| has been promoted to be
19 * the default codec, i.e. the codec whose ID is first in the list of codecs on
20 * the 'm=|type|' line, where |type| is 'audio' or 'video'.
21 * @private
22 */
23function setSdpDefaultCodec(sdp, type, codec) {
24  var sdpLines = splitSdpLines(sdp);
25
26  // Find codec ID, e.g. 100 for 'VP8' if 'a=rtpmap:100 VP8/9000'.
27  var codecId = findRtpmapId(sdpLines, codec);
28  if (codecId === null) {
29    failure('sdpPreferCodec',
30            'Missing a=rtpmap entry for |codec| = ' + codec + ' in ' + sdp);
31  }
32
33  // Find 'm=|type|' line, e.g. 'm=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116'.
34  var mLineNo = findLine(sdpLines, 'm=' + type);
35  if (mLineNo === null) {
36    failure('setSdpDefaultCodec',
37             '\'m=' + type + '\' line missing from |sdp|.');
38  }
39
40  // Modify video line to use the desired codec as the default.
41  sdpLines[mLineNo] = setMLineDefaultCodec(sdpLines[mLineNo], codecId);
42  return mergeSdpLines(sdpLines);
43}
44
45/**
46 *  * See |getSdpDefaultCodec|.
47 *   */
48function getSdpDefaultVideoCodec(sdp) {
49  return getSdpDefaultCodec(sdp, 'video');
50}
51
52/**
53 * Gets the default codec according to the |sdp|, i.e. the name of the codec
54 * whose ID is first in the list of codecs on the 'm=|type|' line, where |type|
55 * is 'audio' or 'video'.
56 * @private
57 */
58function getSdpDefaultCodec(sdp, type) {
59  var sdpLines = splitSdpLines(sdp);
60
61  // Find 'm=|type|' line, e.g. 'm=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116'.
62  var mLineNo = findLine(sdpLines, 'm=' + type);
63  if (mLineNo === null) {
64    failure('getSdpDefaultCodec',
65             '\'m=' + type + '\' line missing from |sdp|.');
66  }
67
68  // The default codec's ID.
69  var defaultCodecId = getMLineDefaultCodec(sdpLines[mLineNo]);
70  if (defaultCodecId === null) {
71    failure('getSdpDefaultCodec',
72             '\'m=' + type + '\' line contains no codecs.');
73  }
74
75  // Find codec name, e.g. 'VP8' for 100 if 'a=rtpmap:100 VP8/9000'.
76  var defaultCodec = findRtpmapCodec(sdpLines, defaultCodecId);
77  if (defaultCodec === null) {
78    failure('getSdpDefaultCodec',
79             'Unknown codec name for default codec ' + defaultCodecId + '.');
80  }
81  return defaultCodec;
82}
83
84/**
85 * Searches through all |sdpLines| for the 'a=rtpmap:' line for the codec of
86 * the specified name, returning its ID as an int if found, or null otherwise.
87 * |codec| is the case-sensitive name of the codec.
88 * For example, if |sdpLines| contains 'a=rtpmap:100 VP8/9000' and |codec| is
89 * 'VP8', this function returns 100.
90 * @private
91 */
92function findRtpmapId(sdpLines, codec) {
93  var lineNo = findRtpmapLine(sdpLines, codec);
94  if (lineNo === null)
95    return null;
96  // Parse <id> from 'a=rtpmap:<id> <codec>/<rate>'.
97  var id = sdpLines[lineNo].substring(9, sdpLines[lineNo].indexOf(' '));
98  return parseInt(id);
99}
100
101/**
102 * Searches through all |sdpLines| for the 'a=rtpmap:' line for the codec of
103 * the specified codec ID, returning its name if found, or null otherwise.
104 * For example, if |sdpLines| contains 'a=rtpmap:100 VP8/9000' and |id| is 100,
105 * this function returns 'VP8'.
106 * @private
107 */
108function findRtpmapCodec(sdpLines, id) {
109  var lineNo = findRtpmapLine(sdpLines, id);
110  if (lineNo === null)
111    return null;
112  // Parse <codec> from 'a=rtpmap:<id> <codec>/<rate>'.
113  var from = sdpLines[lineNo].indexOf(' ');
114  var to = sdpLines[lineNo].indexOf('/', from);
115  if (from === null || to === null || from + 1 >= to)
116    failure('findRtpmapCodec', '');
117  return sdpLines[lineNo].substring(from + 1, to);
118}
119
120/**
121 * Finds the first 'a=rtpmap:' line from |sdpLines| that contains |contains| and
122 * returns its line index, or null if no such line was found. |contains| may be
123 * the codec ID, codec name or bitrate. An 'a=rtpmap:' line looks like this:
124 * 'a=rtpmap:<id> <codec>/<rate>'.
125 */
126function findRtpmapLine(sdpLines, contains) {
127  for (var i = 0; i < sdpLines.length; i++) {
128    // Is 'a=rtpmap:' line containing |contains| string?
129    if (sdpLines[i].startsWith('a=rtpmap:') &&
130        sdpLines[i].indexOf(contains) != -1) {
131      // Expecting pattern 'a=rtpmap:<id> <codec>/<rate>'.
132      var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
133      if (!sdpLines[i].match(pattern))
134        failure('findRtpmapLine', 'Unexpected "a=rtpmap:" pattern.');
135      // Return line index.
136      return i;
137    }
138  }
139  return null;
140}
141
142/**
143 * Returns a modified version of |mLine| that has |codecId| first in the list of
144 * codec IDs. For example, setMLineDefaultCodec(
145 *     'm=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96', 107)
146 * Returns:
147 *     'm=video 9 UDP/TLS/RTP/SAVPF 107 100 101 116 117 96'
148 * @private
149 */
150function setMLineDefaultCodec(mLine, codecId) {
151  var elements = mLine.split(' ');
152
153  // Copy first three elements, codec order starts on fourth.
154  var newLine = elements.slice(0, 3);
155
156  // Put target |codecId| first and copy the rest.
157  newLine.push(codecId);
158  for (var i = 3; i < elements.length; i++) {
159    if (elements[i] != codecId)
160      newLine.push(elements[i]);
161  }
162
163  return newLine.join(' ');
164}
165
166/**
167 * Returns the default codec's ID from the |mLine|, or null if the codec list is
168 * empty. The default codec is the codec whose ID is first in the list of codec
169 * IDs on the |mLine|. For example, getMLineDefaultCodec(
170 *     'm=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96')
171 * Returns:
172 *     100
173 * @private
174 */
175function getMLineDefaultCodec(mLine) {
176  var elements = mLine.split(' ');
177  if (elements.length < 4)
178    return null;
179  return parseInt(elements[3]);
180}
181
182/** @private */
183function splitSdpLines(sdp) {
184  return sdp.split('\r\n');
185}
186
187/** @private */
188function mergeSdpLines(sdpLines) {
189  return sdpLines.join('\r\n');
190}
191
192/** @private */
193function findLine(lines, startsWith) {
194  for (var i = 0; i < lines.length; i++) {
195    if (lines[i].startsWith(startsWith))
196      return i;
197  }
198  return null;
199}
200