123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- <?php
- /**
- * Camera streams capture/recording implementation
- */
- class Recorder {
- /**
- * Contains alter.ini config as key=>value
- *
- * @var array
- */
- protected $altCfg = array();
- /**
- * Contains binpaths.ini config as key=>value
- *
- * @var array
- */
- protected $binPaths = array();
- /**
- * Cameras instance placeholder
- *
- * @var object
- */
- protected $cameras = '';
- /**
- * Contains stardust process manager instance
- *
- * @var object
- */
- protected $stardust = '';
- /**
- * Contains full cameras data as
- *
- * @var array
- */
- protected $allCamerasData = array();
- /**
- * Storages instance placeholder.
- *
- * @var object
- */
- protected $storages = '';
- /**
- * Contains ffmpeg binary path
- *
- * @var string
- */
- protected $ffmpgPath = '';
- /**
- * System messages helper instance placeholder
- *
- * @var object
- */
- protected $messages = '';
- /**
- * Debugging mode flag
- *
- * @var bool
- */
- protected $debugFlag = false;
- /**
- * CliFF instance placeholder
- *
- * @var object
- */
- protected $cliff = '';
- /**
- * some creepy params here
- */
- protected $transportTemplate = '';
- protected $recordOpts = '';
- protected $audioCapture = '';
- protected $supressOutput = '';
- /**
- * Some predefined stuff
- */
- const PID_PREFIX = 'RECORD_';
- const CAPTURE_PID = 'CAPTURE';
- const WRAPPER = '/bin/wrapi';
- const CHUNKS_MASK = '%s';
- const CHUNKS_EXT = '.mp4';
- const DEBUG_LOG = 'exports/recorder_debug.log';
- /**
- * Dinosaurs are my best friends
- * Through thick and thin, until the very end
- * People tell me, do not pretend
- * Stop living in your made up world again
- * But the dinosaurs, they're real to me
- * They bring me up and make me happy
- * Hold on now, I think I see
- * A dinosaur wants to play with me
- */
- public function __construct() {
- $this->initMessages();
- $this->loadConfigs();
- $this->initCliff();
- $this->setOptions();
- $this->initStardust();
- $this->initStorages();
- $this->initCameras();
- }
- /**
- * Loads some required configs
- *
- * @global $ubillingConfig
- *
- * @return void
- */
- protected function loadConfigs() {
- global $ubillingConfig;
- $this->binPaths = $ubillingConfig->getBinpaths();
- $this->altCfg = $ubillingConfig->getAlter();
- }
- /**
- * Sets required properties depends on config options
- *
- * @return void
- */
- protected function setOptions() {
- $this->ffmpgPath = $this->binPaths['FFMPG_PATH'];
- $this->transportTemplate = $this->cliff->getTransportTemplate();
- $this->recordOpts = $this->cliff->getRecordOpts();
- $this->audioCapture = $this->cliff->getAudioCapture();
- $this->supressOutput = '';
- if (isset($this->altCfg['RECORDER_DEBUG'])) {
- if ($this->altCfg['RECORDER_DEBUG']) {
- $this->debugFlag = true;
- }
- }
- }
- /**
- * Inits ffmpeg CLI wrapper
- *
- * @return void
- */
- protected function initCliff() {
- $this->cliff = new CliFF();
- }
- /**
- * Inits system messages helper
- *
- * @return void
- */
- protected function initMessages() {
- $this->messages = new UbillingMessageHelper();
- }
- /**
- * Inits cameras into protected prop and loads its full data
- *
- * @return void
- */
- protected function initCameras() {
- $this->cameras = new Cameras();
- $this->allCamerasData = $this->cameras->getAllCamerasFullData();
- }
- /**
- * Inits storages into protected prop for further usage
- *
- * @return void
- */
- protected function initStorages() {
- $this->storages = new Storages();
- }
- /**
- * Inits stardust process manager
- *
- * @return void
- */
- protected function initStardust() {
- $this->stardust = new StarDust();
- }
- /**
- * Runs recording process of some camera
- *
- * @param int $cameraId
- *
- * @return void
- */
- public function runRecord($cameraId) {
- $cameraId = ubRouting::filters($cameraId, 'int');
- if (isset($this->allCamerasData[$cameraId])) {
- $cameraData = $this->allCamerasData[$cameraId];
- if ($cameraData['CAMERA']['active']) {
- $allRunningRecorders = $this->getRunningRecorders();
- if (!isset($allRunningRecorders[$cameraId])) {
- $pid = self::PID_PREFIX . $cameraId;
- $this->stardust->setProcess($pid);
- if ($this->stardust->notRunning()) {
- if (zb_PingICMP($cameraData['CAMERA']['ip'])) {
- $storageId = $cameraData['CAMERA']['storageid'];
- $channel = $cameraData['CAMERA']['channel'];
- $channelPath = $this->storages->initChannel($storageId, $channel);
- if ($channelPath) {
- if ($cameraData['TEMPLATE']['MAIN_STREAM']) {
- //rtsp proto capture
- if ($cameraData['TEMPLATE']['PROTO'] == 'rtsp') {
- //custom rtsp port is here?
- $rtspPort = $cameraData['TEMPLATE']['RTSP_PORT'];
- if (isset($cameraData['OPTS'])) {
- if (!empty($cameraData['OPTS']['rtspport'])) {
- $rtspPort = $cameraData['OPTS']['rtspport'];
- }
- }
- //custom transport protocol?
- $transTemplate = $this->transportTemplate;
- if (!empty($cameraData['TEMPLATE']['UDP_TRANSPORT'])) {
- $transTemplate = str_replace('-rtsp_transport tcp', '-rtsp_transport udp', $transTemplate);
- }
- $authString = $cameraData['CAMERA']['login'] . ':' . $cameraData['CAMERA']['password'] . '@';
- $streamUrl = $cameraData['CAMERA']['ip'] . ':' . $rtspPort . $cameraData['TEMPLATE']['MAIN_STREAM'];
- $audioOpts = ($cameraData['TEMPLATE']['SOUND']) ? $this->audioCapture : '';
- $captureFullUrl = "'rtsp://" . $authString . $streamUrl . "' " . $audioOpts . $this->recordOpts . ' ' . self::CHUNKS_MASK . self::CHUNKS_EXT;
- $captureCommand = $this->ffmpgPath . ' ' . $transTemplate . ' ' . $captureFullUrl . ' ' . $this->supressOutput;
- $fullCommand = 'cd ' . $channelPath . ' && ' . $captureCommand;
- $this->stardust->start();
- log_register('RECORDER STARTED [' . $cameraId . ']');
- //optional logging there
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' START: ' . $fullCommand . PHP_EOL, FILE_APPEND);
- }
- //locks process till it finishes
- if ($this->debugFlag) {
- $fullCommand .= ' 2>> /tmp/recorder_' . $cameraId . '.log';
- shell_exec($fullCommand);
- } else {
- shell_exec($fullCommand);
- }
- $this->stardust->stop();
- }
- } else {
- log_register('RECORDER FAILED [' . $cameraId . '] NO MAINSTREAM');
- }
- } else {
- log_register('RECORDER FAILED [' . $cameraId . '] CHANNEL NOT EXISTS');
- }
- } else {
- if ($this->debugFlag) {
- log_register('RECORDER NOTSTARTED [' . $cameraId . '] CAMERA NOT ACCESSIBLE');
- }
- }
- } else {
- log_register('RECORDER NOTSTARTED [' . $cameraId . '] ALREADY RUNNING STARDUST');
- }
- } else {
- log_register('RECORDER NOTSTARTED [' . $cameraId . '] ALREADY RUNNING REALPROCESS');
- }
- } else {
- log_register('RECORDER NOTSTARTED [' . $cameraId . '] CAMERA DISABLED');
- }
- } else {
- log_register('RECORDER FAILED [' . $cameraId . '] CAMERA NOT EXISTS');
- }
- }
- /**
- * Returns all running recorders real process PID-s array as pid=>processString
- *
- * @return array
- */
- protected function getRecordersPids() {
- $result = array();
- $command = $this->binPaths['PS'] . ' ax | ' . $this->binPaths['GREP'] . ' ' . $this->ffmpgPath . ' | ' . $this->binPaths['GREP'] . ' -v grep';
- $rawResult = shell_exec($command);
- if (!empty($rawResult)) {
- $rawResult = explodeRows($rawResult);
- foreach ($rawResult as $io => $eachLine) {
- $eachLine = trim($eachLine);
- $rawLine = $eachLine;
- $eachLine = explode(' ', $eachLine);
- if (isset($eachLine[0])) {
- $eachPid = $eachLine[0];
- if (is_numeric($eachPid)) {
- //is this really capture process?
- if (ispos($rawLine, $this->recordOpts)) {
- $result[$eachPid] = $rawLine;
- }
- }
- }
- }
- }
- return ($result);
- }
- /**
- * Returns running cameras recording processes as cameraId=>realPid
- *
- * @return array
- */
- public function getRunningRecorders() {
- $result = array();
- if (!empty($this->allCamerasData)) {
- $recorderPids = $this->getRecordersPids();
- if (!empty($recorderPids)) {
- foreach ($this->allCamerasData as $eachCameraId => $eachCameraData) {
- foreach ($recorderPids as $eachPid => $eachProcess) {
- $camIp = $eachCameraData['CAMERA']['ip'];
- $camLogin = $eachCameraData['CAMERA']['login'];
- $camPass = $eachCameraData['CAMERA']['password'];
- $camPort = $eachCameraData['TEMPLATE']['RTSP_PORT'];
- if (isset($eachCameraData['OPTS'])) {
- if (!empty($eachCameraData['OPTS']['rtspport'])) {
- $camPort = $eachCameraData['OPTS']['rtspport'];
- }
- }
- //looks familiar?
- if (ispos($eachProcess, $camIp) and ispos($eachProcess, $camLogin) and ispos($eachProcess, $camPass) and ispos($eachProcess, ':' . $camPort)) {
- $result[$eachCameraId] = $eachPid;
- }
- }
- }
- }
- }
- return ($result);
- }
- /**
- * Shutdowns recorder process if its running
- *
- * @param int $cameraId
- *
- * @return void
- */
- public function stopRecord($cameraId) {
- $cameraId = ubRouting::filters($cameraId, 'int');
- $allRunningRecorders = $this->getRunningRecorders();
- if (isset($allRunningRecorders[$cameraId])) {
- $count = 0;
- while (isset($allRunningRecorders[$cameraId])) {
- $count++;
- $command = $this->binPaths['SUDO'] . ' ' . $this->binPaths['KILL'] . ' ' . $allRunningRecorders[$cameraId];
- shell_exec($command);
- if ($this->debugFlag) {
- file_put_contents(self::DEBUG_LOG, curdatetime() . ' STOP: ' . $command . PHP_EOL, FILE_APPEND);
- }
- $allRunningRecorders = $this->getRunningRecorders();
- }
- log_register('RECORDER STOPPED [' . $cameraId . '] ATTEMPT `' . $count . '`');
- }
- }
- /**
- * Runs recorder for selected camera in background
- *
- * @param int $cameraId
- *
- * @return void
- */
- public function runRecordBackground($cameraId) {
- $cameraId = ubRouting::filters($cameraId, 'int');
- if (isset($this->allCamerasData[$cameraId])) {
- $cameraData = $this->allCamerasData[$cameraId];
- if ($cameraData['CAMERA']['active']) {
- $recordingProcess = new StarDust(self::PID_PREFIX . $cameraId);
- $allRunningRecorders = $this->getRunningRecorders();
- if (!isset($allRunningRecorders[$cameraId])) {
- $recordingProcess->runBackgroundProcess(self::WRAPPER . ' "recherd&cameraid=' . $cameraId . '"', 1);
- }
- }
- }
- }
- /**
- * Runs all recorders for active cameras in background
- *
- * @return void
- */
- public function captureAll() {
- $captureProcess = new StarDust(self::CAPTURE_PID);
- if ($captureProcess->notRunning()) {
- $captureProcess->start();
- if (!empty($this->allCamerasData)) {
- foreach ($this->allCamerasData as $io => $eachCamera) {
- if ($eachCamera['CAMERA']['active']) {
- $cameraId = $eachCamera['CAMERA']['id'];
- $this->runRecordBackground($cameraId);
- }
- }
- }
- $captureProcess->stop();
- }
- }
- }
|