Merge branch 'master' of github.com:/ZoneMinder/ZoneMinder
This commit is contained in:
commit
847d4c5e80
|
@ -3,6 +3,147 @@ Debian
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
|
|
||||||
|
Easy Way: Debian Stretch
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.32.3 on Debian 9.8.
|
||||||
|
|
||||||
|
**Step 1:** Make sure your system is up to date
|
||||||
|
|
||||||
|
Open a console and use ``su`` command to become Root.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
apt update
|
||||||
|
apt upgrade
|
||||||
|
|
||||||
|
|
||||||
|
**Step 2:** Setup Sudo (optional but recommended)
|
||||||
|
|
||||||
|
By default Debian does not come with sudo, so you have to install it and configure it manually. This step is optional but recommended and the following instructions assume that you have setup sudo. If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
apt install sudo
|
||||||
|
usermod -a -G sudo <username>
|
||||||
|
exit
|
||||||
|
|
||||||
|
Now your terminal session is back under your normal user. You can check that you are now part of the sudo group with the command ``groups``, "sudo" should appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``.
|
||||||
|
|
||||||
|
|
||||||
|
**Step 3:** Install Apache and MySQL
|
||||||
|
|
||||||
|
These are not dependencies for the ZoneMinder package as they could be installed elsewhere. If they are not installed yet in your system, you have to trigger their installation manually.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo apt install apache2 mysql-server
|
||||||
|
|
||||||
|
**Step 4:** Add ZoneMinder's Package repository to your apt sources
|
||||||
|
|
||||||
|
ZoneMinder's Debian packages are not included in Debian's official package repositories. To be able to install ZoneMinder with APT, you have to edit the list of apt sources and add ZoneMinder's repository.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo nano /etc/apt/sources.list
|
||||||
|
|
||||||
|
Add the following to the bottom of the file
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# ZoneMinder repository
|
||||||
|
deb https://zmrepo.zoneminder.com/debian/release stretch/
|
||||||
|
|
||||||
|
CTRL+o and <Enter> to save
|
||||||
|
CTRL+x to exit
|
||||||
|
|
||||||
|
Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS.
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo apt install apt-transport-https
|
||||||
|
|
||||||
|
Finally, download the GPG key for ZoneMinder's repository:
|
||||||
|
::
|
||||||
|
|
||||||
|
wget -O - https://zmrepo.zoneminder.com/debian/archive-keyring.gpg | sudo apt-key add -
|
||||||
|
|
||||||
|
|
||||||
|
**Step 5:** Install ZoneMinder
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install zoneminder
|
||||||
|
|
||||||
|
**Step 6:** Read the Readme
|
||||||
|
|
||||||
|
The rest of the install process is covered in the README.Debian, so feel free to have
|
||||||
|
a read.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
gunzip /usr/share/doc/zoneminder/README.Debian.gz
|
||||||
|
cat /usr/share/doc/zoneminder/README.Debian
|
||||||
|
|
||||||
|
|
||||||
|
**Step 7:** Enable ZoneMinder service
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo systemctl enable zoneminder.service
|
||||||
|
|
||||||
|
**Step 8:** Configure Apache
|
||||||
|
|
||||||
|
The following commands will setup the default /zm virtual directory and configure
|
||||||
|
required apache modules.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo a2enconf zoneminder
|
||||||
|
sudo a2enmod rewrite
|
||||||
|
sudo a2enmod cgi # this is done automatically when installing the package. Redo this command manually only for troubleshooting.
|
||||||
|
|
||||||
|
|
||||||
|
**Step 9:** Edit Timezone in PHP
|
||||||
|
|
||||||
|
Automated way:
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo sed -i "s/;date.timezone =/date.timezone = $(sed 's/\//\\\//' /etc/timezone)/g" /etc/php/7.0/apache2/php.ini
|
||||||
|
|
||||||
|
Manual way
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo nano /etc/php/7.0/apache2/php.ini
|
||||||
|
|
||||||
|
Search for [Date] (Ctrl + w then type Date and press Enter) and change
|
||||||
|
date.timezone for your time zone. Don't forget to remove the ; from in front
|
||||||
|
of date.timezone.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[Date]
|
||||||
|
; Defines the default timezone used by the date functions
|
||||||
|
; http://php.net/date.timezone
|
||||||
|
date.timezone = America/New_York
|
||||||
|
|
||||||
|
CTRL+o then [Enter] to save
|
||||||
|
|
||||||
|
CTRL+x to exit
|
||||||
|
|
||||||
|
|
||||||
|
**Step 10:** Start ZoneMinder
|
||||||
|
|
||||||
|
Reload Apache to enable your changes and then start ZoneMinder.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
sudo systemctl start zoneminder
|
||||||
|
|
||||||
|
You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer.
|
||||||
|
|
||||||
|
|
||||||
Easy Way: Debian Jessie
|
Easy Way: Debian Jessie
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -81,7 +222,7 @@ should you choose to change default database user and password.
|
||||||
cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf
|
cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf
|
||||||
echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
|
|
||||||
** Step 8:** zm.conf Permissions
|
**Step 8:** zm.conf Permissions
|
||||||
|
|
||||||
Adjust permissions to the zm.conf file to allow web account to access it.
|
Adjust permissions to the zm.conf file to allow web account to access it.
|
||||||
|
|
||||||
|
@ -129,6 +270,7 @@ CTRL+x to exit
|
||||||
|
|
||||||
|
|
||||||
**Step 12:** Please check the configuration
|
**Step 12:** Please check the configuration
|
||||||
|
|
||||||
Zoneminder 1.32.x
|
Zoneminder 1.32.x
|
||||||
1. Check path of ZM_PATH in '/etc/zm/conf.d/zmcustom.conf' is ZM_PATH_ZMS=/zm/cgi-bin/nph-zms
|
1. Check path of ZM_PATH in '/etc/zm/conf.d/zmcustom.conf' is ZM_PATH_ZMS=/zm/cgi-bin/nph-zms
|
||||||
::
|
::
|
||||||
|
|
|
@ -11,4 +11,4 @@ A fast video interface core, a user-friendly and comprehensive PHP based web int
|
||||||
|
|
||||||
The core of ZoneMinder is the capture and analysis of images and a highly configurable set of parameters that eliminate false positives whilst ensuring minimum loss of footage. For example, you can define a set of 'zones' for each camera of varying sensitivity and functionality. This eliminates zones that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
|
The core of ZoneMinder is the capture and analysis of images and a highly configurable set of parameters that eliminate false positives whilst ensuring minimum loss of footage. For example, you can define a set of 'zones' for each camera of varying sensitivity and functionality. This eliminates zones that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
|
||||||
|
|
||||||
ZoneMinder is free under GPL License, but if you do find it useful, then please feel free to visit http://www.zoneminder.com/donate.html and help us fund our future improvements.
|
ZoneMinder is free under GPL License, but if you do find it useful, then please feel free to visit https://zoneminder.com/donate/ and help us fund our future improvements.
|
||||||
|
|
|
@ -252,8 +252,6 @@ void Logger::initialise(const std::string &id, const Options &options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logger::terminate() {
|
void Logger::terminate() {
|
||||||
Debug(1, "Terminating Logger");
|
|
||||||
|
|
||||||
if ( mFileLevel > NOLOG )
|
if ( mFileLevel > NOLOG )
|
||||||
closeFile();
|
closeFile();
|
||||||
|
|
||||||
|
@ -574,6 +572,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
|
||||||
|
|
||||||
free(filecopy);
|
free(filecopy);
|
||||||
if ( level <= FATAL ) {
|
if ( level <= FATAL ) {
|
||||||
|
log_mutex.unlock();
|
||||||
logTerm();
|
logTerm();
|
||||||
zmDbClose();
|
zmDbClose();
|
||||||
if ( level <= PANIC )
|
if ( level <= PANIC )
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -291,40 +291,42 @@ protected:
|
||||||
|
|
||||||
bool last_signal;
|
bool last_signal;
|
||||||
|
|
||||||
double fps;
|
double fps;
|
||||||
unsigned int last_camera_bytes;
|
unsigned int last_camera_bytes;
|
||||||
|
|
||||||
Image delta_image;
|
Image delta_image;
|
||||||
Image ref_image;
|
Image ref_image;
|
||||||
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
|
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
|
||||||
Image write_image; // Used when creating snapshot images
|
Image write_image; // Used when creating snapshot images
|
||||||
|
std::string diag_path_r;
|
||||||
|
std::string diag_path_d;
|
||||||
|
|
||||||
Purpose purpose; // What this monitor has been created to do
|
Purpose purpose; // What this monitor has been created to do
|
||||||
int event_count;
|
int event_count;
|
||||||
int image_count;
|
int image_count;
|
||||||
int ready_count;
|
int ready_count;
|
||||||
int first_alarm_count;
|
int first_alarm_count;
|
||||||
int last_alarm_count;
|
int last_alarm_count;
|
||||||
int buffer_count;
|
int buffer_count;
|
||||||
int prealarm_count;
|
int prealarm_count;
|
||||||
State state;
|
State state;
|
||||||
time_t start_time;
|
time_t start_time;
|
||||||
time_t last_fps_time;
|
time_t last_fps_time;
|
||||||
time_t auto_resume_time;
|
time_t auto_resume_time;
|
||||||
unsigned int last_motion_score;
|
unsigned int last_motion_score;
|
||||||
|
|
||||||
EventCloseMode event_close_mode;
|
EventCloseMode event_close_mode;
|
||||||
|
|
||||||
#if ZM_MEM_MAPPED
|
#if ZM_MEM_MAPPED
|
||||||
int map_fd;
|
int map_fd;
|
||||||
char mem_file[PATH_MAX];
|
char mem_file[PATH_MAX];
|
||||||
#else // ZM_MEM_MAPPED
|
#else // ZM_MEM_MAPPED
|
||||||
int shm_id;
|
int shm_id;
|
||||||
#endif // ZM_MEM_MAPPED
|
#endif // ZM_MEM_MAPPED
|
||||||
off_t mem_size;
|
off_t mem_size;
|
||||||
unsigned char *mem_ptr;
|
unsigned char *mem_ptr;
|
||||||
SharedData *shared_data;
|
SharedData *shared_data;
|
||||||
TriggerData *trigger_data;
|
TriggerData *trigger_data;
|
||||||
VideoStoreData *video_store_data;
|
VideoStoreData *video_store_data;
|
||||||
|
|
||||||
Snapshot *image_buffer;
|
Snapshot *image_buffer;
|
||||||
|
|
|
@ -28,7 +28,22 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
|
||||||
RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
RemoteCameraRtsp::RemoteCameraRtsp(
|
||||||
|
unsigned int p_monitor_id,
|
||||||
|
const std::string &p_method,
|
||||||
|
const std::string &p_host,
|
||||||
|
const std::string &p_port,
|
||||||
|
const std::string &p_path,
|
||||||
|
int p_width,
|
||||||
|
int p_height,
|
||||||
|
bool p_rtsp_describe,
|
||||||
|
int p_colours,
|
||||||
|
int p_brightness,
|
||||||
|
int p_contrast,
|
||||||
|
int p_hue,
|
||||||
|
int p_colour,
|
||||||
|
bool p_capture,
|
||||||
|
bool p_record_audio ) :
|
||||||
RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
|
RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
|
||||||
rtsp_describe( p_rtsp_describe ),
|
rtsp_describe( p_rtsp_describe ),
|
||||||
rtspThread( 0 )
|
rtspThread( 0 )
|
||||||
|
@ -63,13 +78,13 @@ RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string
|
||||||
mConvertContext = NULL;
|
mConvertContext = NULL;
|
||||||
#endif
|
#endif
|
||||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||||
if(colours == ZM_COLOUR_RGB32) {
|
if ( colours == ZM_COLOUR_RGB32 ) {
|
||||||
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
||||||
imagePixFormat = AV_PIX_FMT_RGBA;
|
imagePixFormat = AV_PIX_FMT_RGBA;
|
||||||
} else if(colours == ZM_COLOUR_RGB24) {
|
} else if ( colours == ZM_COLOUR_RGB24 ) {
|
||||||
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
||||||
imagePixFormat = AV_PIX_FMT_RGB24;
|
imagePixFormat = AV_PIX_FMT_RGB24;
|
||||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
} else if ( colours == ZM_COLOUR_GRAY8 ) {
|
||||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||||
imagePixFormat = AV_PIX_FMT_GRAY8;
|
imagePixFormat = AV_PIX_FMT_GRAY8;
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,7 +184,7 @@ int RemoteCameraRtsp::PrimeCapture() {
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Have another video stream." );
|
Debug(2, "Have another video stream." );
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
|
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
|
||||||
#else
|
#else
|
||||||
|
@ -181,6 +196,8 @@ int RemoteCameraRtsp::PrimeCapture() {
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Have another audio stream." );
|
Debug(2, "Have another audio stream." );
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Debug(1, "Have unknown codec type in stream %d : %d", i, mFormatContext->streams[i]->codec->codec_type);
|
||||||
}
|
}
|
||||||
} // end foreach stream
|
} // end foreach stream
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,7 @@
|
||||||
// accessed over a network connection using rtsp protocol
|
// accessed over a network connection using rtsp protocol
|
||||||
// (Real Time Streaming Protocol)
|
// (Real Time Streaming Protocol)
|
||||||
//
|
//
|
||||||
class RemoteCameraRtsp : public RemoteCamera
|
class RemoteCameraRtsp : public RemoteCamera {
|
||||||
{
|
|
||||||
protected:
|
protected:
|
||||||
struct sockaddr_in rtsp_sa;
|
struct sockaddr_in rtsp_sa;
|
||||||
struct sockaddr_in rtcp_sa;
|
struct sockaddr_in rtcp_sa;
|
||||||
|
|
|
@ -28,12 +28,12 @@
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
RtpCtrlThread::RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource ) : mRtspThread( rtspThread ), mRtpSource( rtpSource ), mStop( false )
|
RtpCtrlThread::RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource )
|
||||||
|
: mRtspThread( rtspThread ), mRtpSource( rtpSource ), mStop( false )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen )
|
int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen ) {
|
||||||
{
|
|
||||||
const RtcpPacket *rtcpPacket;
|
const RtcpPacket *rtcpPacket;
|
||||||
rtcpPacket = (RtcpPacket *)packet;
|
rtcpPacket = (RtcpPacket *)packet;
|
||||||
|
|
||||||
|
@ -48,33 +48,24 @@ int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen )
|
||||||
int pt = rtcpPacket->header.pt;
|
int pt = rtcpPacket->header.pt;
|
||||||
int len = ntohs(rtcpPacket->header.lenN);
|
int len = ntohs(rtcpPacket->header.lenN);
|
||||||
|
|
||||||
Debug( 5, "RTCP Ver: %d", ver );
|
Debug( 5, "RTCP Ver: %d Count: %d Pt: %d len: %d", ver, count, pt, len);
|
||||||
Debug( 5, "RTCP Count: %d", count );
|
|
||||||
Debug( 5, "RTCP Pt: %d", pt );
|
|
||||||
Debug( 5, "RTCP len: %d", len );
|
|
||||||
|
|
||||||
switch( pt )
|
switch( pt ) {
|
||||||
{
|
|
||||||
case RTCP_SR :
|
case RTCP_SR :
|
||||||
{
|
{
|
||||||
uint32_t ssrc = ntohl(rtcpPacket->body.sr.ssrcN);
|
uint32_t ssrc = ntohl(rtcpPacket->body.sr.ssrcN);
|
||||||
|
|
||||||
Debug( 5, "RTCP Got SR (%x)", ssrc );
|
Debug( 5, "RTCP Got SR (%x)", ssrc );
|
||||||
if ( mRtpSource.getSsrc() )
|
if ( mRtpSource.getSsrc() ) {
|
||||||
{
|
if ( ssrc != mRtpSource.getSsrc() ) {
|
||||||
if ( ssrc != mRtpSource.getSsrc() )
|
|
||||||
{
|
|
||||||
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
|
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
}
|
} else if ( ssrc ) {
|
||||||
else if ( ssrc )
|
|
||||||
{
|
|
||||||
mRtpSource.setSsrc( ssrc );
|
mRtpSource.setSsrc( ssrc );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( len > 1 )
|
if ( len > 1 ) {
|
||||||
{
|
|
||||||
//printf( "NTPts:%d.%d, RTPts:%d\n", $ntptsmsb, $ntptslsb, $rtpts );
|
//printf( "NTPts:%d.%d, RTPts:%d\n", $ntptsmsb, $ntptslsb, $rtpts );
|
||||||
uint16_t ntptsmsb = ntohl(rtcpPacket->body.sr.ntpSecN);
|
uint16_t ntptsmsb = ntohl(rtcpPacket->body.sr.ntpSecN);
|
||||||
uint16_t ntptslsb = ntohl(rtcpPacket->body.sr.ntpFracN);
|
uint16_t ntptslsb = ntohl(rtcpPacket->body.sr.ntpFracN);
|
||||||
|
@ -89,25 +80,21 @@ int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen )
|
||||||
case RTCP_SDES :
|
case RTCP_SDES :
|
||||||
{
|
{
|
||||||
ssize_t contentLen = packetLen - sizeof(rtcpPacket->header);
|
ssize_t contentLen = packetLen - sizeof(rtcpPacket->header);
|
||||||
while ( contentLen )
|
while ( contentLen ) {
|
||||||
{
|
|
||||||
Debug( 5, "RTCP CL: %zd", contentLen );
|
Debug( 5, "RTCP CL: %zd", contentLen );
|
||||||
uint32_t ssrc = ntohl(rtcpPacket->body.sdes.srcN);
|
uint32_t ssrc = ntohl(rtcpPacket->body.sdes.srcN);
|
||||||
|
|
||||||
Debug( 5, "RTCP Got SDES (%x), %d items", ssrc, count );
|
Debug( 5, "RTCP Got SDES (%x), %d items", ssrc, count );
|
||||||
if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) )
|
if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) ) {
|
||||||
{
|
|
||||||
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
|
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char *sdesPtr = (unsigned char *)&rtcpPacket->body.sdes.item;
|
unsigned char *sdesPtr = (unsigned char *)&rtcpPacket->body.sdes.item;
|
||||||
for ( int i = 0; i < count; i++ )
|
for ( int i = 0; i < count; i++ ) {
|
||||||
{
|
|
||||||
RtcpSdesItem *item = (RtcpSdesItem *)sdesPtr;
|
RtcpSdesItem *item = (RtcpSdesItem *)sdesPtr;
|
||||||
Debug( 5, "RTCP Item length %d", item->len );
|
Debug( 5, "RTCP Item length %d", item->len );
|
||||||
switch( item->type )
|
switch( item->type ) {
|
||||||
{
|
|
||||||
case RTCP_SDES_CNAME :
|
case RTCP_SDES_CNAME :
|
||||||
{
|
{
|
||||||
std::string cname( item->data, item->len );
|
std::string cname( item->data, item->len );
|
||||||
|
@ -123,50 +110,39 @@ int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen )
|
||||||
case RTCP_SDES_NOTE :
|
case RTCP_SDES_NOTE :
|
||||||
case RTCP_SDES_PRIV :
|
case RTCP_SDES_PRIV :
|
||||||
default :
|
default :
|
||||||
{
|
|
||||||
Error( "Received unexpected SDES item type %d, ignoring", item->type );
|
Error( "Received unexpected SDES item type %d, ignoring", item->type );
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int paddedLen = 4+2+item->len+1; // Add null byte
|
int paddedLen = 4+2+item->len+1; // Add null byte
|
||||||
paddedLen = (((paddedLen-1)/4)+1)*4; // Round to nearest multiple of 4
|
paddedLen = (((paddedLen-1)/4)+1)*4; // Round to nearest multiple of 4
|
||||||
Debug( 5, "RTCP PL:%d", paddedLen );
|
Debug(5, "RTCP PL:%d", paddedLen);
|
||||||
sdesPtr += paddedLen;
|
sdesPtr += paddedLen;
|
||||||
contentLen = ( paddedLen <= contentLen ) ? ( contentLen - paddedLen ) : 0;
|
contentLen = ( paddedLen <= contentLen ) ? ( contentLen - paddedLen ) : 0;
|
||||||
}
|
}
|
||||||
}
|
} // end whiel contentLen
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RTCP_BYE :
|
case RTCP_BYE :
|
||||||
{
|
Debug(5, "RTCP Got BYE");
|
||||||
Debug( 5, "RTCP Got BYE" );
|
|
||||||
mStop = true;
|
mStop = true;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case RTCP_APP :
|
case RTCP_APP :
|
||||||
{
|
|
||||||
// Ignoring as per RFC 3550
|
// Ignoring as per RFC 3550
|
||||||
Debug( 5, "Received RTCP_APP packet, ignoring.");
|
Debug(5, "Received RTCP_APP packet, ignoring.");
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case RTCP_RR :
|
case RTCP_RR :
|
||||||
{
|
Error("Received RTCP_RR packet.");
|
||||||
Error( "Received RTCP_RR packet." );
|
return -1;
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
default :
|
default :
|
||||||
{
|
|
||||||
// Ignore unknown packet types. Some cameras do this by design.
|
// Ignore unknown packet types. Some cameras do this by design.
|
||||||
Debug( 5, "Received unexpected packet type %d, ignoring", pt );
|
Debug(5, "Received unexpected packet type %d, ignoring", pt);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
consumed = sizeof(uint32_t)*(len+1);
|
consumed = sizeof(uint32_t)*(len+1);
|
||||||
return( consumed );
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpCtrlThread::generateRr( const unsigned char *packet, ssize_t packetLen )
|
int RtpCtrlThread::generateRr( const unsigned char *packet, ssize_t packetLen ) {
|
||||||
{
|
|
||||||
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
||||||
|
|
||||||
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.rr)+sizeof(rtcpPacket->body.rr.rr[0]);
|
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.rr)+sizeof(rtcpPacket->body.rr.rr[0]);
|
||||||
|
@ -180,11 +156,13 @@ int RtpCtrlThread::generateRr( const unsigned char *packet, ssize_t packetLen )
|
||||||
|
|
||||||
mRtpSource.updateRtcpStats();
|
mRtpSource.updateRtcpStats();
|
||||||
|
|
||||||
Debug( 5, "Ssrc = %d", mRtspThread.getSsrc()+1 );
|
Debug(5, "Ssrc = %d Ssrc_1 = %d Last Seq = %d Jitter = %d Last SR = %d",
|
||||||
Debug( 5, "Ssrc_1 = %d", mRtpSource.getSsrc() );
|
mRtspThread.getSsrc()+1,
|
||||||
Debug( 5, "Last Seq = %d", mRtpSource.getMaxSeq() );
|
mRtpSource.getSsrc(),
|
||||||
Debug( 5, "Jitter = %d", mRtpSource.getJitter() );
|
mRtpSource.getMaxSeq(),
|
||||||
Debug( 5, "Last SR = %d", mRtpSource.getLastSrTimestamp() );
|
mRtpSource.getJitter(),
|
||||||
|
mRtpSource.getLastSrTimestamp()
|
||||||
|
);
|
||||||
|
|
||||||
rtcpPacket->body.rr.ssrcN = htonl(mRtspThread.getSsrc()+1);
|
rtcpPacket->body.rr.ssrcN = htonl(mRtspThread.getSsrc()+1);
|
||||||
rtcpPacket->body.rr.rr[0].ssrcN = htonl(mRtpSource.getSsrc());
|
rtcpPacket->body.rr.rr[0].ssrcN = htonl(mRtpSource.getSsrc());
|
||||||
|
@ -195,11 +173,10 @@ int RtpCtrlThread::generateRr( const unsigned char *packet, ssize_t packetLen )
|
||||||
rtcpPacket->body.rr.rr[0].lsrN = htonl(mRtpSource.getLastSrTimestamp());
|
rtcpPacket->body.rr.rr[0].lsrN = htonl(mRtpSource.getLastSrTimestamp());
|
||||||
rtcpPacket->body.rr.rr[0].dlsrN = 0;
|
rtcpPacket->body.rr.rr[0].dlsrN = 0;
|
||||||
|
|
||||||
return( wordLen*sizeof(uint32_t) );
|
return wordLen*sizeof(uint32_t);
|
||||||
}
|
} // end RtpCtrlThread::generateRr
|
||||||
|
|
||||||
int RtpCtrlThread::generateSdes( const unsigned char *packet, ssize_t packetLen )
|
int RtpCtrlThread::generateSdes( const unsigned char *packet, ssize_t packetLen ) {
|
||||||
{
|
|
||||||
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
||||||
|
|
||||||
const std::string &cname = mRtpSource.getCname();
|
const std::string &cname = mRtpSource.getCname();
|
||||||
|
@ -218,11 +195,10 @@ int RtpCtrlThread::generateSdes( const unsigned char *packet, ssize_t packetLen
|
||||||
rtcpPacket->body.sdes.item[0].len = cname.size();
|
rtcpPacket->body.sdes.item[0].len = cname.size();
|
||||||
memcpy( rtcpPacket->body.sdes.item[0].data, cname.data(), cname.size() );
|
memcpy( rtcpPacket->body.sdes.item[0].data, cname.data(), cname.size() );
|
||||||
|
|
||||||
return( wordLen*sizeof(uint32_t) );
|
return wordLen*sizeof(uint32_t);
|
||||||
}
|
} // end RtpCtrlThread::generateSdes
|
||||||
|
|
||||||
int RtpCtrlThread::generateBye( const unsigned char *packet, ssize_t packetLen )
|
int RtpCtrlThread::generateBye( const unsigned char *packet, ssize_t packetLen ) {
|
||||||
{
|
|
||||||
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
|
||||||
|
|
||||||
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.bye)+sizeof(rtcpPacket->body.bye.srcN[0]);
|
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.bye)+sizeof(rtcpPacket->body.bye.srcN[0]);
|
||||||
|
@ -236,11 +212,10 @@ int RtpCtrlThread::generateBye( const unsigned char *packet, ssize_t packetLen )
|
||||||
|
|
||||||
rtcpPacket->body.bye.srcN[0] = htonl(mRtpSource.getSsrc());
|
rtcpPacket->body.bye.srcN[0] = htonl(mRtpSource.getSsrc());
|
||||||
|
|
||||||
return( wordLen*sizeof(uint32_t) );
|
return wordLen*sizeof(uint32_t);
|
||||||
}
|
} // end RtpCtrolThread::generateBye
|
||||||
|
|
||||||
int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes )
|
int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes ) {
|
||||||
{
|
|
||||||
unsigned char *bufferPtr = buffer;
|
unsigned char *bufferPtr = buffer;
|
||||||
|
|
||||||
// u_int32 len; /* length of compound RTCP packet in words */
|
// u_int32 len; /* length of compound RTCP packet in words */
|
||||||
|
@ -259,33 +234,28 @@ int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes )
|
||||||
// /* something wrong with packet format */
|
// /* something wrong with packet format */
|
||||||
// }
|
// }
|
||||||
|
|
||||||
while ( nBytes > 0 )
|
while ( nBytes > 0 ) {
|
||||||
{
|
|
||||||
int consumed = recvPacket( bufferPtr, nBytes );
|
int consumed = recvPacket( bufferPtr, nBytes );
|
||||||
if ( consumed <= 0 )
|
if ( consumed <= 0 )
|
||||||
break;
|
break;
|
||||||
bufferPtr += consumed;
|
bufferPtr += consumed;
|
||||||
nBytes -= consumed;
|
nBytes -= consumed;
|
||||||
}
|
}
|
||||||
return( nBytes );
|
return nBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RtpCtrlThread::run()
|
int RtpCtrlThread::run() {
|
||||||
{
|
|
||||||
Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() );
|
Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() );
|
||||||
SockAddrInet localAddr, remoteAddr;
|
SockAddrInet localAddr, remoteAddr;
|
||||||
|
|
||||||
bool sendReports;
|
bool sendReports;
|
||||||
UdpInetSocket rtpCtrlServer;
|
UdpInetSocket rtpCtrlServer;
|
||||||
if ( mRtpSource.getLocalHost() != "" )
|
if ( mRtpSource.getLocalHost() != "" ) {
|
||||||
{
|
|
||||||
if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) )
|
if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) )
|
||||||
Fatal( "Failed to bind RTCP server" );
|
Fatal( "Failed to bind RTCP server" );
|
||||||
sendReports = false;
|
sendReports = false;
|
||||||
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
|
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if ( !rtpCtrlServer.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalCtrlPort() ) )
|
if ( !rtpCtrlServer.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalCtrlPort() ) )
|
||||||
Fatal( "Failed to bind RTCP server" );
|
Fatal( "Failed to bind RTCP server" );
|
||||||
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
|
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
|
||||||
|
@ -309,17 +279,17 @@ int RtpCtrlThread::run()
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
Select::CommsList readable = select.getReadable();
|
Select::CommsList readable = select.getReadable();
|
||||||
if ( readable.size() == 0 )
|
if ( readable.size() == 0 ) {
|
||||||
{
|
|
||||||
if ( ! timeout ) {
|
if ( ! timeout ) {
|
||||||
// With this code here, we will send an SDES and RR packet every 10 seconds
|
// With this code here, we will send an SDES and RR packet every 10 seconds
|
||||||
ssize_t nBytes;
|
ssize_t nBytes;
|
||||||
unsigned char *bufferPtr = buffer;
|
unsigned char *bufferPtr = buffer;
|
||||||
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
||||||
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
||||||
Debug( 3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc(), ( now-last_receive) );
|
Debug( 3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %d",
|
||||||
if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
|
bufferPtr-buffer, rtpCtrlServer.getWriteDesc(), ( now-last_receive) );
|
||||||
Error( "Unable to send: %s", strerror( errno ) );
|
if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 )
|
||||||
|
Error("Unable to send: %s", strerror(errno));
|
||||||
timeout = true;
|
timeout = true;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
@ -332,25 +302,21 @@ int RtpCtrlThread::run()
|
||||||
timeout = false;
|
timeout = false;
|
||||||
last_receive = time(NULL);
|
last_receive = time(NULL);
|
||||||
}
|
}
|
||||||
for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter )
|
for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) {
|
||||||
{
|
if ( UdpInetSocket *socket = dynamic_cast<UdpInetSocket *>(*iter) ) {
|
||||||
if ( UdpInetSocket *socket = dynamic_cast<UdpInetSocket *>(*iter) )
|
|
||||||
{
|
|
||||||
ssize_t nBytes = socket->recv( buffer, sizeof(buffer) );
|
ssize_t nBytes = socket->recv( buffer, sizeof(buffer) );
|
||||||
Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() );
|
Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() );
|
||||||
|
|
||||||
if ( nBytes )
|
if ( nBytes ) {
|
||||||
{
|
|
||||||
recvPackets( buffer, nBytes );
|
recvPackets( buffer, nBytes );
|
||||||
|
|
||||||
if ( sendReports )
|
if ( sendReports ) {
|
||||||
{
|
|
||||||
unsigned char *bufferPtr = buffer;
|
unsigned char *bufferPtr = buffer;
|
||||||
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
||||||
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
|
||||||
Debug( 3, "Sending %zd bytes on sd %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc() );
|
Debug(3, "Sending %zd bytes on sd %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc());
|
||||||
if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
|
if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
|
||||||
Error( "Unable to send: %s", strerror( errno ) );
|
Error("Unable to send: %s", strerror(errno));
|
||||||
//Debug( 4, "Sent %d bytes on sd %d", nBytes, rtpCtrlServer.getWriteDesc() );
|
//Debug( 4, "Sent %d bytes on sd %d", nBytes, rtpCtrlServer.getWriteDesc() );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -358,16 +324,14 @@ int RtpCtrlThread::run()
|
||||||
mStop = true;
|
mStop = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
Panic("Barfed");
|
||||||
{
|
} // end if socket
|
||||||
Panic( "Barfed" );
|
} // end foeach comms iterator
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rtpCtrlServer.close();
|
rtpCtrlServer.close();
|
||||||
mRtspThread.stop();
|
mRtspThread.stop();
|
||||||
return( 0 );
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
|
@ -34,13 +34,11 @@
|
||||||
class RtspThread;
|
class RtspThread;
|
||||||
class RtpSource;
|
class RtpSource;
|
||||||
|
|
||||||
class RtpCtrlThread : public Thread
|
class RtpCtrlThread : public Thread {
|
||||||
{
|
|
||||||
friend class RtspThread;
|
friend class RtspThread;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
RTCP_SR = 200,
|
RTCP_SR = 200,
|
||||||
RTCP_RR = 201,
|
RTCP_RR = 201,
|
||||||
RTCP_SDES = 202,
|
RTCP_SDES = 202,
|
||||||
|
@ -48,8 +46,7 @@ private:
|
||||||
RTCP_APP = 204
|
RTCP_APP = 204
|
||||||
} RtcpType;
|
} RtcpType;
|
||||||
|
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
RTCP_SDES_END = 0,
|
RTCP_SDES_END = 0,
|
||||||
RTCP_SDES_CNAME = 1,
|
RTCP_SDES_CNAME = 1,
|
||||||
RTCP_SDES_NAME = 2,
|
RTCP_SDES_NAME = 2,
|
||||||
|
@ -61,8 +58,7 @@ private:
|
||||||
RTCP_SDES_PRIV = 8
|
RTCP_SDES_PRIV = 8
|
||||||
} RtcpSdesType;
|
} RtcpSdesType;
|
||||||
|
|
||||||
struct RtcpCommonHeader
|
struct RtcpCommonHeader {
|
||||||
{
|
|
||||||
uint8_t count:5; // varies by packet type
|
uint8_t count:5; // varies by packet type
|
||||||
uint8_t p:1; // padding flag
|
uint8_t p:1; // padding flag
|
||||||
uint8_t version:2; // protocol version
|
uint8_t version:2; // protocol version
|
||||||
|
@ -71,8 +67,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reception report block
|
// Reception report block
|
||||||
struct RtcpRr
|
struct RtcpRr {
|
||||||
{
|
|
||||||
uint32_t ssrcN; // data source being reported
|
uint32_t ssrcN; // data source being reported
|
||||||
int32_t lost:24; // cumul. no. pkts lost (signed!)
|
int32_t lost:24; // cumul. no. pkts lost (signed!)
|
||||||
uint32_t fraction:8; // fraction lost since last SR/RR
|
uint32_t fraction:8; // fraction lost since last SR/RR
|
||||||
|
@ -83,22 +78,18 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
// SDES item
|
// SDES item
|
||||||
struct RtcpSdesItem
|
struct RtcpSdesItem {
|
||||||
{
|
|
||||||
uint8_t type; // type of item (rtcp_sdes_type_t)
|
uint8_t type; // type of item (rtcp_sdes_type_t)
|
||||||
uint8_t len; // length of item (in octets)
|
uint8_t len; // length of item (in octets)
|
||||||
char data[]; // text, not null-terminated
|
char data[]; // text, not null-terminated
|
||||||
};
|
};
|
||||||
|
|
||||||
// RTCP packet
|
// RTCP packet
|
||||||
struct RtcpPacket
|
struct RtcpPacket {
|
||||||
{
|
|
||||||
RtcpCommonHeader header; // common header
|
RtcpCommonHeader header; // common header
|
||||||
union
|
union {
|
||||||
{
|
|
||||||
// Sender Report (SR)
|
// Sender Report (SR)
|
||||||
struct Sr
|
struct Sr {
|
||||||
{
|
|
||||||
uint32_t ssrcN; // sender generating this report, network order
|
uint32_t ssrcN; // sender generating this report, network order
|
||||||
uint32_t ntpSecN; // NTP timestamp, network order
|
uint32_t ntpSecN; // NTP timestamp, network order
|
||||||
uint32_t ntpFracN;
|
uint32_t ntpFracN;
|
||||||
|
@ -109,22 +100,19 @@ private:
|
||||||
} sr;
|
} sr;
|
||||||
|
|
||||||
// Reception Report (RR)
|
// Reception Report (RR)
|
||||||
struct Rr
|
struct Rr {
|
||||||
{
|
|
||||||
uint32_t ssrcN; // receiver generating this report
|
uint32_t ssrcN; // receiver generating this report
|
||||||
RtcpRr rr[]; // variable-length list
|
RtcpRr rr[]; // variable-length list
|
||||||
} rr;
|
} rr;
|
||||||
|
|
||||||
// source description (SDES)
|
// source description (SDES)
|
||||||
struct Sdes
|
struct Sdes {
|
||||||
{
|
|
||||||
uint32_t srcN; // first SSRC/CSRC
|
uint32_t srcN; // first SSRC/CSRC
|
||||||
RtcpSdesItem item[]; // list of SDES items
|
RtcpSdesItem item[]; // list of SDES items
|
||||||
} sdes;
|
} sdes;
|
||||||
|
|
||||||
// BYE
|
// BYE
|
||||||
struct
|
struct {
|
||||||
{
|
|
||||||
uint32_t srcN[]; // list of sources
|
uint32_t srcN[]; // list of sources
|
||||||
// can't express trailing text for reason (what does this mean? it's not even english!)
|
// can't express trailing text for reason (what does this mean? it's not even english!)
|
||||||
} bye;
|
} bye;
|
||||||
|
@ -148,8 +136,7 @@ private:
|
||||||
public:
|
public:
|
||||||
RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource );
|
RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource );
|
||||||
|
|
||||||
void stop()
|
void stop() {
|
||||||
{
|
|
||||||
mStop = true;
|
mStop = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,17 @@
|
||||||
|
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
|
|
||||||
RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, const std::string &remoteHost, int remotePortBase, uint32_t ssrc, uint16_t seq, uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId ) :
|
RtpSource::RtpSource(
|
||||||
|
int id,
|
||||||
|
const std::string &localHost,
|
||||||
|
int localPortBase,
|
||||||
|
const std::string &remoteHost,
|
||||||
|
int remotePortBase,
|
||||||
|
uint32_t ssrc,
|
||||||
|
uint16_t seq,
|
||||||
|
uint32_t rtpClock,
|
||||||
|
uint32_t rtpTime,
|
||||||
|
_AVCODECID codecId ) :
|
||||||
mId( id ),
|
mId( id ),
|
||||||
mSsrc( ssrc ),
|
mSsrc( ssrc ),
|
||||||
mLocalHost( localHost ),
|
mLocalHost( localHost ),
|
||||||
|
@ -65,13 +75,12 @@ RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, c
|
||||||
mLastSrTimeNtp = tvZero();
|
mLastSrTimeNtp = tvZero();
|
||||||
mLastSrTimeRtp = 0;
|
mLastSrTimeRtp = 0;
|
||||||
|
|
||||||
if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4)
|
if ( mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4 )
|
||||||
Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." );
|
Warning("The device is using a codec (%d) that may not be supported. Do not be surprised if things don't work.", mCodecId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpSource::init( uint16_t seq )
|
void RtpSource::init( uint16_t seq ) {
|
||||||
{
|
Debug(3, "Initialising sequence");
|
||||||
Debug( 3, "Initialising sequence" );
|
|
||||||
mBaseSeq = seq;
|
mBaseSeq = seq;
|
||||||
mMaxSeq = seq;
|
mMaxSeq = seq;
|
||||||
mBadSeq = RTP_SEQ_MOD + 1; // so seq == mBadSeq is false
|
mBadSeq = RTP_SEQ_MOD + 1; // so seq == mBadSeq is false
|
||||||
|
@ -84,77 +93,58 @@ void RtpSource::init( uint16_t seq )
|
||||||
mTransit = 0;
|
mTransit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpSource::updateSeq( uint16_t seq )
|
bool RtpSource::updateSeq( uint16_t seq ) {
|
||||||
{
|
|
||||||
uint16_t uDelta = seq - mMaxSeq;
|
uint16_t uDelta = seq - mMaxSeq;
|
||||||
|
|
||||||
// Source is not valid until MIN_SEQUENTIAL packets with
|
// Source is not valid until MIN_SEQUENTIAL packets with
|
||||||
// sequential sequence numbers have been received.
|
// sequential sequence numbers have been received.
|
||||||
Debug( 5, "Seq: %d", seq );
|
Debug( 5, "Seq: %d", seq );
|
||||||
|
|
||||||
if ( mProbation)
|
if ( mProbation) {
|
||||||
{
|
|
||||||
// packet is in sequence
|
// packet is in sequence
|
||||||
if ( seq == mMaxSeq + 1)
|
if ( seq == mMaxSeq + 1) {
|
||||||
{
|
|
||||||
Debug( 3, "Sequence in probation %d, in sequence", mProbation );
|
Debug( 3, "Sequence in probation %d, in sequence", mProbation );
|
||||||
mProbation--;
|
mProbation--;
|
||||||
mMaxSeq = seq;
|
mMaxSeq = seq;
|
||||||
if ( mProbation == 0 )
|
if ( mProbation == 0 ) {
|
||||||
{
|
|
||||||
init( seq );
|
init( seq );
|
||||||
mReceivedPackets++;
|
mReceivedPackets++;
|
||||||
return( true );
|
return( true );
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Sequence in probation %d, out of sequence", mProbation );
|
Warning( "Sequence in probation %d, out of sequence", mProbation );
|
||||||
mProbation = MIN_SEQUENTIAL - 1;
|
mProbation = MIN_SEQUENTIAL - 1;
|
||||||
mMaxSeq = seq;
|
mMaxSeq = seq;
|
||||||
return( false );
|
return( false );
|
||||||
}
|
}
|
||||||
return( true );
|
return( true );
|
||||||
}
|
} else if ( uDelta < MAX_DROPOUT ) {
|
||||||
else if ( uDelta < MAX_DROPOUT )
|
if ( uDelta == 1 ) {
|
||||||
{
|
|
||||||
if ( uDelta == 1 )
|
|
||||||
{
|
|
||||||
Debug( 4, "Packet in sequence, gap %d", uDelta );
|
Debug( 4, "Packet in sequence, gap %d", uDelta );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Packet in sequence, gap %d", uDelta );
|
Warning( "Packet in sequence, gap %d", uDelta );
|
||||||
}
|
}
|
||||||
|
|
||||||
// in order, with permissible gap
|
// in order, with permissible gap
|
||||||
if ( seq < mMaxSeq )
|
if ( seq < mMaxSeq ) {
|
||||||
{
|
|
||||||
// Sequence number wrapped - count another 64K cycle.
|
// Sequence number wrapped - count another 64K cycle.
|
||||||
mCycles += RTP_SEQ_MOD;
|
mCycles += RTP_SEQ_MOD;
|
||||||
}
|
}
|
||||||
mMaxSeq = seq;
|
mMaxSeq = seq;
|
||||||
}
|
} else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER ) {
|
||||||
else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER )
|
|
||||||
{
|
|
||||||
Warning( "Packet out of sequence, gap %d", uDelta );
|
Warning( "Packet out of sequence, gap %d", uDelta );
|
||||||
// the sequence number made a very large jump
|
// the sequence number made a very large jump
|
||||||
if ( seq == mBadSeq )
|
if ( seq == mBadSeq ) {
|
||||||
{
|
|
||||||
Debug( 3, "Restarting sequence" );
|
Debug( 3, "Restarting sequence" );
|
||||||
// Two sequential packets -- assume that the other side
|
// Two sequential packets -- assume that the other side
|
||||||
// restarted without telling us so just re-sync
|
// restarted without telling us so just re-sync
|
||||||
// (i.e., pretend this was the first packet).
|
// (i.e., pretend this was the first packet).
|
||||||
init( seq );
|
init( seq );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1);
|
mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1);
|
||||||
return( false );
|
return( false );
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Packet duplicate or reordered, gap %d", uDelta );
|
Warning( "Packet duplicate or reordered, gap %d", uDelta );
|
||||||
// duplicate or reordered packet
|
// duplicate or reordered packet
|
||||||
return( false );
|
return( false );
|
||||||
|
@ -163,10 +153,8 @@ bool RtpSource::updateSeq( uint16_t seq )
|
||||||
return( uDelta==1?true:false );
|
return( uDelta==1?true:false );
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpSource::updateJitter( const RtpDataHeader *header )
|
void RtpSource::updateJitter( const RtpDataHeader *header ) {
|
||||||
{
|
if ( mRtpFactor > 0 ) {
|
||||||
if ( mRtpFactor > 0 )
|
|
||||||
{
|
|
||||||
Debug( 5, "Delta rtp = %.6f", tvDiffSec( mBaseTimeReal ) );
|
Debug( 5, "Delta rtp = %.6f", tvDiffSec( mBaseTimeReal ) );
|
||||||
uint32_t localTimeRtp = mBaseTimeRtp + uint32_t( tvDiffSec( mBaseTimeReal ) * mRtpFactor );
|
uint32_t localTimeRtp = mBaseTimeRtp + uint32_t( tvDiffSec( mBaseTimeReal ) * mRtpFactor );
|
||||||
Debug( 5, "Local RTP time = %x", localTimeRtp );
|
Debug( 5, "Local RTP time = %x", localTimeRtp );
|
||||||
|
@ -174,8 +162,7 @@ void RtpSource::updateJitter( const RtpDataHeader *header )
|
||||||
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
|
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
|
||||||
Debug( 5, "Packet transit RTP time = %x", packetTransit );
|
Debug( 5, "Packet transit RTP time = %x", packetTransit );
|
||||||
|
|
||||||
if ( mTransit > 0 )
|
if ( mTransit > 0 ) {
|
||||||
{
|
|
||||||
// Jitter
|
// Jitter
|
||||||
int d = packetTransit - mTransit;
|
int d = packetTransit - mTransit;
|
||||||
Debug( 5, "Jitter D = %d", d );
|
Debug( 5, "Jitter D = %d", d );
|
||||||
|
@ -185,28 +172,22 @@ void RtpSource::updateJitter( const RtpDataHeader *header )
|
||||||
mJitter += d - ((mJitter + 8) >> 4);
|
mJitter += d - ((mJitter + 8) >> 4);
|
||||||
}
|
}
|
||||||
mTransit = packetTransit;
|
mTransit = packetTransit;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
mJitter = 0;
|
mJitter = 0;
|
||||||
}
|
}
|
||||||
Debug( 5, "RTP Jitter: %d", mJitter );
|
Debug( 5, "RTP Jitter: %d", mJitter );
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime )
|
void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime ) {
|
||||||
{
|
|
||||||
struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) );
|
struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) );
|
||||||
|
|
||||||
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
||||||
|
|
||||||
if ( mBaseTimeNtp.tv_sec == 0 )
|
if ( mBaseTimeNtp.tv_sec == 0 ) {
|
||||||
{
|
|
||||||
mBaseTimeReal = tvNow();
|
mBaseTimeReal = tvNow();
|
||||||
mBaseTimeNtp = ntpTime;
|
mBaseTimeNtp = ntpTime;
|
||||||
mBaseTimeRtp = rtpTime;
|
mBaseTimeRtp = rtpTime;
|
||||||
}
|
} else if ( !mRtpClock ) {
|
||||||
else if ( !mRtpClock )
|
|
||||||
{
|
|
||||||
Debug( 5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x", mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime );
|
Debug( 5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x", mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime );
|
||||||
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
||||||
|
|
||||||
|
@ -227,8 +208,7 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint
|
||||||
mLastSrTimeRtp = rtpTime;
|
mLastSrTimeRtp = rtpTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpSource::updateRtcpStats()
|
void RtpSource::updateRtcpStats() {
|
||||||
{
|
|
||||||
uint32_t extendedMax = mCycles + mMaxSeq;
|
uint32_t extendedMax = mCycles + mMaxSeq;
|
||||||
mExpectedPackets = extendedMax - mBaseSeq + 1;
|
mExpectedPackets = extendedMax - mBaseSeq + 1;
|
||||||
|
|
||||||
|
@ -255,8 +235,7 @@ void RtpSource::updateRtcpStats()
|
||||||
Debug( 5, "Lost fraction = %d", mLostFraction );
|
Debug( 5, "Lost fraction = %d", mLostFraction );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) {
|
||||||
{
|
|
||||||
const RtpDataHeader *rtpHeader;
|
const RtpDataHeader *rtpHeader;
|
||||||
rtpHeader = (RtpDataHeader *)packet;
|
rtpHeader = (RtpDataHeader *)packet;
|
||||||
int rtpHeaderSize = 12 + rtpHeader->cc * 4;
|
int rtpHeaderSize = 12 + rtpHeader->cc * 4;
|
||||||
|
@ -269,39 +248,29 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
||||||
// that there is no marker bit by changing the number of bits in the payload type field.
|
// that there is no marker bit by changing the number of bits in the payload type field.
|
||||||
bool thisM = rtpHeader->m || h264FragmentEnd;
|
bool thisM = rtpHeader->m || h264FragmentEnd;
|
||||||
|
|
||||||
if ( updateSeq( ntohs(rtpHeader->seqN) ) )
|
if ( updateSeq( ntohs(rtpHeader->seqN) ) ) {
|
||||||
{
|
|
||||||
Hexdump( 4, packet+rtpHeaderSize, 16 );
|
Hexdump( 4, packet+rtpHeaderSize, 16 );
|
||||||
|
|
||||||
if ( mFrameGood )
|
if ( mFrameGood ) {
|
||||||
{
|
|
||||||
int extraHeader = 0;
|
int extraHeader = 0;
|
||||||
|
|
||||||
if( mCodecId == AV_CODEC_ID_H264 )
|
if ( mCodecId == AV_CODEC_ID_H264 ) {
|
||||||
{
|
|
||||||
int nalType = (packet[rtpHeaderSize] & 0x1f);
|
int nalType = (packet[rtpHeaderSize] & 0x1f);
|
||||||
Debug( 3, "Have H264 frame: nal type is %d", nalType );
|
Debug( 3, "Have H264 frame: nal type is %d", nalType );
|
||||||
|
|
||||||
switch (nalType)
|
switch (nalType) {
|
||||||
{
|
|
||||||
case 24: // STAP-A
|
case 24: // STAP-A
|
||||||
{
|
|
||||||
extraHeader = 2;
|
extraHeader = 2;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 25: // STAP-B
|
case 25: // STAP-B
|
||||||
case 26: // MTAP-16
|
case 26: // MTAP-16
|
||||||
case 27: // MTAP-24
|
case 27: // MTAP-24
|
||||||
{
|
|
||||||
extraHeader = 3;
|
extraHeader = 3;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
// FU-A and FU-B
|
// FU-A and FU-B
|
||||||
case 28: case 29:
|
case 28: case 29:
|
||||||
{
|
|
||||||
// Is this NAL the first NAL in fragmentation sequence
|
// Is this NAL the first NAL in fragmentation sequence
|
||||||
if ( packet[rtpHeaderSize+1] & 0x80 )
|
if ( packet[rtpHeaderSize+1] & 0x80 ) {
|
||||||
{
|
|
||||||
// Now we will form new header of frame
|
// Now we will form new header of frame
|
||||||
mFrame.append( "\x0\x0\x1\x0", 4 );
|
mFrame.append( "\x0\x0\x1\x0", 4 );
|
||||||
// Reconstruct NAL header from FU headers
|
// Reconstruct NAL header from FU headers
|
||||||
|
@ -311,17 +280,14 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
||||||
|
|
||||||
extraHeader = 2;
|
extraHeader = 2;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
Debug(3, "Unhandled nalType %d", nalType );
|
Debug(3, "Unhandled nalType %d", nalType );
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append NAL frame start code
|
// Append NAL frame start code
|
||||||
if ( !mFrame.size() )
|
if ( !mFrame.size() )
|
||||||
mFrame.append( "\x0\x0\x1", 3 );
|
mFrame.append( "\x0\x0\x1", 3 );
|
||||||
}
|
} // end if H264
|
||||||
mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader );
|
mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader );
|
||||||
} else {
|
} else {
|
||||||
Debug( 3, "NOT H264 frame: type is %d", mCodecId );
|
Debug( 3, "NOT H264 frame: type is %d", mCodecId );
|
||||||
|
@ -329,16 +295,13 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
||||||
|
|
||||||
Hexdump( 4, mFrame.head(), 16 );
|
Hexdump( 4, mFrame.head(), 16 );
|
||||||
|
|
||||||
if ( thisM )
|
if ( thisM ) {
|
||||||
{
|
if ( mFrameGood ) {
|
||||||
if ( mFrameGood )
|
|
||||||
{
|
|
||||||
Debug( 3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() );
|
Debug( 3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||||
|
|
||||||
mFrameProcessed.setValueImmediate( false );
|
mFrameProcessed.setValueImmediate( false );
|
||||||
mFrameReady.updateValueSignal( true );
|
mFrameReady.updateValueSignal( true );
|
||||||
if ( !mFrameProcessed.getValueImmediate() )
|
if ( !mFrameProcessed.getValueImmediate() ) {
|
||||||
{
|
|
||||||
// What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as
|
// What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as
|
||||||
// if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false;
|
// if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false;
|
||||||
|
|
||||||
|
@ -347,45 +310,34 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
||||||
return( false );
|
return( false );
|
||||||
}
|
}
|
||||||
mFrameCount++;
|
mFrameCount++;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size() );
|
Warning( "Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||||
}
|
}
|
||||||
mFrame.clear();
|
mFrame.clear();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if ( mFrame.size() ) {
|
||||||
{
|
|
||||||
if ( mFrame.size() )
|
|
||||||
{
|
|
||||||
Warning( "Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size() );
|
Warning( "Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Discarding frame %d", mFrameCount );
|
Warning( "Discarding frame %d", mFrameCount );
|
||||||
}
|
}
|
||||||
mFrameGood = false;
|
mFrameGood = false;
|
||||||
mFrame.clear();
|
mFrame.clear();
|
||||||
}
|
}
|
||||||
if ( thisM )
|
if ( thisM ) {
|
||||||
{
|
|
||||||
mFrameGood = true;
|
mFrameGood = true;
|
||||||
prevM = true;
|
prevM = true;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
prevM = false;
|
prevM = false;
|
||||||
|
|
||||||
updateJitter( rtpHeader );
|
updateJitter( rtpHeader );
|
||||||
|
|
||||||
return( true );
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpSource::getFrame( Buffer &buffer )
|
bool RtpSource::getFrame( Buffer &buffer ) {
|
||||||
{
|
|
||||||
Debug( 3, "Getting frame" );
|
Debug( 3, "Getting frame" );
|
||||||
if ( !mFrameReady.getValueImmediate() )
|
if ( !mFrameReady.getValueImmediate() ) {
|
||||||
{
|
|
||||||
// Allow for a couple of spurious returns
|
// Allow for a couple of spurious returns
|
||||||
for ( int count = 0; !mFrameReady.getUpdatedValue( 1 ); count++ )
|
for ( int count = 0; !mFrameReady.getUpdatedValue( 1 ); count++ )
|
||||||
if ( count > 1 )
|
if ( count > 1 )
|
||||||
|
@ -395,7 +347,7 @@ bool RtpSource::getFrame( Buffer &buffer )
|
||||||
mFrameReady.setValueImmediate( false );
|
mFrameReady.setValueImmediate( false );
|
||||||
mFrameProcessed.updateValueSignal( true );
|
mFrameProcessed.updateValueSignal( true );
|
||||||
Debug( 4, "Copied %d bytes", buffer.size() );
|
Debug( 4, "Copied %d bytes", buffer.size() );
|
||||||
return( true );
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBAVCODEC
|
#endif // HAVE_LIBAVCODEC
|
||||||
|
|
213
src/zm_rtsp.cpp
213
src/zm_rtsp.cpp
|
@ -46,53 +46,54 @@ bool RtspThread::sendCommand( std::string message ) {
|
||||||
message += stringtf( "CSeq: %d\r\n\r\n", ++mSeq );
|
message += stringtf( "CSeq: %d\r\n\r\n", ++mSeq );
|
||||||
Debug( 2, "Sending RTSP message: %s", message.c_str() );
|
Debug( 2, "Sending RTSP message: %s", message.c_str() );
|
||||||
if ( mMethod == RTP_RTSP_HTTP ) {
|
if ( mMethod == RTP_RTSP_HTTP ) {
|
||||||
message = base64Encode( message );
|
message = base64Encode(message);
|
||||||
Debug( 2, "Sending encoded RTSP message: %s", message.c_str() );
|
Debug(2, "Sending encoded RTSP message: %s", message.c_str());
|
||||||
if ( mRtspSocket2.send( message.c_str(), message.size() ) != (int)message.length() ) {
|
if ( mRtspSocket2.send(message.c_str(), message.size()) != (int)message.length() ) {
|
||||||
Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
|
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
|
||||||
return( false );
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() ) {
|
if ( mRtspSocket.send(message.c_str(), message.size()) != (int)message.length() ) {
|
||||||
Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
|
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
|
||||||
return( false );
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return( true );
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtspThread::recvResponse( std::string &response ) {
|
bool RtspThread::recvResponse( std::string &response ) {
|
||||||
if ( mRtspSocket.recv( response ) < 0 )
|
if ( mRtspSocket.recv( response ) < 0 )
|
||||||
Error( "Recv failed; %s", strerror(errno) );
|
Error("Recv failed; %s", strerror(errno));
|
||||||
Debug( 2, "Received RTSP response: %s (%zd bytes)", response.c_str(), response.size() );
|
Debug(2, "Received RTSP response: %s (%zd bytes)", response.c_str(), response.size());
|
||||||
float respVer = 0;
|
float respVer = 0;
|
||||||
respCode = -1;
|
respCode = -1;
|
||||||
char respText[ZM_NETWORK_BUFSIZ];
|
char respText[ZM_NETWORK_BUFSIZ];
|
||||||
if ( sscanf( response.c_str(), "RTSP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 ) {
|
if ( sscanf(response.c_str(), "RTSP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText) != 3 ) {
|
||||||
if ( isalnum(response[0]) ) {
|
if ( isalnum(response[0]) ) {
|
||||||
Error( "Response parse failure in '%s'", response.c_str() );
|
Error("Response parse failure in '%s'", response.c_str());
|
||||||
} else {
|
} else {
|
||||||
Error( "Response parse failure, %zd bytes follow", response.size() );
|
Error("Response parse failure, %zd bytes follow", response.size());
|
||||||
if ( response.size() )
|
if ( response.size() )
|
||||||
Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
|
Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
|
||||||
}
|
}
|
||||||
return( false );
|
return false;
|
||||||
}
|
}
|
||||||
if ( respCode == 401) {
|
if ( respCode == 401 ) {
|
||||||
Debug( 2, "Got 401 access denied response code, check WWW-Authenticate header and retry");
|
Debug( 2, "Got 401 access denied response code, check WWW-Authenticate header and retry");
|
||||||
mAuthenticator->checkAuthResponse(response);
|
mAuthenticator->checkAuthResponse(response);
|
||||||
mNeedAuth = true;
|
mNeedAuth = true;
|
||||||
return( false );
|
return false;
|
||||||
} else if ( respCode != 200 ) {
|
} else if ( respCode != 200 ) {
|
||||||
Error( "Unexpected response code %d, text is '%s'", respCode, respText );
|
Error("Unexpected response code %d, text is '%s'", respCode, respText);
|
||||||
return( false );
|
return false;
|
||||||
}
|
}
|
||||||
return( true );
|
return true;
|
||||||
}
|
} // end RtspThread::recResponse
|
||||||
|
|
||||||
int RtspThread::requestPorts() {
|
int RtspThread::requestPorts() {
|
||||||
if ( !smMinDataPort ) {
|
if ( !smMinDataPort ) {
|
||||||
char sql[ZM_SQL_SML_BUFSIZ];
|
char sql[ZM_SQL_SML_BUFSIZ];
|
||||||
|
//FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors
|
||||||
strncpy( sql, "select Id from Monitors where Function != 'None' and Type = 'Remote' and Protocol = 'rtsp' and Method = 'rtpUni' order by Id asc", sizeof(sql) );
|
strncpy( sql, "select Id from Monitors where Function != 'None' and Type = 'Remote' and Protocol = 'rtsp' and Method = 'rtpUni' order by Id asc", sizeof(sql) );
|
||||||
if ( mysql_query( &dbconn, sql ) ) {
|
if ( mysql_query( &dbconn, sql ) ) {
|
||||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||||
|
@ -107,7 +108,7 @@ int RtspThread::requestPorts() {
|
||||||
int nMonitors = mysql_num_rows( result );
|
int nMonitors = mysql_num_rows( result );
|
||||||
int position = 0;
|
int position = 0;
|
||||||
if ( nMonitors ) {
|
if ( nMonitors ) {
|
||||||
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) {
|
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
|
||||||
int id = atoi(dbrow[0]);
|
int id = atoi(dbrow[0]);
|
||||||
if ( mId == id ) {
|
if ( mId == id ) {
|
||||||
position = i;
|
position = i;
|
||||||
|
@ -126,22 +127,30 @@ int RtspThread::requestPorts() {
|
||||||
Debug( 2, "Assigned RTP port range is %d-%d", smMinDataPort, smMaxDataPort );
|
Debug( 2, "Assigned RTP port range is %d-%d", smMinDataPort, smMaxDataPort );
|
||||||
}
|
}
|
||||||
for ( int i = smMinDataPort; i <= smMaxDataPort; i++ ) {
|
for ( int i = smMinDataPort; i <= smMaxDataPort; i++ ) {
|
||||||
PortSet::const_iterator iter = smAssignedPorts.find( i );
|
PortSet::const_iterator iter = smAssignedPorts.find(i);
|
||||||
if ( iter == smAssignedPorts.end() ) {
|
if ( iter == smAssignedPorts.end() ) {
|
||||||
smAssignedPorts.insert( i );
|
smAssignedPorts.insert(i);
|
||||||
return( i );
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Panic( "Can assign RTP port, no ports left in pool" );
|
Panic("Can assign RTP port, no ports left in pool");
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspThread::releasePorts( int port ) {
|
void RtspThread::releasePorts( int port ) {
|
||||||
if ( port > 0 )
|
if ( port > 0 )
|
||||||
smAssignedPorts.erase( port );
|
smAssignedPorts.erase(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth, bool rtsp_describe) :
|
RtspThread::RtspThread(
|
||||||
|
int id,
|
||||||
|
RtspMethod method,
|
||||||
|
const std::string &protocol,
|
||||||
|
const std::string &host,
|
||||||
|
const std::string &port,
|
||||||
|
const std::string &path,
|
||||||
|
const std::string &auth,
|
||||||
|
bool rtsp_describe) :
|
||||||
mId( id ),
|
mId( id ),
|
||||||
mMethod( method ),
|
mMethod( method ),
|
||||||
mProtocol( protocol ),
|
mProtocol( protocol ),
|
||||||
|
@ -168,10 +177,10 @@ RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol,
|
||||||
|
|
||||||
mSsrc = rand();
|
mSsrc = rand();
|
||||||
|
|
||||||
Debug( 2, "RTSP Local SSRC is %x", mSsrc );
|
Debug(2, "RTSP Local SSRC is %x", mSsrc);
|
||||||
|
|
||||||
if ( mMethod == RTP_RTSP_HTTP )
|
if ( mMethod == RTP_RTSP_HTTP )
|
||||||
mHttpSession = stringtf( "%d", rand() );
|
mHttpSession = stringtf("%d", rand());
|
||||||
|
|
||||||
mNeedAuth = false;
|
mNeedAuth = false;
|
||||||
StringVector parts = split(auth,":");
|
StringVector parts = split(auth,":");
|
||||||
|
@ -216,8 +225,8 @@ int RtspThread::run() {
|
||||||
|
|
||||||
bool authTried = false;
|
bool authTried = false;
|
||||||
if ( mMethod == RTP_RTSP_HTTP ) {
|
if ( mMethod == RTP_RTSP_HTTP ) {
|
||||||
if ( !mRtspSocket2.connect( mHost.c_str(), mPort.c_str() ) )
|
if ( !mRtspSocket2.connect(mHost.c_str(), mPort.c_str()) )
|
||||||
Fatal( "Unable to connect auxiliary RTSP/HTTP socket" );
|
Fatal("Unable to connect auxiliary RTSP/HTTP socket");
|
||||||
//Select select( 0.25 );
|
//Select select( 0.25 );
|
||||||
//select.addReader( &mRtspSocket2 );
|
//select.addReader( &mRtspSocket2 );
|
||||||
//while ( select.wait() )
|
//while ( select.wait() )
|
||||||
|
@ -240,15 +249,15 @@ int RtspThread::run() {
|
||||||
message += "\r\n";
|
message += "\r\n";
|
||||||
Debug( 2, "Sending HTTP message: %s", message.c_str() );
|
Debug( 2, "Sending HTTP message: %s", message.c_str() );
|
||||||
if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() ) {
|
if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() ) {
|
||||||
Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
|
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
if ( mRtspSocket.recv( response ) < 0 ) {
|
if ( mRtspSocket.recv( response ) < 0 ) {
|
||||||
Error( "Recv failed; %s", strerror(errno) );
|
Error("Recv failed; %s", strerror(errno));
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug( 2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size() );
|
Debug(2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size());
|
||||||
float respVer = 0;
|
float respVer = 0;
|
||||||
respCode = -1;
|
respCode = -1;
|
||||||
if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 ) {
|
if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 ) {
|
||||||
|
@ -259,25 +268,25 @@ int RtspThread::run() {
|
||||||
if ( response.size() )
|
if ( response.size() )
|
||||||
Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
|
Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
|
||||||
}
|
}
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
// If Server requests authentication, check WWW-Authenticate header and fill required fields
|
// If Server requests authentication, check WWW-Authenticate header and fill required fields
|
||||||
// for requested authentication method
|
// for requested authentication method
|
||||||
if (respCode == 401 && !authTried) {
|
if ( respCode == 401 && !authTried ) {
|
||||||
mNeedAuth = true;
|
mNeedAuth = true;
|
||||||
mAuthenticator->checkAuthResponse(response);
|
mAuthenticator->checkAuthResponse(response);
|
||||||
Debug(2, "Processed 401 response");
|
Debug(2, "Processed 401 response");
|
||||||
mRtspSocket.close();
|
mRtspSocket.close();
|
||||||
if ( !mRtspSocket.connect( mHost.c_str(), mPort.c_str() ) )
|
if ( !mRtspSocket.connect(mHost.c_str(), mPort.c_str()) )
|
||||||
Fatal( "Unable to reconnect RTSP socket" );
|
Fatal("Unable to reconnect RTSP socket");
|
||||||
Debug(2, "connection should be reopened now");
|
Debug(2, "connection should be reopened now");
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (respCode == 401 && !authTried);
|
} while (respCode == 401 && !authTried);
|
||||||
|
|
||||||
if ( respCode != 200 ) {
|
if ( respCode != 200 ) {
|
||||||
Error( "Unexpected response code %d, text is '%s'", respCode, respText );
|
Error("Unexpected response code %d, text is '%s'", respCode, respText);
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message = "POST "+mPath+" HTTP/1.0\r\n";
|
message = "POST "+mPath+" HTTP/1.0\r\n";
|
||||||
|
@ -300,25 +309,25 @@ int RtspThread::run() {
|
||||||
// Request supported RTSP commands by the server
|
// Request supported RTSP commands by the server
|
||||||
message = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
|
message = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
|
||||||
if ( !sendCommand( message ) )
|
if ( !sendCommand( message ) )
|
||||||
return( -1 );
|
return -1;
|
||||||
|
|
||||||
// A negative return here may indicate auth failure, but we will have setup the auth mechanisms so we need to retry.
|
// A negative return here may indicate auth failure, but we will have setup the auth mechanisms so we need to retry.
|
||||||
if ( !recvResponse( response ) ) {
|
if ( !recvResponse(response) ) {
|
||||||
if ( mNeedAuth ) {
|
if ( mNeedAuth ) {
|
||||||
Debug( 2, "Resending OPTIONS due to possible auth requirement" );
|
Debug( 2, "Resending OPTIONS due to possible auth requirement" );
|
||||||
if ( !sendCommand( message ) )
|
if ( !sendCommand(message) )
|
||||||
return( -1 );
|
return -1;
|
||||||
if ( !recvResponse( response ) )
|
if ( !recvResponse(response) )
|
||||||
return( -1 );
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
} // end if failed response maybe due to auth
|
} // end if failed response maybe due to auth
|
||||||
|
|
||||||
char publicLine[256] = "";
|
char publicLine[256] = "";
|
||||||
StringVector lines = split( response, "\r\n" );
|
StringVector lines = split(response, "\r\n");
|
||||||
for ( size_t i = 0; i < lines.size(); i++ )
|
for ( size_t i = 0; i < lines.size(); i++ )
|
||||||
sscanf( lines[i].c_str(), "Public: %[^\r\n]\r\n", publicLine );
|
sscanf(lines[i].c_str(), "Public: %[^\r\n]\r\n", publicLine);
|
||||||
|
|
||||||
// Check if the server supports the GET_PARAMETER command
|
// Check if the server supports the GET_PARAMETER command
|
||||||
// If yes, it is likely that the server will request this command as a keepalive message
|
// If yes, it is likely that the server will request this command as a keepalive message
|
||||||
|
@ -331,44 +340,45 @@ int RtspThread::run() {
|
||||||
do {
|
do {
|
||||||
if (mNeedAuth)
|
if (mNeedAuth)
|
||||||
authTried = true;
|
authTried = true;
|
||||||
sendCommand( message );
|
sendCommand(message);
|
||||||
sleep( 1 );
|
// FIXME WHy sleep 1?
|
||||||
res = recvResponse( response );
|
sleep(1);
|
||||||
if (!res && respCode==401)
|
res = recvResponse(response);
|
||||||
|
if ( !res && respCode==401 )
|
||||||
mNeedAuth = true;
|
mNeedAuth = true;
|
||||||
} while (!res && respCode==401 && !authTried);
|
} while (!res && respCode==401 && !authTried);
|
||||||
|
|
||||||
const std::string endOfHeaders = "\r\n\r\n";
|
const std::string endOfHeaders = "\r\n\r\n";
|
||||||
size_t sdpStart = response.find( endOfHeaders );
|
size_t sdpStart = response.find(endOfHeaders);
|
||||||
if( sdpStart == std::string::npos )
|
if ( sdpStart == std::string::npos )
|
||||||
return( -1 );
|
return -1;
|
||||||
|
|
||||||
if ( mRtspDescribe ) {
|
if ( mRtspDescribe ) {
|
||||||
std::string DescHeader = response.substr( 0,sdpStart );
|
std::string DescHeader = response.substr(0, sdpStart);
|
||||||
Debug( 1, "Processing DESCRIBE response header '%s'", DescHeader.c_str() );
|
Debug(1, "Processing DESCRIBE response header '%s'", DescHeader.c_str());
|
||||||
|
|
||||||
lines = split( DescHeader, "\r\n" );
|
lines = split(DescHeader, "\r\n");
|
||||||
for ( size_t i = 0; i < lines.size(); i++ ) {
|
for ( size_t i = 0; i < lines.size(); i++ ) {
|
||||||
// If the device sends us a url value for Content-Base in the response header, we should use that instead
|
// If the device sends us a url value for Content-Base in the response header, we should use that instead
|
||||||
if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) ) {
|
if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) ) {
|
||||||
mUrl = trimSpaces( lines[i].substr( 13 ) );
|
mUrl = trimSpaces( lines[i].substr( 13 ) );
|
||||||
Info("Received new Content-Base in DESCRIBE response header. Updated device Url to: '%s'", mUrl.c_str() );
|
Info("Received new Content-Base in DESCRIBE response header. Updated device Url to: '%s'", mUrl.c_str() );
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} // end foreach line
|
||||||
|
} // end if mRtspDescribe
|
||||||
|
|
||||||
sdpStart += endOfHeaders.length();
|
sdpStart += endOfHeaders.length();
|
||||||
|
|
||||||
std::string sdp = response.substr( sdpStart );
|
std::string sdp = response.substr(sdpStart);
|
||||||
Debug( 1, "Processing SDP '%s'", sdp.c_str() );
|
Debug(1, "Processing SDP '%s'", sdp.c_str());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mSessDesc = new SessionDescriptor( mUrl, sdp );
|
mSessDesc = new SessionDescriptor( mUrl, sdp );
|
||||||
mFormatContext = mSessDesc->generateFormatContext();
|
mFormatContext = mSessDesc->generateFormatContext();
|
||||||
} catch( const Exception &e ) {
|
} catch( const Exception &e ) {
|
||||||
Error( e.getMessage().c_str() );
|
Error( e.getMessage().c_str() );
|
||||||
return( -1 );
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
@ -672,51 +682,36 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali
|
||||||
Hexdump( 4, (char *)buffer, 16 );
|
Hexdump( 4, (char *)buffer, 16 );
|
||||||
rtpDataThread.recvPacket( buffer+4, len );
|
rtpDataThread.recvPacket( buffer+4, len );
|
||||||
Debug( 4, "Received" );
|
Debug( 4, "Received" );
|
||||||
}
|
} else if ( channel == remoteChannels[1] ) {
|
||||||
else if ( channel == remoteChannels[1] )
|
|
||||||
{
|
|
||||||
// len = ntohs( *((unsigned short *)(buffer+2)) );
|
// len = ntohs( *((unsigned short *)(buffer+2)) );
|
||||||
// Debug( 4, "Got %d bytes on control channel %d", nBytes, channel );
|
// Debug( 4, "Got %d bytes on control channel %d", nBytes, channel );
|
||||||
Debug( 4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len );
|
Debug( 4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len );
|
||||||
Hexdump( 4, (char *)buffer, 16 );
|
Hexdump( 4, (char *)buffer, 16 );
|
||||||
rtpCtrlThread.recvPackets( buffer+4, len );
|
rtpCtrlThread.recvPackets( buffer+4, len );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Error( "Unexpected channel selector %d in RTSP interleaved data", buffer[1] );
|
Error( "Unexpected channel selector %d in RTSP interleaved data", buffer[1] );
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer.consume( len+4 );
|
buffer.consume( len+4 );
|
||||||
nBytes -= len+4;
|
nBytes -= len+4;
|
||||||
}
|
} else {
|
||||||
else
|
if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 ) {
|
||||||
{
|
|
||||||
if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 )
|
|
||||||
{
|
|
||||||
Debug( 4, "Got keepalive response '%s'", (char *)buffer );
|
Debug( 4, "Got keepalive response '%s'", (char *)buffer );
|
||||||
//buffer.consume( keepaliveResponse.size() );
|
//buffer.consume( keepaliveResponse.size() );
|
||||||
if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
|
if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) ) {
|
||||||
{
|
|
||||||
int discardBytes = charPtr-(char *)buffer;
|
int discardBytes = charPtr-(char *)buffer;
|
||||||
buffer -= discardBytes;
|
buffer -= discardBytes;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) ) {
|
||||||
{
|
|
||||||
if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
|
|
||||||
{
|
|
||||||
int discardBytes = charPtr-(char *)buffer;
|
int discardBytes = charPtr-(char *)buffer;
|
||||||
Warning( "Unexpected format RTSP interleaved data, resyncing by %d bytes", discardBytes );
|
Warning( "Unexpected format RTSP interleaved data, resyncing by %d bytes", discardBytes );
|
||||||
Hexdump( -1, (char *)buffer, discardBytes );
|
Hexdump( -1, (char *)buffer, discardBytes );
|
||||||
buffer -= discardBytes;
|
buffer -= discardBytes;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Warning( "Unexpected format RTSP interleaved data, dumping %d bytes", buffer.size() );
|
Warning( "Unexpected format RTSP interleaved data, dumping %d bytes", buffer.size() );
|
||||||
Hexdump( -1, (char *)buffer, 32 );
|
Hexdump( -1, (char *)buffer, 32 );
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
@ -764,16 +759,14 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali
|
||||||
rtpDataThread.start();
|
rtpDataThread.start();
|
||||||
rtpCtrlThread.start();
|
rtpCtrlThread.start();
|
||||||
|
|
||||||
while( !mStop )
|
while ( !mStop ) {
|
||||||
{
|
|
||||||
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
|
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
|
||||||
if ( sendKeepalive && (timeout > 0) && ((time(NULL)-lastKeepalive) > (timeout-5)) )
|
if ( sendKeepalive && (timeout > 0) && ((time(NULL)-lastKeepalive) > (timeout-5)) ) {
|
||||||
{
|
|
||||||
if ( !sendCommand( message ) )
|
if ( !sendCommand( message ) )
|
||||||
return( -1 );
|
return -1;
|
||||||
lastKeepalive = time(NULL);
|
lastKeepalive = time(NULL);
|
||||||
}
|
}
|
||||||
usleep( 100000 );
|
usleep(100000);
|
||||||
}
|
}
|
||||||
#if 0
|
#if 0
|
||||||
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
|
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
|
||||||
|
@ -783,10 +776,10 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali
|
||||||
return( -1 );
|
return( -1 );
|
||||||
#endif
|
#endif
|
||||||
message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
|
message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
|
||||||
if ( !sendCommand( message ) )
|
if ( !sendCommand(message) )
|
||||||
return( -1 );
|
return -1;
|
||||||
if ( !recvResponse( response ) )
|
if ( !recvResponse(response) )
|
||||||
return( -1 );
|
return -1;
|
||||||
|
|
||||||
rtpDataThread.stop();
|
rtpDataThread.stop();
|
||||||
rtpCtrlThread.stop();
|
rtpCtrlThread.stop();
|
||||||
|
@ -801,13 +794,11 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
Panic("Got unexpected method %d", mMethod);
|
||||||
Panic( "Got unexpected method %d", mMethod );
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return( 0 );
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
197
src/zm_sdp.cpp
197
src/zm_sdp.cpp
|
@ -26,17 +26,17 @@
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
||||||
{ 0, "PCMU", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_MULAW, 8000, 1 },
|
{ 0, "PCMU", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_MULAW, 8000, 1 },
|
||||||
{ 3, "GSM", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 3, "GSM", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 4, "G723", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 4, "G723", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 5, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 5, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 6, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 16000, 1 },
|
{ 6, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 16000, 1 },
|
||||||
{ 7, "LPC", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 7, "LPC", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 8, "PCMA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_ALAW, 8000, 1 },
|
{ 8, "PCMA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_ALAW, 8000, 1 },
|
||||||
{ 9, "G722", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 9, "G722", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 10, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 2 },
|
{ 10, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 2 },
|
||||||
{ 11, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 1 },
|
{ 11, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 1 },
|
||||||
{ 12, "QCELP", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_QCELP, 8000, 1 },
|
{ 12, "QCELP", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_QCELP, 8000, 1 },
|
||||||
{ 13, "CN", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 13, "CN", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP2, -1, -1 },
|
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP2, -1, -1 },
|
||||||
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3, -1, -1 },
|
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3, -1, -1 },
|
||||||
{ 15, "G728", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 15, "G728", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
|
@ -45,36 +45,36 @@ SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
||||||
{ 18, "G729", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
{ 18, "G729", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 25, "CelB", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
|
{ 25, "CelB", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
|
||||||
{ 26, "JPEG", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MJPEG, 90000, -1 },
|
{ 26, "JPEG", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MJPEG, 90000, -1 },
|
||||||
{ 28, "nv", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
|
{ 28, "nv", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
|
||||||
{ 31, "H261", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H261, 90000, -1 },
|
{ 31, "H261", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H261, 90000, -1 },
|
||||||
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG1VIDEO, 90000, -1 },
|
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG1VIDEO, 90000, -1 },
|
||||||
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO, 90000, -1 },
|
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO, 90000, -1 },
|
||||||
{ 33, "MP2T", AVMEDIA_TYPE_DATA, AV_CODEC_ID_MPEG2TS, 90000, -1 },
|
{ 33, "MP2T", AVMEDIA_TYPE_DATA, AV_CODEC_ID_MPEG2TS, 90000, -1 },
|
||||||
{ 34, "H263", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H263, 90000, -1 },
|
{ 34, "H263", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H263, 90000, -1 },
|
||||||
{ -1, "", AVMEDIA_TYPE_UNKNOWN, AV_CODEC_ID_NONE, -1, -1 }
|
{ -1, "", AVMEDIA_TYPE_UNKNOWN, AV_CODEC_ID_NONE, -1, -1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = {
|
SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = {
|
||||||
{ "MP4V-ES", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 },
|
{ "MP4V-ES", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 },
|
||||||
{ "mpeg4-generic", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
|
{ "mpeg4-generic", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
|
||||||
{ "H264", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
|
{ "H264", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
|
||||||
{ "AMR", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AMR_NB },
|
{ "AMR", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AMR_NB },
|
||||||
{ "vnd.onvif.metadata", AVMEDIA_TYPE_DATA, AV_CODEC_ID_NONE }
|
{ "vnd.onvif.metadata", AVMEDIA_TYPE_DATA, AV_CODEC_ID_NONE }
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
||||||
{ 0, "PCMU", CODEC_TYPE_AUDIO, CODEC_ID_PCM_MULAW, 8001, 1 },
|
{ 0, "PCMU", CODEC_TYPE_AUDIO, CODEC_ID_PCM_MULAW, 8001, 1 },
|
||||||
{ 3, "GSM", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 3, "GSM", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 4, "G723", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 4, "G723", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 5, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 5, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 6, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 16000, 1 },
|
{ 6, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 16000, 1 },
|
||||||
{ 7, "LPC", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 7, "LPC", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 8, "PCMA", CODEC_TYPE_AUDIO, CODEC_ID_PCM_ALAW, 8000, 1 },
|
{ 8, "PCMA", CODEC_TYPE_AUDIO, CODEC_ID_PCM_ALAW, 8000, 1 },
|
||||||
{ 9, "G722", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 9, "G722", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 10, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 2 },
|
{ 10, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 2 },
|
||||||
{ 11, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 1 },
|
{ 11, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 1 },
|
||||||
{ 12, "QCELP", CODEC_TYPE_AUDIO, CODEC_ID_QCELP, 8000, 1 },
|
{ 12, "QCELP", CODEC_TYPE_AUDIO, CODEC_ID_QCELP, 8000, 1 },
|
||||||
{ 13, "CN", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 13, "CN", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP2, -1, -1 },
|
{ 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP2, -1, -1 },
|
||||||
{ 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP3, -1, -1 },
|
{ 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP3, -1, -1 },
|
||||||
{ 15, "G728", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 15, "G728", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
|
@ -83,7 +83,7 @@ SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
|
||||||
{ 18, "G729", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
{ 18, "G729", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 },
|
||||||
{ 25, "CelB", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 },
|
{ 25, "CelB", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 },
|
||||||
{ 26, "JPEG", CODEC_TYPE_VIDEO, CODEC_ID_MJPEG, 90000, -1 },
|
{ 26, "JPEG", CODEC_TYPE_VIDEO, CODEC_ID_MJPEG, 90000, -1 },
|
||||||
{ 28, "nv", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 },
|
{ 28, "nv", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 },
|
||||||
{ 31, "H261", CODEC_TYPE_VIDEO, CODEC_ID_H261, 90000, -1 },
|
{ 31, "H261", CODEC_TYPE_VIDEO, CODEC_ID_H261, 90000, -1 },
|
||||||
{ 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG1VIDEO, 90000, -1 },
|
{ 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG1VIDEO, 90000, -1 },
|
||||||
{ 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG2VIDEO, 90000, -1 },
|
{ 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG2VIDEO, 90000, -1 },
|
||||||
|
@ -105,7 +105,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) :
|
||||||
mTtl( 16 ),
|
mTtl( 16 ),
|
||||||
mNoAddresses( 0 )
|
mNoAddresses( 0 )
|
||||||
{
|
{
|
||||||
StringVector tokens = split( connInfo, " " );
|
StringVector tokens = split(connInfo, " ");
|
||||||
if ( tokens.size() < 3 )
|
if ( tokens.size() < 3 )
|
||||||
throw Exception( "Unable to parse SDP connection info from '"+connInfo+"'" );
|
throw Exception( "Unable to parse SDP connection info from '"+connInfo+"'" );
|
||||||
mNetworkType = tokens[0];
|
mNetworkType = tokens[0];
|
||||||
|
@ -136,7 +136,12 @@ SessionDescriptor::BandInfo::BandInfo( const std::string &bandInfo ) :
|
||||||
mValue = atoi(tokens[1].c_str());
|
mValue = atoi(tokens[1].c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionDescriptor::MediaDescriptor::MediaDescriptor( const std::string &type, int port, int numPorts, const std::string &transport, int payloadType ) :
|
SessionDescriptor::MediaDescriptor::MediaDescriptor(
|
||||||
|
const std::string &type,
|
||||||
|
int port,
|
||||||
|
int numPorts,
|
||||||
|
const std::string &transport,
|
||||||
|
int payloadType ) :
|
||||||
mType( type ),
|
mType( type ),
|
||||||
mPort( port ),
|
mPort( port ),
|
||||||
mNumPorts( numPorts ),
|
mNumPorts( numPorts ),
|
||||||
|
@ -164,14 +169,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string
|
||||||
if ( line.empty() )
|
if ( line.empty() )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
Debug( 3, "Processing SDP line '%s'", line.c_str() );
|
Debug(3, "Processing SDP line '%s'", line.c_str());
|
||||||
const char sdpType = line[0];
|
const char sdpType = line[0];
|
||||||
if ( line[1] != '=' )
|
if ( line[1] != '=' )
|
||||||
throw Exception( "Invalid SDP format at '"+line+"'" );
|
throw Exception("Invalid SDP format at '"+line+"'");
|
||||||
|
|
||||||
line.erase( 0, 2 );
|
line.erase(0, 2);
|
||||||
switch( sdpType )
|
switch( sdpType ) {
|
||||||
{
|
|
||||||
case 'v' :
|
case 'v' :
|
||||||
mVersion = line;
|
mVersion = line;
|
||||||
break;
|
break;
|
||||||
|
@ -204,19 +208,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string
|
||||||
mAttributes.push_back( line );
|
mAttributes.push_back( line );
|
||||||
StringVector tokens = split( line, ":", 2 );
|
StringVector tokens = split( line, ":", 2 );
|
||||||
std::string attrName = tokens[0];
|
std::string attrName = tokens[0];
|
||||||
if ( currMedia )
|
if ( currMedia ) {
|
||||||
{
|
if ( attrName == "control" ) {
|
||||||
if ( attrName == "control" )
|
|
||||||
{
|
|
||||||
if ( tokens.size() < 2 )
|
if ( tokens.size() < 2 )
|
||||||
throw Exception( "Unable to parse SDP control attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
throw Exception( "Unable to parse SDP control attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
||||||
currMedia->setControlUrl( tokens[1] );
|
currMedia->setControlUrl( tokens[1] );
|
||||||
}
|
} else if ( attrName == "range" ) {
|
||||||
else if ( attrName == "range" )
|
} else if ( attrName == "rtpmap" ) {
|
||||||
{
|
|
||||||
}
|
|
||||||
else if ( attrName == "rtpmap" )
|
|
||||||
{
|
|
||||||
// a=rtpmap:96 MP4V-ES/90000
|
// a=rtpmap:96 MP4V-ES/90000
|
||||||
if ( tokens.size() < 2 )
|
if ( tokens.size() < 2 )
|
||||||
throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
||||||
|
@ -226,53 +224,46 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string
|
||||||
throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) );
|
throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) );
|
||||||
std::string payloadDesc = attrTokens[1];
|
std::string payloadDesc = attrTokens[1];
|
||||||
//currMedia->setPayloadType( payloadType );
|
//currMedia->setPayloadType( payloadType );
|
||||||
if ( attrTokens.size() > 1 )
|
if ( attrTokens.size() > 1 ) {
|
||||||
{
|
|
||||||
StringVector payloadTokens = split( attrTokens[1], "/" );
|
StringVector payloadTokens = split( attrTokens[1], "/" );
|
||||||
std::string payloadDesc = payloadTokens[0];
|
std::string payloadDesc = payloadTokens[0];
|
||||||
int payloadClock = atoi(payloadTokens[1].c_str());
|
int payloadClock = atoi(payloadTokens[1].c_str());
|
||||||
currMedia->setPayloadDesc( payloadDesc );
|
currMedia->setPayloadDesc( payloadDesc );
|
||||||
currMedia->setClock( payloadClock );
|
currMedia->setClock( payloadClock );
|
||||||
}
|
}
|
||||||
}
|
} else if ( attrName == "framesize" ) {
|
||||||
else if ( attrName == "framesize" )
|
|
||||||
{
|
|
||||||
// a=framesize:96 320-240
|
// a=framesize:96 320-240
|
||||||
if ( tokens.size() < 2 )
|
if ( tokens.size() < 2 )
|
||||||
throw Exception( "Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
throw Exception("Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'");
|
||||||
StringVector attrTokens = split( tokens[1], " " );
|
StringVector attrTokens = split(tokens[1], " ");
|
||||||
int payloadType = atoi(attrTokens[0].c_str());
|
int payloadType = atoi(attrTokens[0].c_str());
|
||||||
if ( payloadType != currMedia->getPayloadType() )
|
if ( payloadType != currMedia->getPayloadType() )
|
||||||
throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) );
|
throw Exception( stringtf("Payload type mismatch, expected %d, got %d in '%s'",
|
||||||
|
currMedia->getPayloadType(), payloadType, line.c_str()));
|
||||||
//currMedia->setPayloadType( payloadType );
|
//currMedia->setPayloadType( payloadType );
|
||||||
StringVector sizeTokens = split( attrTokens[1], "-" );
|
StringVector sizeTokens = split(attrTokens[1], "-");
|
||||||
int width = atoi(sizeTokens[0].c_str());
|
int width = atoi(sizeTokens[0].c_str());
|
||||||
int height = atoi(sizeTokens[1].c_str());
|
int height = atoi(sizeTokens[1].c_str());
|
||||||
currMedia->setFrameSize( width, height );
|
currMedia->setFrameSize(width, height);
|
||||||
}
|
} else if ( attrName == "framerate" ) {
|
||||||
else if ( attrName == "framerate" )
|
|
||||||
{
|
|
||||||
// a=framerate:5.0
|
// a=framerate:5.0
|
||||||
if ( tokens.size() < 2 )
|
if ( tokens.size() < 2 )
|
||||||
throw Exception( "Unable to parse SDP framerate attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
throw Exception("Unable to parse SDP framerate attribute '"+line+"' for media '"+currMedia->getType()+"'");
|
||||||
double frameRate = atof(tokens[1].c_str());
|
double frameRate = atof(tokens[1].c_str());
|
||||||
currMedia->setFrameRate( frameRate );
|
currMedia->setFrameRate(frameRate);
|
||||||
}
|
} else if ( attrName == "fmtp" ) {
|
||||||
else if ( attrName == "fmtp" )
|
|
||||||
{
|
|
||||||
// a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F
|
// a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F
|
||||||
if ( tokens.size() < 2 )
|
if ( tokens.size() < 2 )
|
||||||
throw Exception( "Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'" );
|
throw Exception("Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'");
|
||||||
StringVector attrTokens = split( tokens[1], " ", 2 );
|
StringVector attrTokens = split(tokens[1], " ", 2);
|
||||||
int payloadType = atoi(attrTokens[0].c_str());
|
int payloadType = atoi(attrTokens[0].c_str());
|
||||||
if ( payloadType != currMedia->getPayloadType() )
|
if ( payloadType != currMedia->getPayloadType() )
|
||||||
throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) );
|
throw Exception(stringtf("Payload type mismatch, expected %d, got %d in '%s'",
|
||||||
|
currMedia->getPayloadType(), payloadType, line.c_str()));
|
||||||
//currMedia->setPayloadType( payloadType );
|
//currMedia->setPayloadType( payloadType );
|
||||||
if ( attrTokens.size() > 1 )
|
if ( attrTokens.size() > 1 ) {
|
||||||
{
|
|
||||||
StringVector attr2Tokens = split( attrTokens[1], "; " );
|
StringVector attr2Tokens = split( attrTokens[1], "; " );
|
||||||
for ( unsigned int i = 0; i < attr2Tokens.size(); i++ )
|
for ( unsigned int i = 0; i < attr2Tokens.size(); i++ ) {
|
||||||
{
|
|
||||||
StringVector attr3Tokens = split( attr2Tokens[i], "=" );
|
StringVector attr3Tokens = split( attr2Tokens[i], "=" );
|
||||||
//Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() );
|
//Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() );
|
||||||
if ( attr3Tokens[0] == "profile-level-id" ) {
|
if ( attr3Tokens[0] == "profile-level-id" ) {
|
||||||
|
@ -292,40 +283,39 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string
|
||||||
} else if ( attrName == "mpeg4-esid" ) {
|
} else if ( attrName == "mpeg4-esid" ) {
|
||||||
// a=mpeg4-esid:201
|
// a=mpeg4-esid:201
|
||||||
} else {
|
} else {
|
||||||
Debug( 3, "Ignoring SDP attribute '%s' for media '%s'", line.c_str(), currMedia->getType().c_str() )
|
Debug(3, "Ignoring SDP attribute '%s' for media '%s'", line.c_str(), currMedia->getType().c_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Debug( 3, "Ignoring general SDP attribute '%s'", line.c_str() );
|
Debug(3, "Ignoring general SDP attribute '%s'", line.c_str());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'm' :
|
case 'm' :
|
||||||
{
|
{
|
||||||
StringVector tokens = split( line, " " );
|
StringVector tokens = split(line, " ");
|
||||||
if ( tokens.size() < 4 )
|
if ( tokens.size() < 4 )
|
||||||
throw Exception( "Can't parse SDP media description '"+line+"'" );
|
throw Exception("Can't parse SDP media description '"+line+"'");
|
||||||
std::string mediaType = tokens[0];
|
std::string mediaType = tokens[0];
|
||||||
if ( mediaType != "audio" && mediaType != "video" && mediaType != "application" )
|
if ( mediaType != "audio" && mediaType != "video" && mediaType != "application" )
|
||||||
throw Exception( "Unsupported media type '"+mediaType+"' in SDP media attribute '"+line+"'" );
|
throw Exception("Unsupported media type '"+mediaType+"' in SDP media attribute '"+line+"'");
|
||||||
StringVector portTokens = split( tokens[1], "/" );
|
StringVector portTokens = split(tokens[1], "/");
|
||||||
int mediaPort = atoi(portTokens[0].c_str());
|
int mediaPort = atoi(portTokens[0].c_str());
|
||||||
int mediaNumPorts = 1;
|
int mediaNumPorts = 1;
|
||||||
if ( portTokens.size() > 1 )
|
if ( portTokens.size() > 1 )
|
||||||
mediaNumPorts = atoi(portTokens[1].c_str());
|
mediaNumPorts = atoi(portTokens[1].c_str());
|
||||||
std::string mediaTransport = tokens[2];
|
std::string mediaTransport = tokens[2];
|
||||||
if ( mediaTransport != "RTP/AVP" )
|
if ( mediaTransport != "RTP/AVP" )
|
||||||
throw Exception( "Unsupported media transport '"+mediaTransport+"' in SDP media attribute '"+line+"'" );
|
throw Exception("Unsupported media transport '"+mediaTransport+"' in SDP media attribute '"+line+"'");
|
||||||
int payloadType = atoi(tokens[3].c_str());
|
int payloadType = atoi(tokens[3].c_str());
|
||||||
currMedia = new MediaDescriptor( mediaType, mediaPort, mediaNumPorts, mediaTransport, payloadType );
|
currMedia = new MediaDescriptor(mediaType, mediaPort, mediaNumPorts, mediaTransport, payloadType);
|
||||||
mMediaList.push_back( currMedia );
|
mMediaList.push_back(currMedia);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} // end switch
|
||||||
}
|
} // end foreach line
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionDescriptor::~SessionDescriptor()
|
SessionDescriptor::~SessionDescriptor() {
|
||||||
{
|
|
||||||
if ( mConnInfo )
|
if ( mConnInfo )
|
||||||
delete mConnInfo;
|
delete mConnInfo;
|
||||||
if ( mBandInfo )
|
if ( mBandInfo )
|
||||||
|
@ -334,8 +324,7 @@ SessionDescriptor::~SessionDescriptor()
|
||||||
delete mMediaList[i];
|
delete mMediaList[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFormatContext *SessionDescriptor::generateFormatContext() const
|
AVFormatContext *SessionDescriptor::generateFormatContext() const {
|
||||||
{
|
|
||||||
AVFormatContext *formatContext = avformat_alloc_context();
|
AVFormatContext *formatContext = avformat_alloc_context();
|
||||||
|
|
||||||
#if (LIBAVFORMAT_VERSION_CHECK(58, 12, 0, 0, 100))
|
#if (LIBAVFORMAT_VERSION_CHECK(58, 12, 0, 0, 100))
|
||||||
|
@ -353,35 +342,40 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
for ( unsigned int i = 0; i < mMediaList.size(); i++ ) {
|
for ( unsigned int i = 0; i < mMediaList.size(); i++ ) {
|
||||||
const MediaDescriptor *mediaDesc = mMediaList[i];
|
const MediaDescriptor *mediaDesc = mMediaList[i];
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0)
|
||||||
AVStream *stream = av_new_stream( formatContext, i );
|
AVStream *stream = av_new_stream(formatContext, i);
|
||||||
#else
|
#else
|
||||||
AVStream *stream = avformat_new_stream( formatContext, NULL );
|
AVStream *stream = avformat_new_stream(formatContext, NULL);
|
||||||
stream->id = i;
|
stream->id = i;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
AVCodecContext *codec_context = avcodec_alloc_context3(NULL);
|
AVCodecContext *codec_context = avcodec_alloc_context3(NULL);
|
||||||
avcodec_parameters_to_context(codec_context, stream->codecpar);
|
avcodec_parameters_to_context(codec_context, stream->codecpar);
|
||||||
|
stream->codec = codec_context;
|
||||||
#else
|
#else
|
||||||
AVCodecContext *codec_context = stream->codec;
|
AVCodecContext *codec_context = stream->codec;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Debug( 1, "Looking for codec for %s payload type %d / %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() );
|
std::string type = mediaDesc->getType();
|
||||||
|
Debug(1, "Looking for codec for %s payload type %d / %s",
|
||||||
|
type.c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str());
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
if ( mediaDesc->getType() == "video" )
|
if ( type == "video" )
|
||||||
codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
|
codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
else if ( mediaDesc->getType() == "audio" )
|
else if ( type == "audio" )
|
||||||
codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||||
else if ( mediaDesc->getType() == "application" )
|
else if ( type == "application" )
|
||||||
codec_context->codec_type = AVMEDIA_TYPE_DATA;
|
codec_context->codec_type = AVMEDIA_TYPE_DATA;
|
||||||
#else
|
#else
|
||||||
if ( mediaDesc->getType() == "video" )
|
if ( type == "video" )
|
||||||
codec_context->codec_type = CODEC_TYPE_VIDEO;
|
codec_context->codec_type = CODEC_TYPE_VIDEO;
|
||||||
else if ( mediaDesc->getType() == "audio" )
|
else if ( type == "audio" )
|
||||||
codec_context->codec_type = CODEC_TYPE_AUDIO;
|
codec_context->codec_type = CODEC_TYPE_AUDIO;
|
||||||
else if ( mediaDesc->getType() == "application" )
|
else if ( type == "application" )
|
||||||
codec_context->codec_type = CODEC_TYPE_DATA;
|
codec_context->codec_type = CODEC_TYPE_DATA;
|
||||||
#endif
|
#endif
|
||||||
|
else
|
||||||
|
Warning("Unknown media_type %s", type.c_str());
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
||||||
std::string codec_name;
|
std::string codec_name;
|
||||||
|
@ -392,9 +386,9 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
if ( smStaticPayloads[i].payloadType == mediaDesc->getPayloadType() ) {
|
if ( smStaticPayloads[i].payloadType == mediaDesc->getPayloadType() ) {
|
||||||
Debug( 1, "Got static payload type %d, %s", smStaticPayloads[i].payloadType, smStaticPayloads[i].payloadName );
|
Debug( 1, "Got static payload type %d, %s", smStaticPayloads[i].payloadType, smStaticPayloads[i].payloadName );
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
||||||
codec_name = std::string( smStaticPayloads[i].payloadName );
|
codec_name = std::string(smStaticPayloads[i].payloadName);
|
||||||
#else
|
#else
|
||||||
strncpy( codec_context->codec_name, smStaticPayloads[i].payloadName, sizeof(codec_context->codec_name) );;
|
strncpy(codec_context->codec_name, smStaticPayloads[i].payloadName, sizeof(codec_context->codec_name));
|
||||||
#endif
|
#endif
|
||||||
codec_context->codec_type = smStaticPayloads[i].codecType;
|
codec_context->codec_type = smStaticPayloads[i].codecType;
|
||||||
codec_context->codec_id = smStaticPayloads[i].codecId;
|
codec_context->codec_id = smStaticPayloads[i].codecId;
|
||||||
|
@ -406,11 +400,11 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
// Look in dynamic table
|
// Look in dynamic table
|
||||||
for ( unsigned int i = 0; i < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); i++ ) {
|
for ( unsigned int i = 0; i < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); i++ ) {
|
||||||
if ( smDynamicPayloads[i].payloadName == mediaDesc->getPayloadDesc() ) {
|
if ( smDynamicPayloads[i].payloadName == mediaDesc->getPayloadDesc() ) {
|
||||||
Debug( 1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[i].payloadName );
|
Debug(1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[i].payloadName);
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
||||||
codec_name = std::string( smStaticPayloads[i].payloadName );
|
codec_name = std::string(smStaticPayloads[i].payloadName);
|
||||||
#else
|
#else
|
||||||
strncpy( codec_context->codec_name, smDynamicPayloads[i].payloadName, sizeof(codec_context->codec_name) );;
|
strncpy(codec_context->codec_name, smDynamicPayloads[i].payloadName, sizeof(codec_context->codec_name));
|
||||||
#endif
|
#endif
|
||||||
codec_context->codec_type = smDynamicPayloads[i].codecType;
|
codec_context->codec_type = smDynamicPayloads[i].codecType;
|
||||||
codec_context->codec_id = smDynamicPayloads[i].codecId;
|
codec_context->codec_id = smDynamicPayloads[i].codecId;
|
||||||
|
@ -418,7 +412,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} /// end if static or dynamic
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103)
|
||||||
if ( codec_name.empty() )
|
if ( codec_name.empty() )
|
||||||
|
@ -426,7 +420,8 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
if ( !stream->codec->codec_name[0] )
|
if ( !stream->codec->codec_name[0] )
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
Warning( "Can't find payload details for %s payload type %d, name %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() );
|
Warning( "Can't find payload details for %s payload type %d, name %s",
|
||||||
|
mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() );
|
||||||
//return( 0 );
|
//return( 0 );
|
||||||
}
|
}
|
||||||
if ( mediaDesc->getWidth() )
|
if ( mediaDesc->getWidth() )
|
||||||
|
@ -449,11 +444,11 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
|
|
||||||
while (*value && *value != ','
|
while (*value && *value != ','
|
||||||
&& (dst - base64packet) < (long)(sizeof(base64packet)) - 1) {
|
&& (dst - base64packet) < (long)(sizeof(base64packet)) - 1) {
|
||||||
*dst++ = *value++;
|
*dst++ = *value++;
|
||||||
}
|
}
|
||||||
*dst++ = '\0';
|
*dst++ = '\0';
|
||||||
|
|
||||||
if (*value == ',')
|
if ( *value == ',' )
|
||||||
value++;
|
value++;
|
||||||
|
|
||||||
packet_size= av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet));
|
packet_size= av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet));
|
||||||
|
@ -468,23 +463,23 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
FF_INPUT_BUFFER_PADDING_SIZE
|
FF_INPUT_BUFFER_PADDING_SIZE
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
if(dest) {
|
if ( dest ) {
|
||||||
if(codec_context->extradata_size) {
|
if ( codec_context->extradata_size ) {
|
||||||
// av_realloc?
|
// av_realloc?
|
||||||
memcpy(dest, codec_context->extradata, codec_context->extradata_size);
|
memcpy(dest, codec_context->extradata, codec_context->extradata_size);
|
||||||
av_free(codec_context->extradata);
|
av_free(codec_context->extradata);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(dest+codec_context->extradata_size, start_sequence, sizeof(start_sequence));
|
memcpy(dest+codec_context->extradata_size, start_sequence, sizeof(start_sequence));
|
||||||
memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size);
|
memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size);
|
||||||
memset(dest+codec_context->extradata_size+sizeof(start_sequence)+
|
memset(dest+codec_context->extradata_size+sizeof(start_sequence)+
|
||||||
packet_size, 0,
|
packet_size, 0,
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0)
|
#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0)
|
||||||
AV_INPUT_BUFFER_PADDING_SIZE
|
AV_INPUT_BUFFER_PADDING_SIZE
|
||||||
#else
|
#else
|
||||||
FF_INPUT_BUFFER_PADDING_SIZE
|
FF_INPUT_BUFFER_PADDING_SIZE
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
codec_context->extradata= dest;
|
codec_context->extradata= dest;
|
||||||
codec_context->extradata_size+= sizeof(start_sequence)+packet_size;
|
codec_context->extradata_size+= sizeof(start_sequence)+packet_size;
|
||||||
|
@ -497,7 +492,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return( formatContext );
|
return formatContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
|
@ -103,7 +103,6 @@ VideoStore::VideoStore(
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
|
||||||
video_out_stream = avformat_new_stream(oc, NULL);
|
video_out_stream = avformat_new_stream(oc, NULL);
|
||||||
if ( !video_out_stream ) {
|
if ( !video_out_stream ) {
|
||||||
Error("Unable to create video out stream");
|
Error("Unable to create video out stream");
|
||||||
|
@ -112,6 +111,7 @@ VideoStore::VideoStore(
|
||||||
Debug(2, "Success creating video out stream");
|
Debug(2, "Success creating video out stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
// by allocating our own copy, we don't run into the problems when we free the streams
|
// by allocating our own copy, we don't run into the problems when we free the streams
|
||||||
video_out_ctx = avcodec_alloc_context3(video_out_codec);
|
video_out_ctx = avcodec_alloc_context3(video_out_codec);
|
||||||
// Since we are not re-encoding, all we have to do is copy the parameters
|
// Since we are not re-encoding, all we have to do is copy the parameters
|
||||||
|
@ -122,21 +122,9 @@ VideoStore::VideoStore(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
video_out_stream = avformat_new_stream(oc, NULL);
|
|
||||||
if ( !video_out_stream ) {
|
|
||||||
Error("Unable to create video out stream");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Debug(2, "Success creating video out stream");
|
|
||||||
}
|
|
||||||
video_out_ctx = video_out_stream->codec;
|
video_out_ctx = video_out_stream->codec;
|
||||||
// This will wipe out the codec defaults
|
// This will wipe out the codec defaults
|
||||||
ret = avcodec_copy_context(video_out_ctx, video_in_ctx);
|
ret = avcodec_copy_context(video_out_ctx, video_in_ctx);
|
||||||
//video_out_ctx->width = video_in_ctx->width;
|
|
||||||
//video_out_ctx->height = video_in_ctx->height;
|
|
||||||
//video_out_ctx->pix_fmt = video_in_ctx->pix_fmt;
|
|
||||||
//video_out_ctx->max_b_frames = video_in_ctx->max_b_frames;
|
|
||||||
//video_out_ctx->has_b_frames = video_in_ctx->has_b_frames;
|
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
Fatal("Unable to copy in video ctx to out video ctx %s",
|
Fatal("Unable to copy in video ctx to out video ctx %s",
|
||||||
av_make_error_string(ret).c_str());
|
av_make_error_string(ret).c_str());
|
||||||
|
@ -173,7 +161,6 @@ VideoStore::VideoStore(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ( !video_out_ctx->codec_tag ) {
|
if ( !video_out_ctx->codec_tag ) {
|
||||||
Debug(2, "No codec_tag");
|
Debug(2, "No codec_tag");
|
||||||
if (
|
if (
|
||||||
|
@ -207,6 +194,7 @@ VideoStore::VideoStore(
|
||||||
video_out_stream->r_frame_rate = video_in_stream->r_frame_rate;
|
video_out_stream->r_frame_rate = video_in_stream->r_frame_rate;
|
||||||
}
|
}
|
||||||
#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0)
|
#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0)
|
||||||
|
#if 0
|
||||||
if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) {
|
if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) {
|
||||||
//video_out_ctx->level = 32;I//
|
//video_out_ctx->level = 32;I//
|
||||||
video_out_ctx->bit_rate = 400*1024;
|
video_out_ctx->bit_rate = 400*1024;
|
||||||
|
@ -218,6 +206,7 @@ VideoStore::VideoStore(
|
||||||
Debug(2, "Not setting priv_data");
|
Debug(2, "Not setting priv_data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
|
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
Error("Could not initialize video_out_ctx parameters");
|
Error("Could not initialize video_out_ctx parameters");
|
||||||
|
@ -1016,7 +1005,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) {
|
||||||
|
|
||||||
zm_dump_frame(in_frame, "In frame from decode");
|
zm_dump_frame(in_frame, "In frame from decode");
|
||||||
|
|
||||||
if ( ! resample_audio() ) {
|
if ( !resample_audio() ) {
|
||||||
//av_frame_unref(in_frame);
|
//av_frame_unref(in_frame);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1146,11 +1135,22 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) {
|
||||||
}
|
}
|
||||||
} // end if encoding or copying
|
} // end if encoding or copying
|
||||||
|
|
||||||
|
|
||||||
opkt.pos = -1;
|
opkt.pos = -1;
|
||||||
opkt.stream_index = audio_out_stream->index;
|
opkt.stream_index = audio_out_stream->index;
|
||||||
opkt.flags = ipkt->flags;
|
opkt.flags = ipkt->flags;
|
||||||
|
|
||||||
if ( opkt.dts > opkt.pts ) {
|
if ( opkt.dts < audio_out_stream->cur_dts ) {
|
||||||
|
Warning("non increasing dts, fixing");
|
||||||
|
opkt.dts = audio_out_stream->cur_dts;
|
||||||
|
if ( opkt.dts > opkt.pts ) {
|
||||||
|
Debug(1,
|
||||||
|
"opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")."
|
||||||
|
"Decompression must happen before presentation.",
|
||||||
|
opkt.dts, opkt.pts);
|
||||||
|
opkt.pts = opkt.dts;
|
||||||
|
}
|
||||||
|
} else if ( opkt.dts > opkt.pts ) {
|
||||||
Debug(1,
|
Debug(1,
|
||||||
"opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")."
|
"opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")."
|
||||||
"Decompression must happen before presentation.",
|
"Decompression must happen before presentation.",
|
||||||
|
@ -1172,7 +1172,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) {
|
||||||
} // end int VideoStore::writeAudioFramePacket(AVPacket *ipkt)
|
} // end int VideoStore::writeAudioFramePacket(AVPacket *ipkt)
|
||||||
|
|
||||||
int VideoStore::resample_audio() {
|
int VideoStore::resample_audio() {
|
||||||
// Resample the in into the audioSampleBuffer until we process the whole
|
// Resample the in_frame into the audioSampleBuffer until we process the whole
|
||||||
// decoded data. Note: pts does not survive resampling or converting
|
// decoded data. Note: pts does not survive resampling or converting
|
||||||
#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE)
|
#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE)
|
||||||
#if defined(HAVE_LIBSWRESAMPLE)
|
#if defined(HAVE_LIBSWRESAMPLE)
|
||||||
|
@ -1192,8 +1192,8 @@ int VideoStore::resample_audio() {
|
||||||
}
|
}
|
||||||
/** Store the new samples in the FIFO buffer. */
|
/** Store the new samples in the FIFO buffer. */
|
||||||
ret = av_audio_fifo_write(fifo, (void **)out_frame->data, out_frame->nb_samples);
|
ret = av_audio_fifo_write(fifo, (void **)out_frame->data, out_frame->nb_samples);
|
||||||
if ( ret < in_frame->nb_samples ) {
|
if ( ret < out_frame->nb_samples ) {
|
||||||
Error("Could not write data to FIFO on %d written", ret);
|
Error("Could not write data to FIFO on %d written, expecting %d", ret, out_frame->nb_samples);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
error_reporting(0);
|
error_reporting(E_ERROR);
|
||||||
|
|
||||||
if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) {
|
if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) {
|
||||||
ajaxError('No event id(s) supplied');
|
ajaxError('No event id(s) supplied');
|
||||||
|
@ -136,7 +136,7 @@ if ( canEdit('Events') ) {
|
||||||
ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>false));
|
ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>false));
|
||||||
break;
|
break;
|
||||||
case 'delete' :
|
case 'delete' :
|
||||||
$Event = new Event($_REQUEST['id']);
|
$Event = new ZM\Event($_REQUEST['id']);
|
||||||
if ( ! $Event->Id() ) {
|
if ( ! $Event->Id() ) {
|
||||||
ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.'));
|
ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.'));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -288,9 +288,12 @@ class EventsController extends AppController {
|
||||||
$this->Event->recursive = -1;
|
$this->Event->recursive = -1;
|
||||||
$results = array();
|
$results = array();
|
||||||
$this->FilterComponent = $this->Components->load('Filter');
|
$this->FilterComponent = $this->Components->load('Filter');
|
||||||
$conditions = $this->FilterComponent->buildFilter($conditions);
|
if ( $this->request->params['named'] ) {
|
||||||
|
$conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
|
||||||
|
} else {
|
||||||
|
$conditions = array();
|
||||||
|
}
|
||||||
array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)"));
|
array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)"));
|
||||||
|
|
||||||
$query = $this->Event->find('all', array(
|
$query = $this->Event->find('all', array(
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'MonitorId',
|
'MonitorId',
|
||||||
|
|
|
@ -294,7 +294,7 @@ $SLANG = array(
|
||||||
'Display' => 'Prikaz',
|
'Display' => 'Prikaz',
|
||||||
'Displaying' => 'Prikazujem',
|
'Displaying' => 'Prikazujem',
|
||||||
'DonateAlready' => 'Ne, već sam napravio donaciju.',
|
'DonateAlready' => 'Ne, već sam napravio donaciju.',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'Donate' => 'Molimo donirajte',
|
'Donate' => 'Molimo donirajte',
|
||||||
'DonateRemindDay' => 'Ne još, podsjetime za 1 dan',
|
'DonateRemindDay' => 'Ne još, podsjetime za 1 dan',
|
||||||
'DonateRemindHour' => 'Ne još, podsjetime za 1 sat',
|
'DonateRemindHour' => 'Ne još, podsjetime za 1 sat',
|
||||||
|
|
|
@ -293,7 +293,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -289,7 +289,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => '请捐款',
|
'Donate' => '请捐款',
|
||||||
'DonateAlready' => '不,我已经捐赠过了',
|
'DonateAlready' => '不,我已经捐赠过了',
|
||||||
'DonateEnticement' => '迄今,您已经运行ZoneMinder有一阵子了,希望它能够有助于增强您家或者办公区域的安全。尽管ZoneMinder是,并将保持免费和开源,该项目依然在研发和支持中投入了资金和精力。如果您愿意支持今后的开发和新功能,那么请考虑为该项目捐款。捐款不是必须的,任何数量的捐赠,我们都很感谢。<br/><br/>如果您愿意捐款,请选择下列选项,或者访问 http://www.zoneminder.com/donate.html 捐赠主页。<br/><br/>感谢您使用ZoneMinder,并且不要忘记访问访问ZoneMinder.com的论坛以获得支持或建议,这可以提升您的ZoneMinder的体验。',
|
'DonateEnticement' => '迄今,您已经运行ZoneMinder有一阵子了,希望它能够有助于增强您家或者办公区域的安全。尽管ZoneMinder是,并将保持免费和开源,该项目依然在研发和支持中投入了资金和精力。如果您愿意支持今后的开发和新功能,那么请考虑为该项目捐款。捐款不是必须的,任何数量的捐赠,我们都很感谢。<br/><br/>如果您愿意捐款,请选择下列选项,或者访问 https://zoneminder.com/donate/ 捐赠主页。<br/><br/>感谢您使用ZoneMinder,并且不要忘记访问访问ZoneMinder.com的论坛以获得支持或建议,这可以提升您的ZoneMinder的体验。',
|
||||||
'DonateRemindDay' => '现在不,1天内再次提醒我',
|
'DonateRemindDay' => '现在不,1天内再次提醒我',
|
||||||
'DonateRemindHour' => '现在不,1小时内再次提醒我',
|
'DonateRemindHour' => '现在不,1小时内再次提醒我',
|
||||||
'DonateRemindMonth' => '现在不,1个月内再次提醒我',
|
'DonateRemindMonth' => '现在不,1个月内再次提醒我',
|
||||||
|
|
|
@ -289,7 +289,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Prosím podpořte',
|
'Donate' => 'Prosím podpořte',
|
||||||
'DonateAlready' => 'Ne, už jsem podpořil',
|
'DonateAlready' => 'Ne, už jsem podpořil',
|
||||||
'DonateEnticement' => 'Již nějakou dobu používáte software ZoneMinder k ochraně svého majetku a předpokládám, že jej shledáváte užitečným. Přestože je ZoneMinder, znovu připomínám, zdarma a volně šířený software, stojí jeho vývoj a podpora nějaké peníze. Pokud byste chtěl/a podpořit budoucí vývoj a nové možnosti softwaru, prosím zvažte darování finanční pomoci. Darování je, samozřejmě, dobrovolné, ale zato velmi ceněné můžete přispět jakou částkou chcete.<br><br>Pokud máte zájem podpořit náš tým, prosím, vyberte níže uvedenou možnost, nebo navštivte http://www.zoneminder.com/donate.html.<br><br>Děkuji Vám že jste si vybral/a software ZoneMinder a nezapomeňte navštívit fórum na ZoneMinder.com pro podporu a návrhy jak udělat ZoneMinder ještě lepším než je dnes.',
|
'DonateEnticement' => 'Již nějakou dobu používáte software ZoneMinder k ochraně svého majetku a předpokládám, že jej shledáváte užitečným. Přestože je ZoneMinder, znovu připomínám, zdarma a volně šířený software, stojí jeho vývoj a podpora nějaké peníze. Pokud byste chtěl/a podpořit budoucí vývoj a nové možnosti softwaru, prosím zvažte darování finanční pomoci. Darování je, samozřejmě, dobrovolné, ale zato velmi ceněné můžete přispět jakou částkou chcete.<br><br>Pokud máte zájem podpořit náš tým, prosím, vyberte níže uvedenou možnost, nebo navštivte https://zoneminder.com/donate/.<br><br>Děkuji Vám že jste si vybral/a software ZoneMinder a nezapomeňte navštívit fórum na ZoneMinder.com pro podporu a návrhy jak udělat ZoneMinder ještě lepším než je dnes.',
|
||||||
'DonateRemindDay' => 'Nyní ne, připomenout za 1 den',
|
'DonateRemindDay' => 'Nyní ne, připomenout za 1 den',
|
||||||
'DonateRemindHour' => 'Nyní ne, připomenout za hodinu',
|
'DonateRemindHour' => 'Nyní ne, připomenout za hodinu',
|
||||||
'DonateRemindMonth' => 'Nyní ne, připomenout za měsíc',
|
'DonateRemindMonth' => 'Nyní ne, připomenout za měsíc',
|
||||||
|
|
|
@ -291,7 +291,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Bitte spenden Sie.',
|
'Donate' => 'Bitte spenden Sie.',
|
||||||
'DonateAlready' => 'Nein, ich habe schon gespendet',
|
'DonateAlready' => 'Nein, ich habe schon gespendet',
|
||||||
'DonateEnticement' => 'Sie benutzen ZoneMinder nun schon eine Weile und es ist hoffentlich eine nützliche Applikation zur Verbesserung Ihrer Heim- oder Arbeitssicherheit. Obwohl ZoneMinder eine freie Open-Source-Software ist und bleiben wird, entstehen Kosten bei der Entwicklung und dem Support.<br><br>Falls Sie ZoneMinder für Weiterentwicklung in der Zukunft unterstützen möchten, denken Sie bitte über eine Spende für das Projekt unter der Webadresse http://www.zoneminder.com/donate.html oder über nachfolgend stehende Option nach. Spenden sind, wie der Name schon sagt, immer freiwillig. Dem Projekt helfen kleine genauso wie größere Spenden sehr weiter und ein herzlicher Dank ist jedem Spender sicher.<br><br>Vielen Dank dafür, dass sie ZoneMinder benutzen. Vergessen Sie nicht die Foren unter ZoneMinder.com, um Support zu erhalten und Ihre Erfahrung mit ZoneMinder zu verbessern!',
|
'DonateEnticement' => 'Sie benutzen ZoneMinder nun schon eine Weile und es ist hoffentlich eine nützliche Applikation zur Verbesserung Ihrer Heim- oder Arbeitssicherheit. Obwohl ZoneMinder eine freie Open-Source-Software ist und bleiben wird, entstehen Kosten bei der Entwicklung und dem Support.<br><br>Falls Sie ZoneMinder für Weiterentwicklung in der Zukunft unterstützen möchten, denken Sie bitte über eine Spende für das Projekt unter der Webadresse https://zoneminder.com/donate/ oder über nachfolgend stehende Option nach. Spenden sind, wie der Name schon sagt, immer freiwillig. Dem Projekt helfen kleine genauso wie größere Spenden sehr weiter und ein herzlicher Dank ist jedem Spender sicher.<br><br>Vielen Dank dafür, dass sie ZoneMinder benutzen. Vergessen Sie nicht die Foren unter ZoneMinder.com, um Support zu erhalten und Ihre Erfahrung mit ZoneMinder zu verbessern!',
|
||||||
'DonateRemindDay' => 'Noch nicht, erinnere mich in einem Tag noch mal.',
|
'DonateRemindDay' => 'Noch nicht, erinnere mich in einem Tag noch mal.',
|
||||||
'DonateRemindHour' => 'Noch nicht, erinnere mich in einer Stunde noch mal.',
|
'DonateRemindHour' => 'Noch nicht, erinnere mich in einer Stunde noch mal.',
|
||||||
'DonateRemindMonth' => 'Noch nicht, erinnere mich in einem Monat noch mal.',
|
'DonateRemindMonth' => 'Noch nicht, erinnere mich in einem Monat noch mal.',
|
||||||
|
|
|
@ -290,7 +290,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Venligst Donér',
|
'Donate' => 'Venligst Donér',
|
||||||
'DonateAlready' => 'Nej, jeg har allerede doneret',
|
'DonateAlready' => 'Nej, jeg har allerede doneret',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Ikke endnu, påmind igen on 1 dag',
|
'DonateRemindDay' => 'Ikke endnu, påmind igen on 1 dag',
|
||||||
'DonateRemindHour' => 'Ikke endnu, påmind igen on 1 time',
|
'DonateRemindHour' => 'Ikke endnu, påmind igen on 1 time',
|
||||||
'DonateRemindMonth' => 'Ikke endnu, påmind igen on 1 måned',
|
'DonateRemindMonth' => 'Ikke endnu, påmind igen on 1 måned',
|
||||||
|
|
|
@ -296,7 +296,7 @@ $SLANG = array(
|
||||||
'Display' => 'Display',
|
'Display' => 'Display',
|
||||||
'Displaying' => 'Displaying',
|
'Displaying' => 'Displaying',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
|
|
|
@ -240,7 +240,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -289,7 +289,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
||||||
'Donate' => 'Por favor, done',
|
'Donate' => 'Por favor, done',
|
||||||
'DonateAlready' => 'No, ya he donado',
|
'DonateAlready' => 'No, ya he donado',
|
||||||
'DonateEnticement' => 'Ha estado ejecutando ZoneMinder por un tiempo y con suerte le resultará un útil complemento para su seguridad en hogar y trabajo. Aunque ZoneMinder es, y será, libre y de código abierto, cuesta dinero desarrollarlo y mantenerlo. Si quiere ayudar a mantener un futuro desarrollo y nuevas funciones entonces considere hacer un donativo por favor. Donar es, por supuesto, opcional pero muy apreciado y puede donar tanto como desee sin importar la cantidad.<br/><br/>Si desea hacer una donación por favor seleccione la opción de debajo o vaya a http://www.zoneminder.com/donate.html en su navegador.<br/><br/>Muchas gracias por usar ZoneMinder y no se olvide de vistar los foros en ZoneMinder.com para obtener soporte o hacer sugerencias sobre cómo mejorar su experiencia con ZoneMinder aún más.',
|
'DonateEnticement' => 'Ha estado ejecutando ZoneMinder por un tiempo y con suerte le resultará un útil complemento para su seguridad en hogar y trabajo. Aunque ZoneMinder es, y será, libre y de código abierto, cuesta dinero desarrollarlo y mantenerlo. Si quiere ayudar a mantener un futuro desarrollo y nuevas funciones entonces considere hacer un donativo por favor. Donar es, por supuesto, opcional pero muy apreciado y puede donar tanto como desee sin importar la cantidad.<br/><br/>Si desea hacer una donación por favor seleccione la opción de debajo o vaya a https://zoneminder.com/donate/ en su navegador.<br/><br/>Muchas gracias por usar ZoneMinder y no se olvide de vistar los foros en ZoneMinder.com para obtener soporte o hacer sugerencias sobre cómo mejorar su experiencia con ZoneMinder aún más.',
|
||||||
'DonateRemindDay' => 'Aún no, recordarme de nuevo en 1 día',
|
'DonateRemindDay' => 'Aún no, recordarme de nuevo en 1 día',
|
||||||
'DonateRemindHour' => 'Aún no, recordarme de nuevo en 1 hora',
|
'DonateRemindHour' => 'Aún no, recordarme de nuevo en 1 hora',
|
||||||
'DonateRemindMonth' => 'Aún no, recordarme de nuevo en 1 mes',
|
'DonateRemindMonth' => 'Aún no, recordarme de nuevo en 1 mes',
|
||||||
|
|
|
@ -296,7 +296,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
||||||
'Donate' => 'Palun Anneta',
|
'Donate' => 'Palun Anneta',
|
||||||
'DonateAlready' => 'EI, Ma olen juba annetanud',
|
'DonateAlready' => 'EI, Ma olen juba annetanud',
|
||||||
'DonateEnticement' => 'Sa oled juba kasutanud ZoneMinderit juba mõnda aega. Nüüd kus sa oled leidnud, et see on kasulik lisa sinu kodule või sinu töökohale. Kuigi ZoneMinder on, jääb alatiseks, vabaks ja avatud lähtekoodiks, siiski selle arendamiseks kulub aega ja raha. Kui sa soovid meid aidata, siis toeta meid tuleviku arendusteks ja uute lisade loomiseks. Palun mõelge annetuse peale. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'Sa oled juba kasutanud ZoneMinderit juba mõnda aega. Nüüd kus sa oled leidnud, et see on kasulik lisa sinu kodule või sinu töökohale. Kuigi ZoneMinder on, jääb alatiseks, vabaks ja avatud lähtekoodiks, siiski selle arendamiseks kulub aega ja raha. Kui sa soovid meid aidata, siis toeta meid tuleviku arendusteks ja uute lisade loomiseks. Palun mõelge annetuse peale. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Ei veel, tuleta meelde ühe päeva pärast',
|
'DonateRemindDay' => 'Ei veel, tuleta meelde ühe päeva pärast',
|
||||||
'DonateRemindHour' => 'Ei veel, tuleta meelde ühe tunni pärast',
|
'DonateRemindHour' => 'Ei veel, tuleta meelde ühe tunni pärast',
|
||||||
'DonateRemindMonth' => 'Ei veel, tuleta meelde ühe kuu pärast',
|
'DonateRemindMonth' => 'Ei veel, tuleta meelde ühe kuu pärast',
|
||||||
|
|
|
@ -295,7 +295,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Réaliser détection native',
|
'DoNativeMotionDetection'=> 'Réaliser détection native',
|
||||||
'Donate' => 'Veuillez faire un don',
|
'Donate' => 'Veuillez faire un don',
|
||||||
'DonateAlready' => 'Non, j\'ai déjà donné',
|
'DonateAlready' => 'Non, j\'ai déjà donné',
|
||||||
'DonateEnticement' => 'Vous utilisez ZoneMinder depuis quelque temps et nous espérons que vous trouvez cette solution utile. Bien que ZoneMinder est, et restera, une solution libre et ouverte (open source), son développement et son maintien nécessitent des moyens financiers. Si vous voulez aider au développement et à l\'ajout de fonctionnalités, veuillez considérer la possibilité d\'effectuer un don. Les dons sont bien sûr optionnels mais grandement appréciés et vous pouvez donner le montant que vous désirez.<br><br>Si vous voulez effectuer un don, veuillez sélectionner l\'option ci-dessous ou veuillez vous rendre sur http://www.zoneminder.com/donate.html à l\'aide de votre navigateur internet.<br><br>Merci d\'utiliser ZoneMinder et n\'oubliez pas de visiter les forums sur ZoneMinder.com pour le support ou des suggestions pour rendre votre expérience de ZoneMinder encore meilleure.',
|
'DonateEnticement' => 'Vous utilisez ZoneMinder depuis quelque temps et nous espérons que vous trouvez cette solution utile. Bien que ZoneMinder est, et restera, une solution libre et ouverte (open source), son développement et son maintien nécessitent des moyens financiers. Si vous voulez aider au développement et à l\'ajout de fonctionnalités, veuillez considérer la possibilité d\'effectuer un don. Les dons sont bien sûr optionnels mais grandement appréciés et vous pouvez donner le montant que vous désirez.<br><br>Si vous voulez effectuer un don, veuillez sélectionner l\'option ci-dessous ou veuillez vous rendre sur https://zoneminder.com/donate/ à l\'aide de votre navigateur internet.<br><br>Merci d\'utiliser ZoneMinder et n\'oubliez pas de visiter les forums sur ZoneMinder.com pour le support ou des suggestions pour rendre votre expérience de ZoneMinder encore meilleure.',
|
||||||
'DonateRemindDay' => 'Pas encore, me rappeler dans 1 jour',
|
'DonateRemindDay' => 'Pas encore, me rappeler dans 1 jour',
|
||||||
'DonateRemindHour' => 'Pas encore, me rappeler dans 1 heure',
|
'DonateRemindHour' => 'Pas encore, me rappeler dans 1 heure',
|
||||||
'DonateRemindMonth' => 'Pas encore, me rappeler dans 1 mois',
|
'DonateRemindMonth' => 'Pas encore, me rappeler dans 1 mois',
|
||||||
|
|
|
@ -289,7 +289,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'úøåí áá÷ùä',
|
'Donate' => 'úøåí áá÷ùä',
|
||||||
'DonateAlready' => 'ìà, úøîúé ëáø',
|
'DonateAlready' => 'ìà, úøîúé ëáø',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'òãééï ìà, äæëø ìà áòåã éåí àçã',
|
'DonateRemindDay' => 'òãééï ìà, äæëø ìà áòåã éåí àçã',
|
||||||
'DonateRemindHour' => 'òãééï ìà, äæëø ìé áòåã ùòä àçú',
|
'DonateRemindHour' => 'òãééï ìà, äæëø ìé áòåã ùòä àçú',
|
||||||
'DonateRemindMonth' => 'òãééï ìà, äæëø ìé áòåã çåãù àçã',
|
'DonateRemindMonth' => 'òãééï ìà, äæëø ìé áòåã çåãù àçã',
|
||||||
|
|
|
@ -332,7 +332,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
||||||
'Donate' => 'Kérem támogasson',
|
'Donate' => 'Kérem támogasson',
|
||||||
'DonateAlready' => 'Nem, én már támogattam',
|
'DonateAlready' => 'Nem, én már támogattam',
|
||||||
'DonateEnticement' => 'Ön már jó ideje használja a ZoneMindert, és reméljük hasznos eszköznek tartja háza vagy munkahelye biztonságában. Bár a ZoneMinder egy szabad, nyílt forráskódú termék és az is marad, a fejlesztése pénzbe kerül. Ha van lehetősége támogatni a jövőbeni fejlesztéseket és az új funkciókat kérem, tegye meg. A támogatás teljesen önkéntes, de nagyon megbecsült és mértéke is tetszőleges.<br><br>Ha támogatni szertne, kérem, válasszon az alábbi lehetőségekből vagy látogassa meg a http://www.zoneminder.com/donate.html oldalt.<br><br>Köszönjük, hogy használja a ZoneMinder-t és ne felejtse el meglátogatni a fórumokat a ZoneMinder.com oldalon támogatásért és ötletekért, hogy a jövőben is még jobban ki tudja használni a ZoneMinder lehetőségeit.',
|
'DonateEnticement' => 'Ön már jó ideje használja a ZoneMindert, és reméljük hasznos eszköznek tartja háza vagy munkahelye biztonságában. Bár a ZoneMinder egy szabad, nyílt forráskódú termék és az is marad, a fejlesztése pénzbe kerül. Ha van lehetősége támogatni a jövőbeni fejlesztéseket és az új funkciókat kérem, tegye meg. A támogatás teljesen önkéntes, de nagyon megbecsült és mértéke is tetszőleges.<br><br>Ha támogatni szertne, kérem, válasszon az alábbi lehetőségekből vagy látogassa meg a https://zoneminder.com/donate/ oldalt.<br><br>Köszönjük, hogy használja a ZoneMinder-t és ne felejtse el meglátogatni a fórumokat a ZoneMinder.com oldalon támogatásért és ötletekért, hogy a jövőben is még jobban ki tudja használni a ZoneMinder lehetőségeit.',
|
||||||
'DonateRemindDay' => 'Nem most, figyelmeztessen egy nap múlva',
|
'DonateRemindDay' => 'Nem most, figyelmeztessen egy nap múlva',
|
||||||
'DonateRemindHour' => 'Nem most, figyelmeztessen egy óra múlva',
|
'DonateRemindHour' => 'Nem most, figyelmeztessen egy óra múlva',
|
||||||
'DonateRemindMonth' => 'Nem most, figyelmeztessen egy hónap múlva',
|
'DonateRemindMonth' => 'Nem most, figyelmeztessen egy hónap múlva',
|
||||||
|
|
|
@ -294,7 +294,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Donate,per favore',
|
'Donate' => 'Donate,per favore',
|
||||||
'DonateAlready' => 'No, ho gia donato... ',
|
'DonateAlready' => 'No, ho gia donato... ',
|
||||||
'DonateEnticement' => 'Stai usando ZoneMinder da un po\' di tempo e spero che tu lo stia trovando utile per la sicurezza di casa tua o del tuo posto di lavoro..Anche se ZoneMinder e\' distribuito liberamente come software libero,costa soldi sia svilupparlo che supportarlo. Se preferisci che questo software continui ad avere supporto e sviluppo in futuro allora considera l\idea di fare una piccola donazione. Donare e\' ovviamente opzionale, ma apprezzato e puoi donare quanto vuoi,quel poco o tanto che tu desideri.<br><br>Se hai voglia per cortesia seleziona l\'opzione sotto o punta il tuo browser a http://www.zoneminder.com/donate.html .<br><br>Grazie per usare ZoneMinder e non dimenticare di visitare il forum in ZoneMinder.com se cerchi supporto o hai suggerimenti riguardo a come rendere migliore Zoneminder.',
|
'DonateEnticement' => 'Stai usando ZoneMinder da un po\' di tempo e spero che tu lo stia trovando utile per la sicurezza di casa tua o del tuo posto di lavoro..Anche se ZoneMinder e\' distribuito liberamente come software libero,costa soldi sia svilupparlo che supportarlo. Se preferisci che questo software continui ad avere supporto e sviluppo in futuro allora considera l\idea di fare una piccola donazione. Donare e\' ovviamente opzionale, ma apprezzato e puoi donare quanto vuoi,quel poco o tanto che tu desideri.<br><br>Se hai voglia per cortesia seleziona l\'opzione sotto o punta il tuo browser a https://zoneminder.com/donate/ .<br><br>Grazie per usare ZoneMinder e non dimenticare di visitare il forum in ZoneMinder.com se cerchi supporto o hai suggerimenti riguardo a come rendere migliore Zoneminder.',
|
||||||
'DonateRemindDay' => 'Non ancora, ricordamelo ancora tra 1 giorno',
|
'DonateRemindDay' => 'Non ancora, ricordamelo ancora tra 1 giorno',
|
||||||
'DonateRemindHour' => 'Non ancora, ricordamelo ancora tra 1 ora',
|
'DonateRemindHour' => 'Non ancora, ricordamelo ancora tra 1 ora',
|
||||||
'DonateRemindMonth' => 'Non ancora, ricordamelo ancora tra 1 mese',
|
'DonateRemindMonth' => 'Non ancora, ricordamelo ancora tra 1 mese',
|
||||||
|
|
|
@ -290,7 +290,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -290,7 +290,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection', // Added - 2015-04-18
|
||||||
'Donate' => 'Geef a.u.b. een donatie',
|
'Donate' => 'Geef a.u.b. een donatie',
|
||||||
'DonateAlready' => 'Nee, ik heb al gedoneerd',
|
'DonateAlready' => 'Nee, ik heb al gedoneerd',
|
||||||
'DonateEnticement' => 'U gebruikt ZoneMinder nu voor een geruime tijd, hopelijk vindt u het een nuttige toevoeging voor uw huis- of werkplekbeveiliging. Natuurlijk is en blijft ZoneMinder gratis en open source software, maar het kost geld om te ontwikkelen, ondersteunen, en te onderhouden. Wij vragen u dan ook om er over na te denken een donatie te doen om zo de ontwikkeling van ZoneMinder te ondersteunen. Natuurlijk bent u hier vrij in, en elke donatie hoe klein dan ook wordt erg gewaardeerd. <br><br> Als u wilt doneren geef dat hieronder dan aan of ga naar http://www.zoneminder.com/donate.html in uw browser.<br><br>Bedankt voor het gebruiken van ZoneMinder en vergeet niet om ons forum op ZoneMinder.com te bezoeken voor ondersteuning of suggesties waarmee uw ZoneMinder beleving nog beter wordt.',
|
'DonateEnticement' => 'U gebruikt ZoneMinder nu voor een geruime tijd, hopelijk vindt u het een nuttige toevoeging voor uw huis- of werkplekbeveiliging. Natuurlijk is en blijft ZoneMinder gratis en open source software, maar het kost geld om te ontwikkelen, ondersteunen, en te onderhouden. Wij vragen u dan ook om er over na te denken een donatie te doen om zo de ontwikkeling van ZoneMinder te ondersteunen. Natuurlijk bent u hier vrij in, en elke donatie hoe klein dan ook wordt erg gewaardeerd. <br><br> Als u wilt doneren geef dat hieronder dan aan of ga naar https://zoneminder.com/donate/ in uw browser.<br><br>Bedankt voor het gebruiken van ZoneMinder en vergeet niet om ons forum op ZoneMinder.com te bezoeken voor ondersteuning of suggesties waarmee uw ZoneMinder beleving nog beter wordt.',
|
||||||
'DonateRemindDay' => 'Nu niet, herinner mij over 1 dag hieraan',
|
'DonateRemindDay' => 'Nu niet, herinner mij over 1 dag hieraan',
|
||||||
'DonateRemindHour' => 'Nu niet, herinner mij over een uur hieraan',
|
'DonateRemindHour' => 'Nu niet, herinner mij over een uur hieraan',
|
||||||
'DonateRemindMonth' => 'Nu niet, herinner mij over een maand hieraan',
|
'DonateRemindMonth' => 'Nu niet, herinner mij over een maand hieraan',
|
||||||
|
|
|
@ -304,7 +304,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -229,7 +229,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -260,7 +260,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Please Donate',
|
'Donate' => 'Please Donate',
|
||||||
'DonateAlready' => 'No, I\'ve already donated',
|
'DonateAlready' => 'No, I\'ve already donated',
|
||||||
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br><br>If you would like to donate please select the option below or go to https://zoneminder.com/donate/ in your browser.<br><br>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
|
||||||
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
'DonateRemindDay' => 'Not yet, remind again in 1 day',
|
||||||
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
'DonateRemindHour' => 'Not yet, remind again in 1 hour',
|
||||||
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
'DonateRemindMonth' => 'Not yet, remind again in 1 month',
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
// ZoneMinder Russian Translation by Borodin A.S.
|
// ZoneMinder Russian Translation by Borodin A.S.
|
||||||
// ZoneMinder Russian Translation updated by IDDQDesnik, 2017
|
// ZoneMinder Russian Translation updated by IDDQDesnik, 2017
|
||||||
|
// ZoneMinder Russian Translation updated by santos995, 2019
|
||||||
|
|
||||||
// Notes for Translators
|
// Notes for Translators
|
||||||
// 0. Get some credit, put your name in the line above (optional)
|
// 0. Get some credit, put your name in the line above (optional)
|
||||||
|
@ -76,11 +77,11 @@ $SLANG = array(
|
||||||
'32BitColour' => '32 битный цвет', // Added - 2011-06-15
|
'32BitColour' => '32 битный цвет', // Added - 2011-06-15
|
||||||
'8BitGrey' => '256 оттенков серого',
|
'8BitGrey' => '256 оттенков серого',
|
||||||
'Action' => 'Действие',
|
'Action' => 'Действие',
|
||||||
'Actual' => 'Actual', // Added - 2018-08-30
|
'Actual' => 'Актуальный', // Edited - 2019-03-25
|
||||||
'AddNewControl' => 'Добавить новый',
|
'AddNewControl' => 'Добавить новый',
|
||||||
'AddNewMonitor' => 'Добавить монитор',
|
'AddNewMonitor' => 'Добавить монитор',
|
||||||
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
|
'AddNewServer' => 'Добавить новый сервер', // Edited - 2019-03-25
|
||||||
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
|
'AddNewStorage' => 'Добавить новое хранилище', // Edited - 2019-03-25
|
||||||
'AddNewUser' => 'Добавить пользователя',
|
'AddNewUser' => 'Добавить пользователя',
|
||||||
'AddNewZone' => 'Добавить зону',
|
'AddNewZone' => 'Добавить зону',
|
||||||
'Alarm' => 'Тревога',
|
'Alarm' => 'Тревога',
|
||||||
|
@ -110,24 +111,24 @@ $SLANG = array(
|
||||||
'AttrCause' => 'Причина',
|
'AttrCause' => 'Причина',
|
||||||
'AttrDiskBlocks' => 'Диск, блоки',
|
'AttrDiskBlocks' => 'Диск, блоки',
|
||||||
'AttrDiskPercent' => 'Диск, проценты',
|
'AttrDiskPercent' => 'Диск, проценты',
|
||||||
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
|
'AttrDiskSpace' => 'Дисковое пространство', // Edited - 2019-03-24
|
||||||
'AttrDuration' => 'Длительность',
|
'AttrDuration' => 'Длительность',
|
||||||
'AttrEndDate' => 'End Date', // Added - 2018-08-30
|
'AttrEndDate' => 'Дата окончания', // Edited - 2019-03-24
|
||||||
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
|
'AttrEndDateTime' => 'Дата/время окончания', // Edited - 2019-03-24
|
||||||
'AttrEndTime' => 'End Time', // Added - 2018-08-30
|
'AttrEndTime' => 'Время окончания', // Edited - 2019-03-24
|
||||||
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
|
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
|
||||||
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
|
'AttrFilterServer' => 'Фильтр для серверов запущен', // Edited - 2019-03-24
|
||||||
'AttrFrames' => 'Кол-во кадров',
|
'AttrFrames' => 'Кол-во кадров',
|
||||||
'AttrId' => 'ИД',
|
'AttrId' => 'ИД',
|
||||||
'AttrMaxScore' => 'Макс. оценка',
|
'AttrMaxScore' => 'Макс. оценка',
|
||||||
'AttrMonitorId' => 'ИД Монитора',
|
'AttrMonitorId' => 'ИД Монитора',
|
||||||
'AttrMonitorName' => 'Название Монитора',
|
'AttrMonitorName' => 'Название Монитора',
|
||||||
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
|
'AttrMonitorServer' => 'Монитор серверов запущен', // Edited - 2019-03-24
|
||||||
'AttrName' => 'Имя',
|
'AttrName' => 'Имя',
|
||||||
'AttrNotes' => 'Примечание',
|
'AttrNotes' => 'Примечание',
|
||||||
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
|
'AttrStartDate' => 'Дата начала', // Edited - 2019-03-24
|
||||||
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
|
'AttrStartDateTime' => 'Дата/Время начала', // Edited - 2019-03-24
|
||||||
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
|
'AttrStartTime' => 'Время начала', // Edited - 2019-03-24
|
||||||
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
|
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
|
||||||
'AttrStateId' => 'Run State', // Added - 2018-08-30
|
'AttrStateId' => 'Run State', // Added - 2018-08-30
|
||||||
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
|
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
|
||||||
|
@ -170,7 +171,7 @@ $SLANG = array(
|
||||||
'BadStreamReplayBuffer'=> 'Буфер потока повторного воспроизведения должен быть целочисленным и большим либо равным нулю',
|
'BadStreamReplayBuffer'=> 'Буфер потока повторного воспроизведения должен быть целочисленным и большим либо равным нулю',
|
||||||
'BadWarmupCount' => 'Кол-во кадров разогрева должно быть целочисленным и большим либо равным нулю',
|
'BadWarmupCount' => 'Кол-во кадров разогрева должно быть целочисленным и большим либо равным нулю',
|
||||||
'BadWebColour' => 'Цвет отметки должен быть правильным Web-цветом',
|
'BadWebColour' => 'Цвет отметки должен быть правильным Web-цветом',
|
||||||
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
|
'BadWebSitePath' => 'Пожалуйста, введите полной название сайта url, включая http:// или https:// префикс.', // Edited - 2019-03-24
|
||||||
'BadWidth' => 'Неправильная ширина',
|
'BadWidth' => 'Неправильная ширина',
|
||||||
'Bandwidth' => 'канал',
|
'Bandwidth' => 'канал',
|
||||||
'BandwidthHead' => 'канал', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing;
|
'BandwidthHead' => 'канал', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing;
|
||||||
|
@ -178,9 +179,9 @@ $SLANG = array(
|
||||||
'BlobSizes' => 'Размер объектов',
|
'BlobSizes' => 'Размер объектов',
|
||||||
'Blobs' => 'Кол-во объектов',
|
'Blobs' => 'Кол-во объектов',
|
||||||
'Brightness' => 'Яркость',
|
'Brightness' => 'Яркость',
|
||||||
'Buffer' => 'Buffer', // Added - 2015-04-18
|
'Buffer' => 'Буфер', // Edited - 2019-03-24
|
||||||
'Buffers' => 'Буферы',
|
'Buffers' => 'Буферы',
|
||||||
'CSSDescription' => 'Change the default css for this computer', // Added - 2015-04-18
|
'CSSDescription' => 'Изменить стандартный CSS для данного компьютера', // Edited - 2019-03-24
|
||||||
'CanAutoFocus' => 'Автофокус',
|
'CanAutoFocus' => 'Автофокус',
|
||||||
'CanAutoGain' => 'Автоусиление',
|
'CanAutoGain' => 'Автоусиление',
|
||||||
'CanAutoIris' => 'Автодиафрагма',
|
'CanAutoIris' => 'Автодиафрагма',
|
||||||
|
@ -206,7 +207,7 @@ $SLANG = array(
|
||||||
'CanMoveRel' => 'Относительное перемещение',
|
'CanMoveRel' => 'Относительное перемещение',
|
||||||
'CanPan' => 'Панорама' ,
|
'CanPan' => 'Панорама' ,
|
||||||
'CanReset' => 'Сброс',
|
'CanReset' => 'Сброс',
|
||||||
'CanReboot' => 'Can Reboot',
|
'CanReboot' => 'Перезагрузка', // Added - 2019-03-24
|
||||||
'CanSetPresets' => 'Создание предустановок',
|
'CanSetPresets' => 'Создание предустановок',
|
||||||
'CanSleep' => 'Сон',
|
'CanSleep' => 'Сон',
|
||||||
'CanTilt' => 'Наклон',
|
'CanTilt' => 'Наклон',
|
||||||
|
@ -225,17 +226,17 @@ $SLANG = array(
|
||||||
'CaptureHeight' => 'Размер по Y',
|
'CaptureHeight' => 'Размер по Y',
|
||||||
'CaptureMethod' => 'Метод захвата', // Added - 2009-02-08
|
'CaptureMethod' => 'Метод захвата', // Added - 2009-02-08
|
||||||
'CapturePalette' => 'Режим захвата',
|
'CapturePalette' => 'Режим захвата',
|
||||||
'CaptureResolution' => 'Capture Resolution', // Added - 2015-04-18
|
'CaptureResolution' => 'Разрешение', // Edited - 2019-03-24
|
||||||
'CaptureWidth' => 'Размер по X',
|
'CaptureWidth' => 'Размер по X',
|
||||||
'Cause' => 'Причина',
|
'Cause' => 'Причина',
|
||||||
'CheckMethod' => 'Метод проверки тревоги',
|
'CheckMethod' => 'Метод проверки тревоги',
|
||||||
'ChooseDetectedCamera' => 'Выберите камеру', // Added - 2009-03-31
|
'ChooseDetectedCamera' => 'Выберите камеру', // Added - 2009-03-31
|
||||||
'ChooseFilter' => 'Выбрать фильтр',
|
'ChooseFilter' => 'Выбрать фильтр',
|
||||||
'ChooseLogFormat' => 'Choose a log format', // Added - 2011-06-17
|
'ChooseLogFormat' => 'Выбрать формат лога', // Edited - 2019-03-25
|
||||||
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
|
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
|
||||||
'ChoosePreset' => 'Выберите предустановку',
|
'ChoosePreset' => 'Выберите предустановку',
|
||||||
'Clear' => 'Очистить', // Added - 2011-06-16
|
'Clear' => 'Очистить', // Added - 2011-06-16
|
||||||
'CloneMonitor' => 'Clone', // Added - 2018-08-30
|
'CloneMonitor' => 'Клонировать', // Edited - 2019-03-25
|
||||||
'Close' => 'Закрыть',
|
'Close' => 'Закрыть',
|
||||||
'Colour' => 'Цвет',
|
'Colour' => 'Цвет',
|
||||||
'Command' => 'Command',
|
'Command' => 'Command',
|
||||||
|
@ -249,7 +250,7 @@ $SLANG = array(
|
||||||
'ConjOr' => 'или',
|
'ConjOr' => 'или',
|
||||||
'Console' => 'Сервер',
|
'Console' => 'Сервер',
|
||||||
'ContactAdmin' => 'Пожалуйста обратитесь к вашему администратору.',
|
'ContactAdmin' => 'Пожалуйста обратитесь к вашему администратору.',
|
||||||
'Continue' => 'Continue',
|
'Continue' => 'Продолжить', // Added - 2019-03-25
|
||||||
'Contrast' => 'Контраст',
|
'Contrast' => 'Контраст',
|
||||||
'Control' => 'Управление',
|
'Control' => 'Управление',
|
||||||
'ControlAddress' => 'Адрес устройства',
|
'ControlAddress' => 'Адрес устройства',
|
||||||
|
@ -263,12 +264,12 @@ $SLANG = array(
|
||||||
'CycleWatch' => 'Циклический просмотр',
|
'CycleWatch' => 'Циклический просмотр',
|
||||||
'DateTime' => 'Дата/Время', // Added - 2011-06-16
|
'DateTime' => 'Дата/Время', // Added - 2011-06-16
|
||||||
'Day' => 'День',
|
'Day' => 'День',
|
||||||
'Debug' => 'Debug',
|
'Debug' => 'Отладка', // Added - 2019-03-25
|
||||||
'DefaultRate' => 'Скорость по умолчанию',
|
'DefaultRate' => 'Скорость по умолчанию',
|
||||||
'DefaultScale' => 'Масштаб по умолчанию',
|
'DefaultScale' => 'Масштаб по умолчанию',
|
||||||
'DefaultView' => 'Вид по умолчанию',
|
'DefaultView' => 'Вид по умолчанию',
|
||||||
'Deinterlacing' => 'Устранение чересстрочности', // Added - 2015-04-18
|
'Deinterlacing' => 'Устранение чересстрочности', // Added - 2015-04-18
|
||||||
'Delay' => 'Delay', // Added - 2015-04-18
|
'Delay' => 'Задержка', // Edited - 2019-03-25
|
||||||
'Delete' => 'Удалить',
|
'Delete' => 'Удалить',
|
||||||
'DeleteAndNext' => 'Удалить & след.',
|
'DeleteAndNext' => 'Удалить & след.',
|
||||||
'DeleteAndPrev' => 'Удалить & пред.',
|
'DeleteAndPrev' => 'Удалить & пред.',
|
||||||
|
@ -276,18 +277,18 @@ $SLANG = array(
|
||||||
'Description' => 'Описание',
|
'Description' => 'Описание',
|
||||||
'DetectedCameras' => 'Найденные камеры', // Added - 2009-03-31
|
'DetectedCameras' => 'Найденные камеры', // Added - 2009-03-31
|
||||||
'DetectedProfiles' => 'Найденные профили', // Added - 2015-04-18
|
'DetectedProfiles' => 'Найденные профили', // Added - 2015-04-18
|
||||||
'Device' => 'Device', // Added - 2009-02-08
|
'Device' => 'Устройство', // Edited - 2019-03-25
|
||||||
'DeviceChannel' => 'Канал',
|
'DeviceChannel' => 'Канал',
|
||||||
'DeviceFormat' => 'Формат',
|
'DeviceFormat' => 'Формат',
|
||||||
'DeviceNumber' => 'Номер устройства',
|
'DeviceNumber' => 'Номер устройства',
|
||||||
'DevicePath' => 'Путь к устройству',
|
'DevicePath' => 'Путь к устройству',
|
||||||
'Devices' => 'Devices',
|
'Devices' => 'Устройства', // Edited - 2019-03-25
|
||||||
'Dimensions' => 'Размеры',
|
'Dimensions' => 'Размеры',
|
||||||
'DisableAlarms' => 'Запретить тревогу',
|
'DisableAlarms' => 'Запретить тревогу',
|
||||||
'Disk' => 'Диск',
|
'Disk' => 'Диск',
|
||||||
'Display' => 'Display', // Added - 2011-01-30
|
'Display' => 'Display', // Added - 2011-01-30
|
||||||
'Displaying' => 'Отображено', // Added - 2011-06-16
|
'Displaying' => 'Отображено', // Added - 2011-06-16
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Использовать встроенное обнаружение движения', // Edited - 2019-03-25
|
||||||
'Donate' => 'Поддержите проект',
|
'Donate' => 'Поддержите проект',
|
||||||
'DonateAlready' => 'Нет, я уже сделал пожертвование',
|
'DonateAlready' => 'Нет, я уже сделал пожертвование',
|
||||||
'DonateEnticement' => 'Вы какое-то время используете ZoneMinder и, надеемся, находите его полезным дополнением к вашей домашней или рабочей безопасности. Хотя ZoneMinder есть и будет оставаться свободным и бесплатным, он требует денег на разработку и поддержку. Если Вы хотите поддержать его будущее развитие и новые функции, пожалуйста сделайте пожертвование. Это, конечно, необязательно, но очень высоко ценится. Вы можете пожертвовать любую сумму.<br><br>Если Вы хотите сделать пожертвование, то выберите соответствующий вариант ниже или перейдите по адресу https://www.bountysource.com/teams/zoneminder в вашем браузере.<br><br>Спасибо за использование ZoneMinder, и не забывайте посетить форум на ZoneMinder.com для поддержки и пожеланий как сделать ZoneMinder еще лучше.',
|
'DonateEnticement' => 'Вы какое-то время используете ZoneMinder и, надеемся, находите его полезным дополнением к вашей домашней или рабочей безопасности. Хотя ZoneMinder есть и будет оставаться свободным и бесплатным, он требует денег на разработку и поддержку. Если Вы хотите поддержать его будущее развитие и новые функции, пожалуйста сделайте пожертвование. Это, конечно, необязательно, но очень высоко ценится. Вы можете пожертвовать любую сумму.<br><br>Если Вы хотите сделать пожертвование, то выберите соответствующий вариант ниже или перейдите по адресу https://www.bountysource.com/teams/zoneminder в вашем браузере.<br><br>Спасибо за использование ZoneMinder, и не забывайте посетить форум на ZoneMinder.com для поддержки и пожеланий как сделать ZoneMinder еще лучше.',
|
||||||
|
@ -298,11 +299,11 @@ $SLANG = array(
|
||||||
'DonateRemindWeek' => 'Нет, не сейчас, напомнить через неделю',
|
'DonateRemindWeek' => 'Нет, не сейчас, напомнить через неделю',
|
||||||
'DonateYes' => 'Да, я хотел бы сделать пожертвование',
|
'DonateYes' => 'Да, я хотел бы сделать пожертвование',
|
||||||
'Download' => 'Скачать',
|
'Download' => 'Скачать',
|
||||||
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
|
'DownloadVideo' => 'Скачать видео', // Added - 2019-03-24
|
||||||
'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31
|
'DuplicateMonitorName' => 'Скопировать имя монитора', // Added - 2019-03-25
|
||||||
'Duration' => 'Длительность',
|
'Duration' => 'Длительность',
|
||||||
'Edit' => 'Редактирование',
|
'Edit' => 'Редактирование',
|
||||||
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
|
'EditLayout' => 'Редактирование шаблона', // Added - 2019-03-25
|
||||||
'Email' => 'Email',
|
'Email' => 'Email',
|
||||||
'EnableAlarms' => 'Разрешить тревогу',
|
'EnableAlarms' => 'Разрешить тревогу',
|
||||||
'Enabled' => 'Включен',
|
'Enabled' => 'Включен',
|
||||||
|
@ -313,13 +314,13 @@ $SLANG = array(
|
||||||
'Etc' => 'и т.д.',
|
'Etc' => 'и т.д.',
|
||||||
'Event' => 'Событие',
|
'Event' => 'Событие',
|
||||||
'EventFilter' => 'Фильтр событий',
|
'EventFilter' => 'Фильтр событий',
|
||||||
'EventId' => 'Event Id',
|
'EventId' => 'Id события', // Added - 2019-03-25
|
||||||
'EventName' => 'Event Name',
|
'EventName' => 'Имя события', // Added - 2019-03-25
|
||||||
'EventPrefix' => 'Префикс события',
|
'EventPrefix' => 'Префикс события',
|
||||||
'Events' => 'События',
|
'Events' => 'События',
|
||||||
'Exclude' => 'Исключить',
|
'Exclude' => 'Исключить',
|
||||||
'Execute' => 'Выполнить',
|
'Execute' => 'Выполнить',
|
||||||
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
|
'Exif' => 'Включить EXIF информацию в изображение', // Added - 2019-03-24
|
||||||
'Export' => 'Экспорт',
|
'Export' => 'Экспорт',
|
||||||
'ExportDetails' => 'Экспортировать описание события',
|
'ExportDetails' => 'Экспортировать описание события',
|
||||||
'ExportFailed' => 'Ошибка экспорта',
|
'ExportFailed' => 'Ошибка экспорта',
|
||||||
|
@ -351,14 +352,14 @@ $SLANG = array(
|
||||||
'FilterMessageEvents' => 'Message details of all matches',
|
'FilterMessageEvents' => 'Message details of all matches',
|
||||||
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
|
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
|
||||||
'FilterPx' => 'Пкс фильтра',
|
'FilterPx' => 'Пкс фильтра',
|
||||||
'FilterUnset' => 'You must specify a filter width and height',
|
'FilterUnset' => 'Вы должны указать ширину и высоту фильтра', // Added - 2019-03-25
|
||||||
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
|
'FilterUpdateDiskSpace'=> 'Обновить используемое дисковое пространство', // Edited - 2019-03-25
|
||||||
'FilterUploadEvents' => 'Upload all matches',
|
'FilterUploadEvents' => 'Загрузить все совпадения', // Added - 2019-03-25
|
||||||
'FilterVideoEvents' => 'Create video for all matches',
|
'FilterVideoEvents' => 'Создать видео для всех совпадений', // Added - 2019-03-25
|
||||||
'Filters' => 'Фильтры',
|
'Filters' => 'Фильтры',
|
||||||
'First' => 'Первый',
|
'First' => 'Первый',
|
||||||
'FlippedHori' => 'Flipped Horizontally',
|
'FlippedHori' => 'Перевернутый горизонтально', // Added - 2019-03-25
|
||||||
'FlippedVert' => 'Flipped Vertically',
|
'FlippedVert' => 'Перевернутый вертикально', // Added - 2019-03-25
|
||||||
'FnMocord' => 'Mocord', // Added 2013.08.16.
|
'FnMocord' => 'Mocord', // Added 2013.08.16.
|
||||||
'FnModect' => 'Modect', // Added 2013.08.16.
|
'FnModect' => 'Modect', // Added 2013.08.16.
|
||||||
'FnMonitor' => 'Monitor', // Added 2013.08.16.
|
'FnMonitor' => 'Monitor', // Added 2013.08.16.
|
||||||
|
@ -377,7 +378,7 @@ $SLANG = array(
|
||||||
'Function' => 'Функция',
|
'Function' => 'Функция',
|
||||||
'Gain' => 'Gain',
|
'Gain' => 'Gain',
|
||||||
'General' => 'Основные',
|
'General' => 'Основные',
|
||||||
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
|
'GenerateDownload' => 'Сгенерировать загрузку', // Edited - 2019-03-25
|
||||||
'GenerateVideo' => 'Генерировать видео',
|
'GenerateVideo' => 'Генерировать видео',
|
||||||
'GeneratingVideo' => 'Генерируется видео',
|
'GeneratingVideo' => 'Генерируется видео',
|
||||||
'GoToZoneMinder' => 'Перейти на ZoneMinder.com',
|
'GoToZoneMinder' => 'Перейти на ZoneMinder.com',
|
||||||
|
@ -398,7 +399,7 @@ $SLANG = array(
|
||||||
'High' => 'широкий',
|
'High' => 'широкий',
|
||||||
'HighBW' => 'Широкий канал',
|
'HighBW' => 'Широкий канал',
|
||||||
'Home' => 'Домой',
|
'Home' => 'Домой',
|
||||||
'Hostname' => 'Hostname', // Added - 2018-08-30
|
'Hostname' => 'Имя хоста', // Edited - 2019-03-25
|
||||||
'Hour' => 'Час',
|
'Hour' => 'Час',
|
||||||
'Hue' => 'Оттенок',
|
'Hue' => 'Оттенок',
|
||||||
'Id' => 'ИД',
|
'Id' => 'ИД',
|
||||||
|
@ -406,13 +407,13 @@ $SLANG = array(
|
||||||
'Ignore' => 'Игнорировать',
|
'Ignore' => 'Игнорировать',
|
||||||
'Image' => 'Изображение',
|
'Image' => 'Изображение',
|
||||||
'ImageBufferSize' => 'Буфер изображений',
|
'ImageBufferSize' => 'Буфер изображений',
|
||||||
'Images' => 'Images',
|
'Images' => 'Изображения', // Added - 2019-03-25
|
||||||
'In' => 'In',
|
'In' => 'In',
|
||||||
'Include' => 'Включить',
|
'Include' => 'Включить',
|
||||||
'Inverted' => 'Инвертировать',
|
'Inverted' => 'Инвертировать',
|
||||||
'Iris' => 'Наклон',
|
'Iris' => 'Наклон',
|
||||||
'KeyString' => 'Key String',
|
'KeyString' => 'Key String',
|
||||||
'Label' => 'Label',
|
'Label' => 'Имя', // Added - 2019-03-25
|
||||||
'Language' => 'Язык',
|
'Language' => 'Язык',
|
||||||
'Last' => 'Последний',
|
'Last' => 'Последний',
|
||||||
'Layout' => 'Раскладка', // Added - 2009-02-08
|
'Layout' => 'Раскладка', // Added - 2009-02-08
|
||||||
|
@ -423,16 +424,16 @@ $SLANG = array(
|
||||||
'Line' => 'Строка', // Added - 2011-06-16
|
'Line' => 'Строка', // Added - 2011-06-16
|
||||||
'LinkedMonitors' => 'Привязанные мониторы',
|
'LinkedMonitors' => 'Привязанные мониторы',
|
||||||
'List' => 'Список',
|
'List' => 'Список',
|
||||||
'ListMatches' => 'List Matches', // Added - 2018-08-30
|
'ListMatches' => 'Список совпадений', // Edited - 2019-03-25
|
||||||
'Load' => 'Нагрузка',
|
'Load' => 'Нагрузка',
|
||||||
'Local' => 'Локальный',
|
'Local' => 'Локальный',
|
||||||
'Log' => 'Лог', // Added - 2011-06-16;
|
'Log' => 'Лог', // Added - 2011-06-16;
|
||||||
'LoggedInAs' => 'Пользователь',
|
'LoggedInAs' => 'Пользователь',
|
||||||
'Logging' => 'Logging', // Added - 2011-06-16
|
'Logging' => 'Логгирование', // Added - 2019-03-24
|
||||||
'LoggingIn' => 'Вход в систему',
|
'LoggingIn' => 'Вход в систему',
|
||||||
'Login' => 'Войти',
|
'Login' => 'Войти',
|
||||||
'Logout' => 'Выйти',
|
'Logout' => 'Выйти',
|
||||||
'Logs' => 'Logs', // Added - 2011-06-17
|
'Logs' => 'Логи', // Added - 2019-03-24
|
||||||
'Low' => 'узкий',
|
'Low' => 'узкий',
|
||||||
'LowBW' => 'Узкий канал',
|
'LowBW' => 'Узкий канал',
|
||||||
'Main' => 'Основные',
|
'Main' => 'Основные',
|
||||||
|
@ -440,7 +441,7 @@ $SLANG = array(
|
||||||
'Manual' => 'Manual',
|
'Manual' => 'Manual',
|
||||||
'Mark' => 'Метка',
|
'Mark' => 'Метка',
|
||||||
'Max' => 'Макс.',
|
'Max' => 'Макс.',
|
||||||
'MaxBandwidth' => 'Max Bandwidth',
|
'MaxBandwidth' => 'Макс. пропускная способность', // Added - 2019-03-25
|
||||||
'MaxBrScore' => 'Макс.<br/>оценка',
|
'MaxBrScore' => 'Макс.<br/>оценка',
|
||||||
'MaxFocusRange' => 'Макс. диап. фокуса',
|
'MaxFocusRange' => 'Макс. диап. фокуса',
|
||||||
'MaxFocusSpeed' => 'Макс. скор. фокуса',
|
'MaxFocusSpeed' => 'Макс. скор. фокуса',
|
||||||
|
@ -510,7 +511,7 @@ $SLANG = array(
|
||||||
'MonitorProbeIntro' => 'В этом списке показаны найденные аналоговые и сетевые камеры, как уже заведенные, так и доступные для выбора.<br/><br/>Выберите нужную из списка ниже.<br/><br/>Обратите внимание, что не все камеры могут быть найдены, и что выбор камеры может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2009-03-31
|
'MonitorProbeIntro' => 'В этом списке показаны найденные аналоговые и сетевые камеры, как уже заведенные, так и доступные для выбора.<br/><br/>Выберите нужную из списка ниже.<br/><br/>Обратите внимание, что не все камеры могут быть найдены, и что выбор камеры может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2009-03-31
|
||||||
'Monitors' => 'Мониторы',
|
'Monitors' => 'Мониторы',
|
||||||
'Montage' => 'Монтаж',
|
'Montage' => 'Монтаж',
|
||||||
'MontageReview' => 'Montage Review', // Added - 2018-08-30
|
'MontageReview' => 'Обзор монтажа', // Added - 2019-03-24
|
||||||
'Month' => 'Месяц',
|
'Month' => 'Месяц',
|
||||||
'More' => 'Еще', // Added - 2011-06-16
|
'More' => 'Еще', // Added - 2011-06-16
|
||||||
'MotionFrameSkip' => 'Кол-во пропуск. кадров движения',
|
'MotionFrameSkip' => 'Кол-во пропуск. кадров движения',
|
||||||
|
@ -530,16 +531,16 @@ $SLANG = array(
|
||||||
'Network' => 'Сеть',
|
'Network' => 'Сеть',
|
||||||
'New' => 'Нов.',
|
'New' => 'Нов.',
|
||||||
'NewGroup' => 'Новая группа',
|
'NewGroup' => 'Новая группа',
|
||||||
'NewLabel' => 'New Label',
|
'NewLabel' => 'Новое имя', // Added - 2019-03-25
|
||||||
'NewPassword' => 'Новый пароль',
|
'NewPassword' => 'Новый пароль',
|
||||||
'NewState' => 'Новое состояние',
|
'NewState' => 'Новое состояние',
|
||||||
'NewUser' => 'Новый пользователь',
|
'NewUser' => 'Новый пользователь',
|
||||||
'Next' => 'След.',
|
'Next' => 'След.',
|
||||||
'No' => 'Нет',
|
'No' => 'Нет',
|
||||||
'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31
|
'NoDetectedCameras' => 'Нет камер', // Added - 2019-03-24
|
||||||
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
|
'NoDetectedProfiles' => 'Нет профилей', // Added - 2019-03-24
|
||||||
'NoFramesRecorded' => 'Это событие не содержит кадров',
|
'NoFramesRecorded' => 'Это событие не содержит кадров',
|
||||||
'NoGroup' => 'No Group',
|
'NoGroup' => 'Нет группы', // Added - 2019-03-24
|
||||||
'NoSavedFilters' => 'нет сохраненных фильтров',
|
'NoSavedFilters' => 'нет сохраненных фильтров',
|
||||||
'NoStatisticsRecorded' => 'Статистика по этому событию/кадру не записана',
|
'NoStatisticsRecorded' => 'Статистика по этому событию/кадру не записана',
|
||||||
'None' => 'отсутствует',
|
'None' => 'отсутствует',
|
||||||
|
@ -547,8 +548,8 @@ $SLANG = array(
|
||||||
'Normal' => 'Нормальная',
|
'Normal' => 'Нормальная',
|
||||||
'Notes' => 'Примечание',
|
'Notes' => 'Примечание',
|
||||||
'NumPresets' => 'Кол-во предустановок',
|
'NumPresets' => 'Кол-во предустановок',
|
||||||
'Off' => 'Off',
|
'Off' => 'Выкл.', // Added - 2019-03-25
|
||||||
'On' => 'On',
|
'On' => 'Вкл.', // Added - 2019-03-25
|
||||||
'OnvifCredentialsIntro'=> 'Пожалуйста укажите имя пользователя и пароль для выбранной камеры.<br/><br/>Если пользователь для камеры не был создан, тогда будет создан новый с указанными данными.<br/><br/>', // Added - 2015-04-18
|
'OnvifCredentialsIntro'=> 'Пожалуйста укажите имя пользователя и пароль для выбранной камеры.<br/><br/>Если пользователь для камеры не был создан, тогда будет создан новый с указанными данными.<br/><br/>', // Added - 2015-04-18
|
||||||
'OnvifProbe' => 'ONVIF', // Added - 2015-04-18
|
'OnvifProbe' => 'ONVIF', // Added - 2015-04-18
|
||||||
'OnvifProbeIntro' => 'В этом списке показаны найденные ONVIF камеры, как уже заведенные, так и доступные для выбора.<br/><br/>Выберите нужную из списка ниже.<br/><br/>Обратите внимание, что не все камеры могут быть найдены, и что выбор камеры может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2015-04-18
|
'OnvifProbeIntro' => 'В этом списке показаны найденные ONVIF камеры, как уже заведенные, так и доступные для выбора.<br/><br/>Выберите нужную из списка ниже.<br/><br/>Обратите внимание, что не все камеры могут быть найдены, и что выбор камеры может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2015-04-18
|
||||||
|
@ -567,7 +568,7 @@ $SLANG = array(
|
||||||
'Open' => 'Открыть',
|
'Open' => 'Открыть',
|
||||||
'OptionHelp' => 'Справка',
|
'OptionHelp' => 'Справка',
|
||||||
'OptionRestartWarning' => 'Эти изменения подействуют только после перезапуска программы.',
|
'OptionRestartWarning' => 'Эти изменения подействуют только после перезапуска программы.',
|
||||||
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
|
'OptionalEncoderParam' => 'Необязательные параметры кодировщика', // Added - 2019-03-24
|
||||||
'Options' => 'Опции',
|
'Options' => 'Опции',
|
||||||
'OrEnterNewName' => 'или введите новое имя',
|
'OrEnterNewName' => 'или введите новое имя',
|
||||||
'Order' => 'Сортировка',
|
'Order' => 'Сортировка',
|
||||||
|
@ -592,7 +593,7 @@ $SLANG = array(
|
||||||
'Play' => 'Играть',
|
'Play' => 'Играть',
|
||||||
'PlayAll' => 'Воспр. все',
|
'PlayAll' => 'Воспр. все',
|
||||||
'PleaseWait' => 'Пожалуйста подождите',
|
'PleaseWait' => 'Пожалуйста подождите',
|
||||||
'Plugins' => 'Plugins',
|
'Plugins' => 'Плагины', // Edited - 2019-03-24
|
||||||
'Point' => 'Точка',
|
'Point' => 'Точка',
|
||||||
'PostEventImageBuffer' => 'Буфер после события',
|
'PostEventImageBuffer' => 'Буфер после события',
|
||||||
'PreEventImageBuffer' => 'Буфер до события',
|
'PreEventImageBuffer' => 'Буфер до события',
|
||||||
|
@ -605,13 +606,13 @@ $SLANG = array(
|
||||||
'ProfileProbeIntro' => 'В этом списке показаны существующие профили потока выбранной камеры.<br/><br/>Выберите нужный из списка ниже.<br/><br/>Обратите внимание, что ZoneMinder не может добавить дополнительный профиль, и что выбор профиля может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2015-04-18
|
'ProfileProbeIntro' => 'В этом списке показаны существующие профили потока выбранной камеры.<br/><br/>Выберите нужный из списка ниже.<br/><br/>Обратите внимание, что ZoneMinder не может добавить дополнительный профиль, и что выбор профиля может переписать настройки определенные для этого монитора.<br/><br/>', // Added - 2015-04-18
|
||||||
'Progress' => 'Прогресс', // Added - 2015-04-18
|
'Progress' => 'Прогресс', // Added - 2015-04-18
|
||||||
'Protocol' => 'Протокол',
|
'Protocol' => 'Протокол',
|
||||||
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
|
'RTSPDescribe' => 'Использовать RTSP URL для ответа', // Edited - 2019-03-25
|
||||||
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
|
'RTSPTransport' => 'Транспортный протокол RTSP', // Edited - 2019-03-25
|
||||||
'Rate' => 'Скорость',
|
'Rate' => 'Скорость',
|
||||||
'Real' => 'Реальная',
|
'Real' => 'Реальная',
|
||||||
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
|
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
|
||||||
'Record' => 'Record',
|
'Record' => 'Record',
|
||||||
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
|
'RecordAudio' => 'Сохранять ли аудиопоток при сохранении события.', // Edited - 2019-03-25
|
||||||
'RefImageBlendPct' => 'Смешение опорного кадра, %',
|
'RefImageBlendPct' => 'Смешение опорного кадра, %',
|
||||||
'Refresh' => 'Обновить',
|
'Refresh' => 'Обновить',
|
||||||
'Remote' => 'Удаленный',
|
'Remote' => 'Удаленный',
|
||||||
|
@ -627,7 +628,7 @@ $SLANG = array(
|
||||||
'ReplayAll' => 'Все события',
|
'ReplayAll' => 'Все события',
|
||||||
'ReplayGapless' => 'События подряд',
|
'ReplayGapless' => 'События подряд',
|
||||||
'ReplaySingle' => 'Одно событие',
|
'ReplaySingle' => 'Одно событие',
|
||||||
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
|
'ReportEventAudit' => 'Отчёт о событиях аудита', // Edited - 2019-03-24
|
||||||
'Reset' => 'Сбросить',
|
'Reset' => 'Сбросить',
|
||||||
'ResetEventCounts' => 'Обнулить счетчик событий',
|
'ResetEventCounts' => 'Обнулить счетчик событий',
|
||||||
'Restart' => 'Перезапустить',
|
'Restart' => 'Перезапустить',
|
||||||
|
@ -639,14 +640,14 @@ $SLANG = array(
|
||||||
'Rewind' => 'Назад',
|
'Rewind' => 'Назад',
|
||||||
'RotateLeft' => 'Повернуть влево',
|
'RotateLeft' => 'Повернуть влево',
|
||||||
'RotateRight' => 'Повернуть вправо',
|
'RotateRight' => 'Повернуть вправо',
|
||||||
'RunLocalUpdate' => 'Please run zmupdate.pl to update', // Added - 2011-05-25
|
'RunLocalUpdate' => 'Запустите zmupdate.pl для обновления', // Edited - 2019-03-24
|
||||||
'RunMode' => 'Режим работы',
|
'RunMode' => 'Режим работы',
|
||||||
'RunState' => 'Состояние',
|
'RunState' => 'Состояние',
|
||||||
'Running' => 'Выполняется',
|
'Running' => 'Выполняется',
|
||||||
'Save' => 'Сохранить',
|
'Save' => 'Сохранить',
|
||||||
'SaveAs' => 'Сохранить как',
|
'SaveAs' => 'Сохранить как',
|
||||||
'SaveFilter' => 'Сохранить фильтр',
|
'SaveFilter' => 'Сохранить фильтр',
|
||||||
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
|
'SaveJPEGs' => 'Сохранить JPEG-и', // Edited - 2019-03-24
|
||||||
'Scale' => 'Масштаб',
|
'Scale' => 'Масштаб',
|
||||||
'Score' => 'Оценка',
|
'Score' => 'Оценка',
|
||||||
'Secs' => 'Сек.',
|
'Secs' => 'Сек.',
|
||||||
|
@ -654,46 +655,46 @@ $SLANG = array(
|
||||||
'Select' => 'Выбор',
|
'Select' => 'Выбор',
|
||||||
'SelectFormat' => 'Выберите формат', // Added - 2011-06-17
|
'SelectFormat' => 'Выберите формат', // Added - 2011-06-17
|
||||||
'SelectLog' => 'Выберите лог', // Added - 2011-06-17
|
'SelectLog' => 'Выберите лог', // Added - 2011-06-17
|
||||||
'SelectMonitors' => 'Select Monitors',
|
'SelectMonitors' => 'Выбрать Мониторы', // Edited - 2019-03-24
|
||||||
'SelfIntersecting' => 'Polygon edges must not intersect',
|
'SelfIntersecting' => 'Polygon edges must not intersect',
|
||||||
'Set' => 'Set',
|
'Set' => 'Установка', // Edited - 2019-03-24
|
||||||
'SetNewBandwidth' => 'Установка новой ширина канала',
|
'SetNewBandwidth' => 'Установка новой ширина канала',
|
||||||
'SetPreset' => 'Set Preset',
|
'SetPreset' => 'Установка пресета', // Edited - 2019-03-24
|
||||||
'Settings' => 'Настройки',
|
'Settings' => 'Настройки',
|
||||||
'ShowFilterWindow' => 'Показать окно фильтра',
|
'ShowFilterWindow' => 'Показать окно фильтра',
|
||||||
'ShowTimeline' => 'Показать график',
|
'ShowTimeline' => 'Показать график',
|
||||||
'SignalCheckColour' => 'Цвет проверки сигнала',
|
'SignalCheckColour' => 'Цвет проверки сигнала',
|
||||||
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
|
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
|
||||||
'Size' => 'Size',
|
'Size' => 'Размер', // Edited - 2019-03-24
|
||||||
'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30
|
'SkinDescription' => 'Смена стандартного скина для данного компьютера', // Edited - 2019-03-24
|
||||||
'Sleep' => 'Sleep',
|
'Sleep' => 'Sleep',
|
||||||
'SortAsc' => 'По возр.',
|
'SortAsc' => 'По возр.',
|
||||||
'SortBy' => 'Сортировать',
|
'SortBy' => 'Сортировать',
|
||||||
'SortDesc' => 'По убыв.',
|
'SortDesc' => 'По убыв.',
|
||||||
'Source' => 'Источник',
|
'Source' => 'Источник',
|
||||||
'SourceColours' => 'Source Colours', // Added - 2009-02-08
|
'SourceColours' => 'Цвета источника', // Edited - 2019-03-24
|
||||||
'SourcePath' => 'Путь к источнику', // Added - 2009-02-08
|
'SourcePath' => 'Путь к источнику', // Added - 2009-02-08
|
||||||
'SourceType' => 'Тип источника',
|
'SourceType' => 'Тип источника',
|
||||||
'Speed' => 'Speed',
|
'Speed' => 'Скорость', //Edited - 2019-03-24
|
||||||
'SpeedHigh' => 'High Speed',
|
'SpeedHigh' => 'Высокая скорость', //Edited - 2019-03-24
|
||||||
'SpeedLow' => 'Low Speed',
|
'SpeedLow' => 'Низкая скорость', //Edited - 2019-03-24
|
||||||
'SpeedMedium' => 'Medium Speed',
|
'SpeedMedium' => 'Средняя скорость',
|
||||||
'SpeedTurbo' => 'Turbo Speed',
|
'SpeedTurbo' => 'Максимальная скорость', // Edited - 2019-03-24
|
||||||
'Start' => 'Запустить',
|
'Start' => 'Запустить',
|
||||||
'State' => 'Состояние',
|
'State' => 'Состояние',
|
||||||
'Stats' => 'Статистика',
|
'Stats' => 'Статистика',
|
||||||
'Status' => 'Статус',
|
'Status' => 'Статус',
|
||||||
'StatusConnected' => 'Capturing', // Added - 2018-08-30
|
'StatusConnected' => 'Записывается', // Edited - 2019-03-25
|
||||||
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
|
'StatusNotRunning' => 'Не запущен', // Edited - 2019-03-25
|
||||||
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
|
'StatusRunning' => 'Не записывается', // Edited - 2019-03-25
|
||||||
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
|
'StatusUnknown' => 'Неизвестно', // Edited - 2019-03-25
|
||||||
'Step' => 'Шаг',
|
'Step' => 'Шаг',
|
||||||
'StepBack' => 'Кадр назад',
|
'StepBack' => 'Кадр назад',
|
||||||
'StepForward' => 'Кадр вперед',
|
'StepForward' => 'Кадр вперед',
|
||||||
'StepLarge' => 'Large Step',
|
'StepLarge' => 'Большой шаг', // Added - 2019-03-25
|
||||||
'StepMedium' => 'Medium Step',
|
'StepMedium' => 'Средний шаг', // Added - 2019-03-25
|
||||||
'StepNone' => 'No Step',
|
'StepNone' => 'Без шагов', // Added - 2019-03-25
|
||||||
'StepSmall' => 'Small Step',
|
'StepSmall' => 'Малый шаг', // Added - 2019-03-25
|
||||||
'Stills' => 'Стоп-кадры',
|
'Stills' => 'Стоп-кадры',
|
||||||
'Stop' => 'Остановить',
|
'Stop' => 'Остановить',
|
||||||
'Stopped' => 'Остановлен',
|
'Stopped' => 'Остановлен',
|
||||||
|
@ -758,24 +759,24 @@ $SLANG = array(
|
||||||
'VersionRemindNever' => 'Не говорить о новых версиях',
|
'VersionRemindNever' => 'Не говорить о новых версиях',
|
||||||
'VersionRemindWeek' => 'Напомнить через неделю',
|
'VersionRemindWeek' => 'Напомнить через неделю',
|
||||||
'Video' => 'Видео',
|
'Video' => 'Видео',
|
||||||
'VideoFormat' => 'Video Format',
|
'VideoFormat' => 'Формат видео', // Edited - 2019-03-24
|
||||||
'VideoGenFailed' => 'Ошибка генерации видео!',
|
'VideoGenFailed' => 'Ошибка генерации видео!',
|
||||||
'VideoGenFiles' => 'Existing Video Files',
|
'VideoGenFiles' => 'Existing Video Files',
|
||||||
'VideoGenNoFiles' => 'No Video Files Found',
|
'VideoGenNoFiles' => 'Видео не найдено', // Edited - 2019-03-24
|
||||||
'VideoGenParms' => 'Параметры генерации видео',
|
'VideoGenParms' => 'Параметры генерации видео',
|
||||||
'VideoGenSucceeded' => 'Video Generation Succeeded!',
|
'VideoGenSucceeded' => 'Видео сгенерировано!', // Edited - 2019-03-24
|
||||||
'VideoSize' => 'Размер изображения',
|
'VideoSize' => 'Размер изображения',
|
||||||
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
|
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
|
||||||
'View' => 'Просмотр',
|
'View' => 'Просмотр',
|
||||||
'ViewAll' => 'Просм. все',
|
'ViewAll' => 'Просм. все',
|
||||||
'ViewEvent' => 'View Event',
|
'ViewEvent' => 'Просм. событие', // Edited - 2019-03-24
|
||||||
'ViewPaged' => 'Просм. постранично',
|
'ViewPaged' => 'Просм. постранично',
|
||||||
'Wake' => 'Wake',
|
'Wake' => 'Wake',
|
||||||
'WarmupFrames' => 'Кадры разогрева',
|
'WarmupFrames' => 'Кадры разогрева',
|
||||||
'Watch' => 'Watch',
|
'Watch' => 'Watch',
|
||||||
'Web' => 'Интерфейс',
|
'Web' => 'Интерфейс',
|
||||||
'WebColour' => 'Цвет отметки',
|
'WebColour' => 'Цвет отметки',
|
||||||
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
|
'WebSiteUrl' => 'URL сайта', // Edited - 2019-03-25
|
||||||
'Week' => 'Неделя',
|
'Week' => 'Неделя',
|
||||||
'White' => 'Бал. белого',
|
'White' => 'Бал. белого',
|
||||||
'WhiteBalance' => 'White Balance',
|
'WhiteBalance' => 'White Balance',
|
||||||
|
@ -798,7 +799,7 @@ $SLANG = array(
|
||||||
'ZoneMinMaxBlobs' => 'Мин/Макс кол-во объектов',
|
'ZoneMinMaxBlobs' => 'Мин/Макс кол-во объектов',
|
||||||
'ZoneMinMaxFiltArea' => 'Мин/Макс разм. фильтр. зоны ',
|
'ZoneMinMaxFiltArea' => 'Мин/Макс разм. фильтр. зоны ',
|
||||||
'ZoneMinMaxPixelThres' => 'Мин/Макс порог изм. пикс. (0-255)',
|
'ZoneMinMaxPixelThres' => 'Мин/Макс порог изм. пикс. (0-255)',
|
||||||
'ZoneMinderLog' => 'ZoneMinder Log', // Added - 2011-06-17
|
'ZoneMinderLog' => 'Лог ZoneMinder', // Edited - 2019-03-25
|
||||||
'ZoneOverloadFrames' => 'Кол-во игнор. кадров перегрузки',
|
'ZoneOverloadFrames' => 'Кол-во игнор. кадров перегрузки',
|
||||||
'Zones' => 'Зоны',
|
'Zones' => 'Зоны',
|
||||||
'Zoom' => 'Увеличение',
|
'Zoom' => 'Увеличение',
|
||||||
|
|
|
@ -290,7 +290,7 @@ $SLANG = array(
|
||||||
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
|
||||||
'Donate' => 'Var vänlig och donera',
|
'Donate' => 'Var vänlig och donera',
|
||||||
'DonateAlready' => 'Nej, Jag har redan donerat',
|
'DonateAlready' => 'Nej, Jag har redan donerat',
|
||||||
'DonateEnticement' => 'Du har kört ZoneMinder ett tag nu och förhoppningsvis har du sett att det fungerar bra hemma eller på ditt företag. Även om ZoneMinder är, och kommer att vara, fri programvara och öppen kallkod, så kostar det pengar att utveckla och underhålla. Om du vill hjälpa till med framtida utveckling och nya funktioner så var vanlig och bidrag med en slant. Bidragen är naturligtvis en option men mycket uppskattade och du kan bidra med precis hur mycket du vill.<br><br>Om du vill ge ett bidrag väljer du nedan eller surfar till http://www.zoneminder.com/donate.html.<br><br>Tack för att du använder ZoneMinder, glöm inte att besöka forumen på ZoneMinder.com för support och förslag om hur du får din ZoneMinder att fungera lite bättre.',
|
'DonateEnticement' => 'Du har kört ZoneMinder ett tag nu och förhoppningsvis har du sett att det fungerar bra hemma eller på ditt företag. Även om ZoneMinder är, och kommer att vara, fri programvara och öppen kallkod, så kostar det pengar att utveckla och underhålla. Om du vill hjälpa till med framtida utveckling och nya funktioner så var vanlig och bidrag med en slant. Bidragen är naturligtvis en option men mycket uppskattade och du kan bidra med precis hur mycket du vill.<br><br>Om du vill ge ett bidrag väljer du nedan eller surfar till https://zoneminder.com/donate/.<br><br>Tack för att du använder ZoneMinder, glöm inte att besöka forumen på ZoneMinder.com för support och förslag om hur du får din ZoneMinder att fungera lite bättre.',
|
||||||
'DonateRemindDay' => 'Inte än, påminn om 1 dag',
|
'DonateRemindDay' => 'Inte än, påminn om 1 dag',
|
||||||
'DonateRemindHour' => 'Inte än, påminn om en 1 timme',
|
'DonateRemindHour' => 'Inte än, påminn om en 1 timme',
|
||||||
'DonateRemindMonth' => 'Inte än, påminn om 1 månad',
|
'DonateRemindMonth' => 'Inte än, påminn om 1 månad',
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .centerBtn {
|
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .centerBtn {
|
||||||
background: url("../skins/classic/graphics/graphics/center.png") no-repeat 0 0;
|
background: url("../skins/classic/graphics/center.png") no-repeat 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .rightBtn {
|
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .rightBtn {
|
||||||
|
|
|
@ -173,12 +173,12 @@ echo output_link_if_exists( array(
|
||||||
<script src="<?php echo cache_bust('skins/classic/js/base.js') ?>"></script>
|
<script src="<?php echo cache_bust('skins/classic/js/base.js') ?>"></script>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<script src="<?php echo cache_bust($skinJsFile) ?>"></script>
|
<script src="<?php echo cache_bust($skinJsFile) ?>"></script>
|
||||||
<script src="js/logger.js"></script>
|
<script src="<?php echo cache_bust('js/logger.js')?>"></script>
|
||||||
<?php
|
<?php
|
||||||
if ($basename == 'watch' or $basename == 'log' ) {
|
if ($basename == 'watch' or $basename == 'log' ) {
|
||||||
// This is used in the log popup for the export function. Not sure if it's used anywhere else
|
// This is used in the log popup for the export function. Not sure if it's used anywhere else
|
||||||
?>
|
?>
|
||||||
<script src="js/overlay.js"></script>
|
<script src="<?php echo cache_bust('js/overlay.js') ?>"></script>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<?php
|
<?php
|
||||||
if ( $viewJsFile ) {
|
if ( $viewJsFile ) {
|
||||||
|
|
|
@ -64,6 +64,15 @@ function initPage() {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener( 'DOMContentLoaded', initPage );
|
// Disable form submit on enter
|
||||||
|
$j('#contentForm input').on('keyup keypress', function(e) {
|
||||||
|
var keyCode = e.keyCode || e.which;
|
||||||
|
if (keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} // end function initPage()
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', initPage);
|
||||||
|
|
|
@ -46,7 +46,7 @@ if ( isset($_REQUEST['scale']) )
|
||||||
else
|
else
|
||||||
$scale = reScale(SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE);
|
$scale = reScale(SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE);
|
||||||
|
|
||||||
$Event = new Event($event['Id']);
|
$Event = new ZM\Event($event['Id']);
|
||||||
$eventPath = $Event->Path();
|
$eventPath = $Event->Path();
|
||||||
|
|
||||||
$videoFormats = array();
|
$videoFormats = array();
|
||||||
|
|
Loading…
Reference in New Issue