Skip to content

Instantly share code, notes, and snippets.

@legotheboss
Created October 1, 2016 15:55
Show Gist options
  • Save legotheboss/6f8fb7ac5df32aa67069dab9d9e466fd to your computer and use it in GitHub Desktop.
Save legotheboss/6f8fb7ac5df32aa67069dab9d9e466fd to your computer and use it in GitHub Desktop.
'use strict';
var debug = require('debug')('Camera');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var clone = require('./util/clone').clone;
var uuid = require('./util/uuid');
var Service = require('./Service').Service;
var Characteristic = require('./Characteristic').Characteristic;
var StreamController = require('./StreamController').StreamController;
var HomeKitTypes = require('./gen/HomeKitTypes');
var fs = require('fs');
var ip = require('ip');
var spawn = require('child_process').spawn;
module.exports = {
Camera: Camera
};
function Camera() {
this.services = [];
this.streamControllers = [];
this.pendingSessions = {};
this.ongoingSessions = {};
let options = {
proxy: false, // Requires RTP/RTCP MUX Proxy
disable_audio_proxy: false, // If proxy = true, you can opt out audio proxy via this
srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption
video: {
resolutions: [
[640, 360, 30],
],
codec: {
profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes
levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes
}
},
audio: {
comfort_noise: false,
codecs: [
{
type: "OPUS", // Audio Codec
samplerate: 24 // 8, 16, 24 KHz
},
{
type: "AAC-eld",
samplerate: 16
}
]
}
}
this.createCameraControlService();
this._createStreamControllers(2, options);
}
Camera.prototype.handleSnapshotRequest = function(request, callback) {
// Image request: {width: number, height: number}
// Please override this and invoke callback(error, image buffer) when the snapshot is ready
var snapshot = fs.readFileSync(__dirname + '/res/snapshot.jpg');
callback(undefined, snapshot);
}
Camera.prototype.prepareStream = function(request, callback) {
// Invoked when iOS device requires stream
var sessionInfo = {};
let sessionID = request["sessionID"];
let targetAddress = request["targetAddress"];
sessionInfo["address"] = targetAddress;
var response = {};
let videoInfo = request["video"];
if (videoInfo) {
let targetPort = videoInfo["port"];
let srtp_key = videoInfo["srtp_key"];
let srtp_salt = videoInfo["srtp_salt"];
let videoResp = {
port: targetPort,
ssrc: 1,
srtp_key: srtp_key,
srtp_salt: srtp_salt
};
response["video"] = videoResp;
sessionInfo["video_port"] = targetPort;
sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
sessionInfo["video_ssrc"] = 1;
}
let audioInfo = request["audio"];
if (audioInfo) {
let targetPort = audioInfo["port"];
let srtp_key = audioInfo["srtp_key"];
let srtp_salt = audioInfo["srtp_salt"];
let audioResp = {
port: targetPort,
ssrc: 1,
srtp_key: srtp_key,
srtp_salt: srtp_salt
};
response["audio"] = audioResp;
sessionInfo["audio_port"] = targetPort;
sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
sessionInfo["audio_ssrc"] = 1;
}
let currentAddress = ip.address();
var addressResp = {
address: currentAddress
};
if (ip.isV4Format(currentAddress)) {
addressResp["type"] = "v4";
} else {
addressResp["type"] = "v6";
}
response["address"] = addressResp;
this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo;
callback(response);
}
Camera.prototype.handleStreamRequest = function(request) {
// Invoked when iOS device asks stream to start/stop/reconfigure
var sessionID = request["sessionID"];
var requestType = request["type"];
if (sessionID) {
let sessionIdentifier = uuid.unparse(sessionID);
if (requestType == "start") {
var sessionInfo = this.pendingSessions[sessionIdentifier];
if (sessionInfo) {
var width = 1280;
var height = 720;
var fps = 30;
var bitrate = 300;
let videoInfo = request["video"];
if (videoInfo) {
width = videoInfo["width"];
height = videoInfo["height"];
let expectedFPS = videoInfo["fps"];
if (expectedFPS < fps) {
fps = expectedFPS;
}
bitrate = videoInfo["max_bit_rate"];
}
let targetAddress = sessionInfo["address"];
let targetVideoPort = sessionInfo["video_port"];
let videoKey = sessionInfo["video_srtp"];
let ffmpegCommand = '-re -i rtsp://address here/ch0_1.h264 -threads 0 -vcodec h264_omx -an -pix_fmt yuv420p -r '+ fps +' -f rawvideo -tune zerolatency -vf scale='+ width +':'+ height +' -b:v '+ bitrate +'k -bufsize '+ bitrate +'k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params '+videoKey.toString('base64')+' srtp://'+targetAddress+':'+targetVideoPort+'?rtcpport='+targetVideoPort+'&localrtcpport='+targetVideoPort+'&pkt_size=1378';
let ffmpeg = spawn('ffmpeg', ffmpegCommand.split(' '), {env: process.env});
this.ongoingSessions[sessionIdentifier] = ffmpeg;
}
delete this.pendingSessions[sessionIdentifier];
} else if (requestType == "stop") {
var ffmpegProcess = this.ongoingSessions[sessionIdentifier];
if (ffmpegProcess) {
ffmpegProcess.kill();
}
delete this.ongoingSessions[sessionIdentifier];
}
}
}
Camera.prototype.createCameraControlService = function() {
var controlService = new Service.CameraControl();
// Developer can add control characteristics like rotation, night vision at here.
this.services.push(controlService);
}
// Private
Camera.prototype._createStreamControllers = function(maxStreams, options) {
let self = this;
for (var i = 0; i < maxStreams; i++) {
var streamController = new StreamController(i, options, self);
self.services.push(streamController.service);
self.streamControllers.push(streamController);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment