common: fix rtsp track selection, path suffixing, udp/tcp sdp port setup.

This commit is contained in:
Koushik Dutta
2022-03-03 13:03:16 -08:00
parent 6c76e54275
commit 8a3ce5118d
3 changed files with 43 additions and 26 deletions

View File

@@ -14,10 +14,6 @@ interface Headers {
[header: string]: string
}
function findSyncFrame(streamChunks: StreamChunk[]): StreamChunk[] {
return streamChunks;
}
export interface RtspStreamParser extends StreamParser {
sdp: Promise<string>;
}
@@ -141,14 +137,16 @@ export class RtspClient extends RtspBase {
}
}
writeRequest(method: string, headers?: Headers, path?: string, body?: Buffer, authenticating?: boolean) {
writeRequest(method: string, headers?: Headers, path?: string, body?: Buffer) {
headers = headers || {};
let fullUrl: string;
if (path)
fullUrl = new URL(path, this.url).toString();
else
fullUrl = this.url;
let fullUrl = this.url;
if (path) {
// strangely, RTSP urls do not behave like expected from an HTTP-ish server.
// ffmpeg will happily suffix path segments after query strings:
// SETUP rtsp://localhost:5554/cam/realmonitor?channel=1&subtype=0/trackID=0 RTSP/1.0
fullUrl += '/' + path;
}
const sanitized = new URL(fullUrl);
sanitized.username = '';
@@ -172,7 +170,7 @@ export class RtspClient extends RtspBase {
headers: Headers,
body: Buffer
}> {
this.writeRequest(method, headers, path, body, authenticating);
this.writeRequest(method, headers, path, body);
const message = await this.readMessage();
const status = message[0];
@@ -381,6 +379,8 @@ export class RtspServer {
this.respond(200, 'OK', requestHeaders, headers, Buffer.from(this.sdp))
}
// todo: use the sdp itself to determine the audio/video track ids so
// rewriting is not necessary.
setup(url: string, requestHeaders: Headers) {
const headers: Headers = {};
const transport = requestHeaders['transport'];
@@ -391,7 +391,7 @@ export class RtspServer {
const [_, rtp, rtcp] = match;
if (url.includes('audio'))
this.udpPorts.audio = parseInt(rtp);
else
else if (url.includes('video'))
this.udpPorts.video = parseInt(rtp);
}
else if (transport.includes('TCP')) {
@@ -402,7 +402,7 @@ export class RtspServer {
if (url.includes('audio')) {
this.audioChannel = low;
}
else {
else if (url.includes('video')) {
this.videoChannel = low;
}
}

View File

@@ -18,21 +18,33 @@ export function parsePayloadTypes(sdp: string) {
export function findTrack(sdp: string, type: string, directions: TrackDirection[] = ['recvonly']) {
const tracks = sdp.split('m=').filter(track => track.startsWith(type));
for (const track of tracks) {
const returnTrack = () => {
const lines = track.split('\n').map(line => line.trim());
const control = lines.find(line => line.startsWith('a=control:'));
return {
section: 'm=' + track,
trackId: control?.split('a=control:')?.[1],
};
}
for (const dir of directions) {
if (track.includes(`a=${dir}`)) {
const lines = track.split('\n').map(line => line.trim());
const control = lines.find(line => line.startsWith('a=control:'));
return {
section: 'm=' + track,
trackId: control?.split('a=control:')?.[1],
};
return returnTrack();
}
}
// some sdp do not advertise a media flow direction. i think recvonly is the default?
if ((directions.includes('recvonly'))
&& !track.includes('sendonly')
&& !track.includes('inactive')) {
return returnTrack();
}
}
}
type TrackDirection = 'sendonly' | 'sendrecv' | 'recvonly';
type TrackDirection = 'sendonly' | 'sendrecv' | 'recvonly' | 'inactive';
export function parseTrackIds(sdp: string, directions: TrackDirection[] = ['recvonly', 'sendrecv']) {
return {

View File

@@ -10,10 +10,15 @@ import { RTCSessionControl, ScryptedDeviceBase } from "@scrypted/sdk";
// h264 baseline and opus are required codecs that all webrtc implementations must provide.
function createSdpInput(audioPort: number, videoPort: number, sdp: string) {
let outputSdp = sdp
.replace(/c=IN .*?/, `c=IN IP4 127.0.0.1`)
.replace(/m=audio \d+/, `m=audio ${audioPort}`)
.replace(/m=video \d+/, `m=video ${videoPort}`);
const lines = outputSdp.split('\n').map(line => line.trim());
let lines = outputSdp.split('\n').map(line => line.trim());
lines = lines
.filter(line => !line.includes('a=candidate'))
.filter(line => !line.includes('a=ice'));
const vindex = lines.findIndex(line => line.startsWith('m=video'));
lines.splice(vindex + 1, 0, 'a=control:trackID=video');
const aindex = lines.findIndex(line => line.startsWith('m=audio'));
@@ -43,8 +48,8 @@ export function getRTCMediaStreamOptions(id: string, name: string): MediaStreamO
export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase & RTCSignalingChannel, id: string): Promise<FFMpegInput> {
const { console, name } = channel;
const videoPort = Math.round(Math.random() * 10000 + 30000);
const audioPort = Math.round(Math.random() * 10000 + 30000);
const videoPort = useUdp ? Math.round(Math.random() * 10000 + 30000) : 0;
const audioPort = useUdp ? Math.round(Math.random() * 10000 + 30000) : 0;
const { clientPromise, port } = await listenZeroSingleClient();
@@ -65,13 +70,13 @@ export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase
}
catch (e) {
}
sessionControl?.endSession().catch(() => {});
sessionControl?.endSession().catch(() => { });
};
clientPromise.then(async (client) => {
socket = client;
const rtspServer = new RtspServer(socket, undefined, udp);
// rtspServer.console = console;
rtspServer.console = console;
rtspServer.audioChannel = 0;
rtspServer.videoChannel = 2;
@@ -203,7 +208,7 @@ export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase
};
rtspServer.client.write(rtspServer.sdp + '\r\n');
rtspServer.client.end();
rtspServer.client.on('data', () => {});
rtspServer.client.on('data', () => { });
// rtspServer.client.destroy();
console.log('sdp sent');
}