Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make resolution/frame rate controllable via ONVIF #157

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 102 additions & 43 deletions lib/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import { Utils } from './utils';
import fs = require('fs');
import parser = require('body-parser');
import { ChildProcess } from 'child_process';
import { ChildProcess, exec } from 'child_process';
import { v4l2ctl } from './v4l2ctl';

var utils = Utils.utils;

enum ServerState {
Running,
Restart,
Stopped,
}

class Camera {
options = {
resolutions: <Resolution[]>[
Expand All @@ -16,7 +22,6 @@ class Camera {
{ Width: 1024, Height: 768 },
{ Width: 1280, Height: 1024 },
{ Width: 1280, Height: 720 },
{ Width: 1640, Height: 1232 },
Copy link
Author

@wryun wryun Apr 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my Camera 2.1 module this resolution was causing issues. I believe it's because the height is greater than 1080.

{ Width: 1920, Height: 1080 }
],
framerates: [2, 5, 10, 15, 25, 30],
Expand All @@ -34,19 +39,22 @@ class Camera {
]
}

settings: CameraSettingsBase = {
forceGop: true,
settings: CameraSettings = {
gop: v4l2ctl.Controls.CodecControls.h264_i_frame_period.value,
quality: v4l2ctl.Controls.CodecControls.video_bitrate_mode.value == 0 ? 1 : 0,
bitrate: v4l2ctl.Controls.CodecControls.video_bitrate.value / 1000,
resolution: <Resolution>{ Width: 1280, Height: 720 },
framerate: 25,
}

config: rposConfig;
rtspServer: ChildProcess;
rtspServerState: ServerState;
webserver: any;

constructor(config: rposConfig, webserver: any) {
this.config = config;
this.rtspServer = null;
this.rtspServerState = ServerState.Stopped;
if (this.config.RTSPServer != 0) {
if (this.config.CameraType == 'usbcam') {
if (this.config.RTSPServer != 3) {
Expand Down Expand Up @@ -81,7 +89,6 @@ class Camera {
}
if (this.config.CameraType == 'picam') {
if (!fs.existsSync("/dev/video0")) {
// this.loadDriver();
if (utils.isPi()) {
// Needs a V4L2 Driver to be installed
console.log('Use modprobe to load the Pi Camera V4L2 driver');
Expand All @@ -102,11 +109,6 @@ class Camera {
utils.cleanup(() => {
this.stopRtsp();
var stop = new Date().getTime() + 2000;
while (new Date().getTime() < stop) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is only necessary because of the driver unload below, which was commented out. I think it's a good idea not to get involved with module loading/unloading and leave this to the user if necessary.

//wait for rtsp server to stop
;
}
// this.unloadDriver();
});

if (this.config.RTSPServer == 1 )fs.chmodSync("./bin/rtspServer", "0755");
Expand Down Expand Up @@ -190,53 +192,62 @@ class Camera {
})
}

loadDriver() {
try {
utils.execSync("sudo modprobe bcm2835-v4l2"); // only on PI, and not needed with USB Camera
} catch (err) {}
}

unloadDriver(){
try {
utils.execSync("sudo modprobe -r bcm2835-v4l2");
} catch (err) {}
}

setupCamera() {
v4l2ctl.SetPixelFormat(v4l2ctl.Pixelformat.H264)
v4l2ctl.SetResolution(this.settings.resolution);
v4l2ctl.SetFrameRate(this.settings.framerate);
v4l2ctl.SetPriority(v4l2ctl.ProcessPriority.record);
v4l2ctl.ReadFromFile();
v4l2ctl.ApplyControls();
this.setSettings(this.settings);
}

setSettings(newsettings: CameraSettingsParameter) {
v4l2ctl.SetResolution(newsettings.resolution);
v4l2ctl.SetFrameRate(newsettings.framerate);
setSettings(newsettings: CameraSettings) {
const requireRestartParams = [c => c.resolution.Height, c => c.resolution.Width, c => c.framerate];
const requiresRestart = requireRestartParams.some(f => f(this.settings) !== f(newsettings));
Object.assign(this.settings, newsettings);

v4l2ctl.Controls.CodecControls.video_bitrate.value = newsettings.bitrate * 1000;
v4l2ctl.Controls.CodecControls.video_bitrate_mode.value = newsettings.quality > 0 ? 0 : 1;
v4l2ctl.Controls.CodecControls.h264_i_frame_period.value = this.settings.forceGop ? v4l2ctl.Controls.CodecControls.h264_i_frame_period.value : newsettings.gop;
v4l2ctl.Controls.CodecControls.h264_i_frame_period.value = newsettings.gop;
v4l2ctl.ApplyControls();

if (this.config.RTSPServer === 1) {
// If it's our RTSPServer, we should have sufficient access to change resolution.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think looking at how it's launched this is probably not true, but I haven't actually tested it.

v4l2ctl.SetResolution(newsettings.resolution);
v4l2ctl.SetFrameRate(newsettings.framerate);
} else {
// If not, we need to restart the server with appropriate parameters, but make sure it's actually necessary.
// It does seem to be possible to dynamically set the framerate, but since the servers in general
// expect to set it...
if (requiresRestart) {
this.restartRtsp();
}
}
}

restartRtsp() {
utils.log.info("Restarting RTSP server");
this.rtspServerState = ServerState.Restart;
this.killRtsp();
}

startRtsp() {
if (this.rtspServer) {
utils.log.warn("Cannot start rtspServer, already running");
return;
}
utils.log.info("Starting rtsp server");

if (this.config.MulticastEnabled) {
this.rtspServer = utils.spawn("v4l2rtspserver", ["-P", this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(), "-m", this.config.RTSPMulticastName, "-M", this.config.MulticastAddress.toString() + ":" + this.config.MulticastPort.toString(), "-W",this.settings.resolution.Width.toString(), "-H", this.settings.resolution.Height.toString(), "/dev/video0"]);
if (this.config.RTSPServer !== 2) {
utils.log.warn("Multicast enabled; forcing use of RTSPServer 2 (v4l2rtspserver) instead of %s", this.config.RTSPServer);
this.config.RTSPServer = 2;
}
this.rtspServer = utils.spawn("v4l2rtspserver", ["-P", this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(), "-m", this.config.RTSPMulticastName, "-M", this.config.MulticastAddress.toString() + ":" + this.config.MulticastPort.toString(), "-W",this.settings.resolution.Width.toString(), "-H", this.settings.resolution.Height.toString(), "/dev/video0"]);
} else {
if (this.config.RTSPServer == 1) this.rtspServer = utils.spawn("./bin/rtspServer", ["/dev/video0", "2088960", this.config.RTSPPort.toString(), "0", this.config.RTSPName.toString()]);
if (this.config.RTSPServer == 2) this.rtspServer = utils.spawn("v4l2rtspserver", ["-P",this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(),"-W",this.settings.resolution.Width.toString(),"-H",this.settings.resolution.Height.toString(),"/dev/video0"]);
if (this.config.RTSPServer == 3) this.rtspServer = utils.spawn("./python/gst-rtsp-launch.sh", ["-P",this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(),"-W",this.settings.resolution.Width.toString(),"-H",this.settings.resolution.Height.toString(), "-t", this.config.CameraType, "-d", (this.config.CameraDevice == "" ? "auto" : this.config.CameraDevice)]);
if (this.config.RTSPServer == 1) this.rtspServer = utils.spawn("./bin/rtspServer", ["/dev/video0", "2088960", this.config.RTSPPort.toString(), "0", this.config.RTSPName.toString()]);
if (this.config.RTSPServer == 2) this.rtspServer = utils.spawn("v4l2rtspserver", ["-P",this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(),"-W",this.settings.resolution.Width.toString(),"-H",this.settings.resolution.Height.toString(),"-F",this.settings.framerate.toString(),"/dev/video0"]);
if (this.config.RTSPServer == 3) this.rtspServer = utils.spawn("./python/gst-rtsp-launch.sh", ["-P",this.config.RTSPPort.toString(), "-u" , this.config.RTSPName.toString(),"-W",this.settings.resolution.Width.toString(),"-H",this.settings.resolution.Height.toString(), "-t", this.config.CameraType, "-d", (this.config.CameraDevice == "" ? "auto" : this.config.CameraDevice)]);
}

if (this.rtspServer) {
this.rtspServerState = ServerState.Running;
const started = Date.now();

this.rtspServer.stdout.on('data', data => utils.log.debug("rtspServer: %s", data));
this.rtspServer.stderr.on('data', data => utils.log.error("rtspServer: %s", data));
this.rtspServer.on('error', err=> utils.log.error("rtspServer error: %s", err));
Expand All @@ -245,16 +256,64 @@ class Camera {
utils.log.error("rtspServer exited with code: %s", code);
else
utils.log.debug("rtspServer exited")

if (this.rtspServerState === ServerState.Stopped) {
return; // Requested exit (see stopRtsp).
}

// Otherwise, we need to restart.

if (Date.now() - started > 3000 || this.rtspServerState === ServerState.Restart) {
this.startRtsp();
} else {
// We're probably having some startup issue, so should wait a bit.

// Nasty hack: We might be failing to start because an existing v4l2rtspserver is listening
// on our port (potentially left over from a crashed RPOS). Let's try to wipe that out.
if (this.config.RTSPServer == 2) {
exec(`
if ! PIDS="$(pgrep v4l2rtspserver)"; then
sleep 2
exit 0
fi
kill $PIDS
sleep 2
if ! PIDS="$(pgrep v4l2rtspserver)"; then
exit 0
fi
kill -s 9 $PIDS
sleep 1
`, {}, () => this.startRtsp());
} else {
setTimeout(() => {
this.startRtsp();
}, 2000);
}
}
});
}
}

stopRtsp() {
if (this.rtspServer) {
utils.log.info("Stopping rtsp server");
this.rtspServer.kill();
this.rtspServer = null;
}
this.rtspServerState = ServerState.Stopped;
this.killRtsp();
}

killRtsp() {
utils.log.info("Stopping rtsp server")

let dead = false;
this.rtspServer.on('exit', () => {
dead = true;
});

this.rtspServer.kill();
setTimeout(() => {
if (!dead) {
utils.log.error("Rtsp server didn't respond to SIGTERM within 2 seconds; sending SIGKILL");
this.rtspServer.kill('SIGKILL');
}
}, 2000);
}
}

Expand Down
8 changes: 1 addition & 7 deletions rpos.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,10 @@ interface Resolution {
Width: number;
Height: number;
}
interface CameraSettingsParameter {
interface CameraSettings {
gop: number; //keyframe every X sec.
resolution: Resolution;
framerate: number;
bitrate: number;
profile: string;
quality: number;
}
interface CameraSettingsBase {
forceGop: boolean; // Use iframe interval setting from v4l2ctl.json instead of Onvif
resolution: Resolution;
framerate: number;
}
15 changes: 7 additions & 8 deletions services/media_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class MediaService extends SoapService {

var videoConfigurationOptions = {
QualityRange: {
Min: 1,
Min: 0,
Max: 1
},
H264: {
Expand Down Expand Up @@ -169,7 +169,7 @@ class MediaService extends SoapService {
Width: cameraSettings.resolution.Width,
Height: cameraSettings.resolution.Height
},
Quality: v4l2ctl.Controls.CodecControls.video_bitrate.value ? 1 : 1,
Quality: v4l2ctl.Controls.CodecControls.video_bitrate.value ? 0 : 1,
RateControl: {
FrameRateLimit: cameraSettings.framerate,
EncodingInterval: 1,
Expand Down Expand Up @@ -325,12 +325,11 @@ class MediaService extends SoapService {

port.SetVideoEncoderConfiguration = (args) => {
var settings = {
bitrate: args.Configuration.RateControl.BitrateLimit,
framerate: args.Configuration.RateControl.FrameRateLimit,
gop: args.Configuration.H264.GovLength,
profile: args.Configuration.H264.H264Profile,
quality: args.Configuration.Quality instanceof Object ? 1 : args.Configuration.Quality,
resolution: args.Configuration.Resolution
bitrate: (videoEncoderConfiguration.RateControl.BitrateLimit = args.Configuration.RateControl.BitrateLimit),
framerate: (videoEncoderConfiguration.RateControl.FrameRateLimit = args.Configuration.RateControl.FrameRateLimit),
gop: (videoEncoderConfiguration.H264.GovLength = args.Configuration.H264.GovLength),
quality: (videoEncoderConfiguration.Quality = args.Configuration.Quality instanceof Object ? 1 : args.Configuration.Quality),
resolution: (videoEncoderConfiguration.Resolution = args.Configuration.Resolution),
};
camera.setSettings(settings);

Expand Down