Skip to content

Commit f655a13

Browse files
committed
feat: add better support for sdp with multiple media definitions
1 parent 61725ee commit f655a13

File tree

4 files changed

+113
-59
lines changed

4 files changed

+113
-59
lines changed

src/app.js

+41-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const audioInterfaces = ref([]);
1111
export const networkInterfaces = ref([]);
1212
export const selectedStream = ref(null);
1313
export const selectedChannel = ref([]);
14+
export const streamIndex = ref([]);
1415
export const visibleStreams = ref(0);
1516
export const playing = ref("");
1617
export const persistentData = ref({
@@ -158,7 +159,6 @@ export const getTextareaRowNumber = () => {
158159
export const viewStream = (stream) => {
159160
page.value = "stream";
160161
selectedStream.value = stream;
161-
console.log(stream.name);
162162
};
163163

164164
export const isDevMode = () => {
@@ -212,6 +212,7 @@ export const getChannelSelectValues = (stream) => {
212212

213213
if (!selectedChannel.value[stream.id]) {
214214
selectedChannel.value[stream.id] = values[0].value;
215+
streamIndex.value[stream.id] = 0;
215216
}
216217

217218
return values;
@@ -225,26 +226,53 @@ export const playStream = (stream) => {
225226
playing.value = stream.id;
226227

227228
let channelMapping = selectedChannel.value[stream.id].split(",");
229+
let streamMapping = streamIndex.value[stream.id];
228230
let channel0 = parseInt(channelMapping[0]);
229231
let channel1 = parseInt(channelMapping[1]);
230232

231-
sendMessage({
232-
type: "play",
233-
data: {
233+
if (stream.media[streamMapping] && stream.media[streamMapping].rtp[0]) {
234+
let samplerate = stream.media[streamMapping].rtp[0].rate;
235+
let channels = stream.media[streamMapping].rtp[0].encoding;
236+
let ptime = stream.media[streamMapping].ptime;
237+
let codec = stream.media[streamMapping].rtp[0].codec;
238+
let port = stream.media[streamMapping].port;
239+
let mcast = stream.mcast;
240+
let filter = false;
241+
let filterAddr = "";
242+
243+
if (streamMapping > 0) {
244+
mcast = stream.media[streamMapping].connection.ip.split("/")[0];
245+
}
246+
247+
if (stream.media[streamMapping].sourceFilter) {
248+
filter = true;
249+
filterAddr = stream.media[streamMapping].sourceFilter.srcList;
250+
}
251+
252+
let data = {
234253
id: stream.id,
235-
mcast: stream.mcast,
236-
port: stream.media[0].port,
237-
addr: stream.origin.address,
238-
codec: stream.codec,
239-
ptime: stream.media[0].ptime,
240-
samplerate: stream.samplerate,
241-
channels: stream.channels,
254+
mcast: mcast,
255+
port: port,
256+
codec: codec,
257+
ptime: ptime,
258+
samplerate: samplerate,
259+
channels: channels,
242260
ch1Map: channel0,
243261
ch2Map: channel1,
244262
jitterBufferEnabled: persistentData.value.settings.bufferEnabled,
245263
jitterBufferSize: persistentData.value.settings.bufferSize,
246-
},
247-
});
264+
filter: filter,
265+
filterAddr: filterAddr,
266+
};
267+
268+
console.log(data);
269+
sendMessage({
270+
type: "play",
271+
data: data,
272+
});
273+
} else {
274+
console.error("Error playing stream");
275+
}
248276
}
249277
};
250278

src/components/pages/StreamsPage.vue

+24-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,25 @@
5555
<template v-for="stream in sortedStreams" :key="stream.id">
5656
<tr>
5757
<td>{{ stream.name }}</td>
58-
<td>{{ stream.mcast }}:{{ stream.media[0].port }}</td>
58+
<td>
59+
<span
60+
v-if="stream.media.length > 1 && stream.isSupported"
61+
class="copy"
62+
>
63+
<select
64+
class="form-select form-select-sm"
65+
v-model="streamIndex[stream.id]"
66+
:disabled="stream.id === playing"
67+
>
68+
<template v-for="(media, index) in stream.media" :key="index">
69+
<option :value="index">
70+
{{ media.connection.ip.split("/")[0] }}:{{ media.port }}
71+
</option>
72+
</template>
73+
</select>
74+
</span>
75+
<span v-else class="copy">{{ stream.mcast }}:{{ stream.media[0].port }}</span>
76+
</td>
5977
<td>
6078
<span class="badge bg-primary me-1" v-if="stream.dante">Dante</span>
6179
<span class="badge bg-primary me-1" v-if="stream.manual"
@@ -68,7 +86,9 @@
6886
<td>
6987
{{ stream.media[0].description ? stream.media[0].description : "" }}
7088
</td>
71-
<td>{{ stream.origin.address }}</td>
89+
<td>
90+
{{ stream.origin.address }}
91+
</td>
7292
<td>
7393
<span v-if="stream.isSupported" class="copy">
7494
{{ stream.codec }} / {{ stream.samplerate }}Hz /
@@ -142,6 +162,7 @@ import {
142162
visibleStreams,
143163
playing,
144164
persistentData,
165+
streamIndex,
145166
} from "../../app.js";
146167
import { ref, computed } from "vue";
147168
@@ -213,6 +234,7 @@ export default {
213234
visibleStreams,
214235
playing,
215236
persistentData,
237+
streamIndex,
216238
};
217239
},
218240
methods: {

src/lib/audio.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,26 @@ const start = function (args) {
3333
const samplesPerPacket = Math.round((args.samplerate / 1000) * args.ptime);
3434
const bytesPerSample = args.codec == "L24" ? 3 : 2;
3535
const pcmDataSize = samplesPerPacket * bytesPerSample * args.channels;
36-
const packetSize = pcmDataSize + 12;
3736
const pcmL16out = Buffer.alloc(samplesPerPacket * 4 * bufferSize);
3837

3938
let seqInternal = -1;
4039

4140
client.on("message", function (buffer, remote) {
42-
if (buffer.length != packetSize || remote.address != args.addr) {
41+
const firstByte = buffer.readUInt8(0);
42+
const csrcCount = firstByte & 0x0f;
43+
let headerLength = 12 + csrcCount * 4;
44+
const extensionFlag = (firstByte >> 4) & 0x01;
45+
46+
if (extensionFlag) {
47+
let extIndex = 12 + csrcCount * 4;
48+
const extensionLength = buffer.readUInt16BE(extIndex + 2);
49+
headerLength += 4 + extensionLength * 4;
50+
}
51+
52+
if (
53+
buffer.length != (pcmDataSize + headerLength) ||
54+
(args.filter && remote.address != args.filterAddr)
55+
) {
4356
return;
4457
}
4558

@@ -49,21 +62,21 @@ const start = function (args) {
4962
for (let sample = 0; sample < samplesPerPacket; sample++) {
5063
pcmL16out.writeUInt16LE(
5164
buffer.readUInt16BE(
52-
(sample * args.channels + args.ch1Map) * bytesPerSample + 12
65+
(sample * args.channels + args.ch1Map) * bytesPerSample + headerLength
5366
),
5467
sample * 4 + bufferIndex
5568
);
5669
pcmL16out.writeUInt16LE(
5770
buffer.readUInt16BE(
58-
(sample * args.channels + args.ch2Map) * bytesPerSample + 12
71+
(sample * args.channels + args.ch2Map) * bytesPerSample + headerLength
5972
),
6073
sample * 4 + bufferIndex + 2
6174
);
6275
}
6376

6477
if (seqInternal != -1) {
6578
let bufferIndex = seqInternal * samplesPerPacket * 4;
66-
let outBuf = pcmL16out.slice(
79+
let outBuf = pcmL16out.subarray(
6780
bufferIndex,
6881
bufferIndex + samplesPerPacket * 4
6982
);

src/lib/sdp.js

+30-39
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,6 @@ const preParse = function (sdp) {
3030
sdp.description = sdp.media[0].description;
3131
}
3232

33-
//put all filter relevant stuff into one string (name, description, multicast, unicast host)
34-
sdp.filterBy = (
35-
sdp.name +
36-
" " +
37-
sdp.origin.address +
38-
" " +
39-
sdp.description +
40-
" " +
41-
sdp.mcast
42-
).toLowerCase();
43-
4433
if (sdp.isSupported) {
4534
sdp.codec = sdp.media[0].rtp[0].codec;
4635
sdp.samplerate = sdp.media[0].rtp[0].rate;
@@ -60,37 +49,39 @@ const isSupportedStream = function (sdp) {
6049
return sdp;
6150
}
6251

63-
if (sdp.media[0].type != "audio" || sdp.media[0].protocol != "RTP/AVP") {
64-
sdp.isSupported = false;
65-
sdp.unsupportedReason = "Unsupported media type";
66-
return sdp;
67-
}
52+
for (let i = 0; i < sdp.media.length; i++) {
53+
if (sdp.media[i].type != "audio" || sdp.media[i].protocol != "RTP/AVP") {
54+
sdp.isSupported = false;
55+
sdp.unsupportedReason = "Unsupported media type";
56+
return sdp;
57+
}
6858

69-
if (sdp.media[0].rtp.length != 1) {
70-
sdp.isSupported = false;
71-
sdp.unsupportedReason = "Unsupported rtpmap";
72-
return sdp;
73-
}
59+
if (sdp.media[i].rtp.length != 1) {
60+
sdp.isSupported = false;
61+
sdp.unsupportedReason = "Unsupported rtpmap";
62+
return sdp;
63+
}
7464

75-
if (supportedSampleRates.indexOf(sdp.media[0].rtp[0].rate) === -1) {
76-
sdp.isSupported = false;
77-
sdp.unsupportedReason = "Unsupported samplerate";
78-
return sdp;
79-
}
65+
if (supportedSampleRates.indexOf(sdp.media[i].rtp[0].rate) === -1) {
66+
sdp.isSupported = false;
67+
sdp.unsupportedReason = "Unsupported samplerate";
68+
return sdp;
69+
}
8070

81-
if (
82-
sdp.media[0].rtp[0].codec != "L24" &&
83-
sdp.media[0].rtp[0].codec != "L16"
84-
) {
85-
sdp.isSupported = false;
86-
sdp.unsupportedReason = "Unsupported codec";
87-
return sdp;
88-
}
71+
if (
72+
sdp.media[i].rtp[0].codec != "L24" &&
73+
sdp.media[i].rtp[0].codec != "L16"
74+
) {
75+
sdp.isSupported = false;
76+
sdp.unsupportedReason = "Unsupported codec";
77+
return sdp;
78+
}
8979

90-
if (sdp.media[0].rtp[0].encoding < 1 || sdp.media[0].rtp[0].encoding > 64) {
91-
sdp.isSupported = false;
92-
sdp.unsupportedReason = "Unsupported channel number";
93-
return sdp;
80+
if (sdp.media[i].rtp[0].encoding < 1 || sdp.media[i].rtp[0].encoding > 64) {
81+
sdp.isSupported = false;
82+
sdp.unsupportedReason = "Unsupported channel number";
83+
return sdp;
84+
}
9485
}
9586

9687
sdp.isSupported = true;
@@ -158,7 +149,7 @@ const announceStream = function (rawSDP, addr, header) {
158149
let sdpMsg = Buffer.concat([sapHeader, sapContentType, sdpBody]);
159150

160151
socket.send(sdpMsg, 9875, "239.255.255.255", function (err) {
161-
console.error("Error sending SDP Message", err);
152+
if (err) console.error("Error sending SDP Message", err);
162153
});
163154
};
164155

0 commit comments

Comments
 (0)