mirror of
https://github.com/Motion-Project/motion.git
synced 2026-02-08 05:51:35 -05:00
2284 lines
76 KiB
C++
2284 lines
76 KiB
C++
/*
|
|
* This file is part of MotionPlus.
|
|
*
|
|
* MotionPlus is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MotionPlus is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with MotionPlus. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* Copyright 2020-2023 MotionMrDave@gmail.com
|
|
*
|
|
*/
|
|
|
|
|
|
/***********************************************************
|
|
*
|
|
* The functions:
|
|
* netcam_setup
|
|
* netcam_next
|
|
* netcam_cleanup
|
|
* are called from video_common.c which is on the main thread
|
|
*
|
|
***********************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <regex.h>
|
|
#include <time.h>
|
|
#include "motionplus.hpp"
|
|
#include "conf.hpp"
|
|
#include "logger.hpp"
|
|
#include "util.hpp"
|
|
#include "rotate.hpp"
|
|
#include "netcam.hpp"
|
|
#include "movie.hpp"
|
|
|
|
static void netcam_check_buffsize(netcam_buff_ptr buff, size_t numbytes)
|
|
{
|
|
int min_size_to_alloc;
|
|
int real_alloc;
|
|
int new_size;
|
|
|
|
if ((buff->size - buff->used) >= numbytes) {
|
|
return;
|
|
}
|
|
|
|
min_size_to_alloc = numbytes - (buff->size - buff->used);
|
|
real_alloc = ((min_size_to_alloc / NETCAM_BUFFSIZE) * NETCAM_BUFFSIZE);
|
|
|
|
if ((min_size_to_alloc - real_alloc) > 0) {
|
|
real_alloc += NETCAM_BUFFSIZE;
|
|
}
|
|
|
|
new_size = buff->size + real_alloc;
|
|
|
|
buff->ptr =(char*) myrealloc(buff->ptr, new_size,
|
|
"netcam_check_buf_size");
|
|
buff->size = new_size;
|
|
}
|
|
|
|
/*
|
|
* The following three routines (netcam_url_match, netcam_url_parse and
|
|
* netcam_url_free are for 'parsing' (i.e. separating into the relevant
|
|
* components) the URL provided by the user. They make use of regular
|
|
* expressions (which is outside the scope of this module, so detailed
|
|
* comments are not provided). netcam_url_parse is called from netcam_start,
|
|
* and puts the "broken-up" components of the URL into the "url" element of
|
|
* the netcam_context structure.
|
|
*
|
|
* Note that the routines are not "very clever", but they work sufficiently
|
|
* well for the limited requirements of this module. The expression:
|
|
* (http)://(((.*):(.*))@)?([^/:]|[-.a-z0-9]+)(:([0-9]+))?($|(/[^:]*))
|
|
* requires
|
|
* 1) a string which begins with 'http', followed by '://'
|
|
* 2) optionally a '@' which is preceded by two strings
|
|
* (with 0 or more characters each) separated by a ':'
|
|
* [this is for an optional username:password]
|
|
* 3) a string comprising alpha-numerics, '-' and '.' characters
|
|
* [this is for the hostname]
|
|
* 4) optionally a ':' followed by one or more numeric characters
|
|
* [this is for an optional port number]
|
|
* 5) finally, either an end of line or a series of segments,
|
|
* each of which begins with a '/', and contains anything
|
|
* except a ':'
|
|
*/
|
|
|
|
/**
|
|
* netcam_url_match
|
|
*
|
|
* Finds the matched part of a regular expression
|
|
*
|
|
* Parameters:
|
|
*
|
|
* m A structure containing the regular expression to be used
|
|
* input The input string
|
|
*
|
|
* Returns: The string which was matched
|
|
*
|
|
*/
|
|
static char *netcam_url_match(regmatch_t m, const char *input)
|
|
{
|
|
char *match = NULL;
|
|
int len;
|
|
|
|
if (m.rm_so != -1) {
|
|
len = m.rm_eo - m.rm_so;
|
|
|
|
if ((match =(char*) mymalloc(len + 1)) != NULL) {
|
|
strncpy(match, input + m.rm_so, len);
|
|
match[len] = '\0';
|
|
}
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
static void netcam_url_invalid(ctx_url *parse_url)
|
|
{
|
|
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Invalid URL. Can not parse values."));
|
|
|
|
parse_url->host =(char*) malloc(5);
|
|
parse_url->service =(char*) malloc(5);
|
|
parse_url->path =(char*) malloc(10);
|
|
parse_url->userpass =(char*) malloc(10);
|
|
parse_url->port = 0;
|
|
sprintf(parse_url->host, "%s","????");
|
|
sprintf(parse_url->service, "%s","????");
|
|
sprintf(parse_url->path, "%s","INVALID");
|
|
sprintf(parse_url->userpass, "%s","INVALID");
|
|
|
|
}
|
|
/**
|
|
* netcam_url_parse
|
|
*
|
|
* parses a string containing a URL into it's components
|
|
*
|
|
* Parameters:
|
|
* parse_url A structure which will receive the results
|
|
* of the parsing
|
|
* text_url The input string containing the URL
|
|
*
|
|
* Returns: Nothing
|
|
*
|
|
*/
|
|
static void netcam_url_parse(ctx_url *parse_url, const char *text_url)
|
|
{
|
|
char *s;
|
|
int i;
|
|
|
|
const char *re = "(.*)://(((.*):(.*))@)?"
|
|
"([^/:]|[-_.a-z0-9]+)(:([0-9]+))?($|(/[^*]*))";
|
|
regex_t pattbuf;
|
|
regmatch_t matches[10];
|
|
|
|
if (!strncmp(text_url, "file", 4)) {
|
|
re = "(file)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))";
|
|
}
|
|
if (!strncmp(text_url, "v4l2", 4)) {
|
|
re = "(v4l2)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))";
|
|
}
|
|
/* Note that log messages are commented out to avoid leaking info related
|
|
* to user/host/pass etc. Keeing them in the code for easier debugging if
|
|
* it is needed
|
|
*/
|
|
|
|
//MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "Entry netcam_url_parse data %s",text_url);
|
|
|
|
memset(parse_url, 0, sizeof(ctx_url));
|
|
/*
|
|
* regcomp compiles regular expressions into a form that is
|
|
* suitable for regexec searches
|
|
* regexec matches the URL string against the regular expression
|
|
* and returns an array of pointers to strings matching each match
|
|
* within (). The results that we need are finally placed in parse_url.
|
|
*/
|
|
if (!regcomp(&pattbuf, re, REG_EXTENDED | REG_ICASE)) {
|
|
if (regexec(&pattbuf, text_url, 10, matches, 0) != REG_NOMATCH) {
|
|
for (i = 0; i < 10; i++) {
|
|
if ((s = netcam_url_match(matches[i], text_url)) != NULL) {
|
|
//MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "Parse case %d data %s", i, s);
|
|
switch (i) {
|
|
case 1:
|
|
parse_url->service = s;
|
|
break;
|
|
case 3:
|
|
parse_url->userpass = s;
|
|
break;
|
|
case 6:
|
|
parse_url->host = s;
|
|
break;
|
|
case 8:
|
|
parse_url->port = atoi(s);
|
|
free(s);
|
|
break;
|
|
case 9:
|
|
parse_url->path = s;
|
|
break;
|
|
/* Other components ignored */
|
|
default:
|
|
free(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
netcam_url_invalid(parse_url);
|
|
}
|
|
} else {
|
|
netcam_url_invalid(parse_url);
|
|
}
|
|
if (((!parse_url->port) && (parse_url->service)) ||
|
|
((parse_url->port > 65535) && (parse_url->service))) {
|
|
if (mystreq(parse_url->service, "http")) {
|
|
parse_url->port = 80;
|
|
} else if (mystreq(parse_url->service, "ftp")) {
|
|
parse_url->port = 21;
|
|
} else if (mystreq(parse_url->service, "rtmp")) {
|
|
parse_url->port = 1935;
|
|
} else if (mystreq(parse_url->service, "rtsp")) {
|
|
parse_url->port = 554;
|
|
}
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Using port number %d"),parse_url->port);
|
|
}
|
|
|
|
regfree(&pattbuf);
|
|
}
|
|
|
|
/**
|
|
* netcam_url_free
|
|
*
|
|
* General cleanup of the URL structure, called from netcam_cleanup.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* parse_url Structure containing the parsed data.
|
|
*
|
|
* Returns: Nothing
|
|
*
|
|
*/
|
|
static void netcam_url_free(ctx_url *parse_url)
|
|
{
|
|
myfree(&parse_url->service);
|
|
myfree(&parse_url->userpass);
|
|
myfree(&parse_url->host);
|
|
myfree(&parse_url->path);
|
|
}
|
|
|
|
static void netcam_free_pkt(ctx_netcam *netcam)
|
|
{
|
|
mypacket_free(netcam->packet_recv);
|
|
netcam->packet_recv = NULL;
|
|
}
|
|
|
|
static int netcam_check_pixfmt(ctx_netcam *netcam)
|
|
{
|
|
/* Determine if the format is YUV420P */
|
|
int retcd;
|
|
|
|
retcd = -1;
|
|
if (((enum AVPixelFormat)netcam->frame->format == MY_PIX_FMT_YUV420P) ||
|
|
((enum AVPixelFormat)netcam->frame->format == MY_PIX_FMT_YUVJ420P)) {
|
|
retcd = 0;
|
|
}
|
|
|
|
return retcd;
|
|
|
|
}
|
|
|
|
static void netcam_pktarray_free(ctx_netcam *netcam)
|
|
{
|
|
|
|
int indx;
|
|
pthread_mutex_lock(&netcam->mutex_pktarray);
|
|
if (netcam->pktarray_size > 0) {
|
|
for(indx = 0; indx < netcam->pktarray_size; indx++) {
|
|
mypacket_free(netcam->pktarray[indx].packet);
|
|
netcam->pktarray[indx].packet = NULL;
|
|
}
|
|
}
|
|
myfree(&netcam->pktarray);
|
|
netcam->pktarray_size = 0;
|
|
netcam->pktarray_index = -1;
|
|
pthread_mutex_unlock(&netcam->mutex_pktarray);
|
|
|
|
}
|
|
|
|
static void netcam_null_context(ctx_netcam *netcam)
|
|
{
|
|
|
|
netcam->swsctx = NULL;
|
|
netcam->swsframe_in = NULL;
|
|
netcam->swsframe_out = NULL;
|
|
netcam->frame = NULL;
|
|
netcam->codec_context = NULL;
|
|
netcam->format_context = NULL;
|
|
netcam->transfer_format = NULL;
|
|
netcam->hw_device_ctx = NULL;
|
|
|
|
}
|
|
|
|
static void netcam_close_context(ctx_netcam *netcam)
|
|
{
|
|
|
|
if (netcam->swsctx != NULL) sws_freeContext(netcam->swsctx);
|
|
if (netcam->swsframe_in != NULL) myframe_free(netcam->swsframe_in);
|
|
if (netcam->swsframe_out != NULL) myframe_free(netcam->swsframe_out);
|
|
if (netcam->frame != NULL) myframe_free(netcam->frame);
|
|
if (netcam->pktarray != NULL) netcam_pktarray_free(netcam);
|
|
if (netcam->codec_context != NULL) myavcodec_close(netcam->codec_context);
|
|
if (netcam->format_context != NULL) avformat_close_input(&netcam->format_context);
|
|
if (netcam->transfer_format != NULL) avformat_close_input(&netcam->transfer_format);
|
|
if (netcam->hw_device_ctx != NULL) av_buffer_unref(&netcam->hw_device_ctx);
|
|
netcam_null_context(netcam);
|
|
|
|
}
|
|
|
|
static void netcam_pktarray_resize(ctx_dev *cam, bool is_highres)
|
|
{
|
|
/* This is called from netcam_next and is on the motion loop thread
|
|
* The netcam->mutex is locked around the call to this function.
|
|
*/
|
|
|
|
/* Remember that this is a ring and we have two threads chasing around it
|
|
* the ffmpeg is writing out of this ring while we are filling it up. "Bad"
|
|
* things will occur if the "add" thread catches up with the "write" thread.
|
|
* We need this ring to be big enough so they don't collide.
|
|
* The alternative is that we'd need to make a copy of the entire packet
|
|
* array in the ffmpeg module and do our writing from that copy. The
|
|
* downside is that is a lot to be copying around for each image we want
|
|
* to write out. And putting a mutex on the array during adding function would
|
|
* slow down the capture thread to the speed of the writing thread. And that
|
|
* writing thread operates at the user specified FPS which could be really slow
|
|
* ...So....make this array big enough so we never catch our tail. :)
|
|
*/
|
|
|
|
int64_t idnbr_last, idnbr_first;
|
|
int indx;
|
|
ctx_netcam *netcam;
|
|
ctx_packet_item *tmp;
|
|
int newsize;
|
|
|
|
if (is_highres) {
|
|
idnbr_last = cam->imgs.image_ring[cam->imgs.ring_out].idnbr_high;
|
|
idnbr_first = cam->imgs.image_ring[cam->imgs.ring_in].idnbr_high;
|
|
netcam = cam->netcam_high;
|
|
} else {
|
|
idnbr_last = cam->imgs.image_ring[cam->imgs.ring_out].idnbr_norm;
|
|
idnbr_first = cam->imgs.image_ring[cam->imgs.ring_in].idnbr_norm;
|
|
netcam = cam->netcam;
|
|
}
|
|
|
|
if (!netcam->passthrough) {
|
|
return;
|
|
}
|
|
|
|
/* The 30 is arbitrary */
|
|
/* Double the size plus double last diff so we don't catch our tail */
|
|
newsize =((idnbr_first - idnbr_last) * 1 ) + ((netcam->idnbr - idnbr_last ) * 2);
|
|
if (newsize < 30) {
|
|
newsize = 30;
|
|
}
|
|
|
|
pthread_mutex_lock(&netcam->mutex_pktarray);
|
|
if ((netcam->pktarray_size < newsize) || (netcam->pktarray_size < 30)) {
|
|
tmp =(ctx_packet_item*) mymalloc(newsize * sizeof(ctx_packet_item));
|
|
if (netcam->pktarray_size > 0 ) {
|
|
memcpy(tmp, netcam->pktarray, sizeof(ctx_packet_item) * netcam->pktarray_size);
|
|
}
|
|
for(indx = netcam->pktarray_size; indx < newsize; indx++) {
|
|
tmp[indx].packet = NULL;
|
|
tmp[indx].packet = mypacket_alloc(tmp[indx].packet);
|
|
tmp[indx].idnbr = 0;
|
|
tmp[indx].iskey = false;
|
|
tmp[indx].iswritten = false;
|
|
}
|
|
|
|
myfree(&netcam->pktarray);
|
|
netcam->pktarray = tmp;
|
|
netcam->pktarray_size = newsize;
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Resized packet array to %d"), netcam->cameratype,newsize);
|
|
}
|
|
pthread_mutex_unlock(&netcam->mutex_pktarray);
|
|
|
|
}
|
|
|
|
static void netcam_pktarray_add(ctx_netcam *netcam)
|
|
{
|
|
|
|
int indx_next;
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
pthread_mutex_lock(&netcam->mutex_pktarray);
|
|
|
|
if (netcam->pktarray_size == 0) {
|
|
pthread_mutex_unlock(&netcam->mutex_pktarray);
|
|
return;
|
|
}
|
|
|
|
/* Recall pktarray_size is one based but pktarray is zero based */
|
|
if (netcam->pktarray_index == (netcam->pktarray_size-1)) {
|
|
indx_next = 0;
|
|
} else {
|
|
indx_next = netcam->pktarray_index + 1;
|
|
}
|
|
|
|
netcam->pktarray[indx_next].idnbr = netcam->idnbr;
|
|
|
|
mypacket_free(netcam->pktarray[indx_next].packet);
|
|
netcam->pktarray[indx_next].packet = NULL;
|
|
netcam->pktarray[indx_next].packet = mypacket_alloc(netcam->pktarray[indx_next].packet);
|
|
|
|
retcd = mycopy_packet(netcam->pktarray[indx_next].packet, netcam->packet_recv);
|
|
if ((netcam->interrupted) || (retcd < 0)) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: av_copy_packet: %s ,Interrupt: %s")
|
|
,netcam->cameratype
|
|
,errstr, netcam->interrupted ? _("true"):_("false"));
|
|
mypacket_free(netcam->pktarray[indx_next].packet);
|
|
netcam->pktarray[indx_next].packet = NULL;
|
|
}
|
|
|
|
if (netcam->pktarray[indx_next].packet->flags & AV_PKT_FLAG_KEY) {
|
|
netcam->pktarray[indx_next].iskey = true;
|
|
} else {
|
|
netcam->pktarray[indx_next].iskey = false;
|
|
}
|
|
netcam->pktarray[indx_next].iswritten = false;
|
|
|
|
netcam->pktarray_index = indx_next;
|
|
pthread_mutex_unlock(&netcam->mutex_pktarray);
|
|
|
|
}
|
|
|
|
static int netcam_decode_sw(ctx_netcam *netcam)
|
|
{
|
|
#if (MYFFVER >= 57041)
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
retcd = avcodec_receive_frame(netcam->codec_context, netcam->frame);
|
|
if ((netcam->interrupted) || (netcam->finish) || (retcd < 0)) {
|
|
if (retcd == AVERROR(EAGAIN)) {
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Ignoring packet with invalid data")
|
|
,netcam->cameratype);
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Rec frame error: %s")
|
|
,netcam->cameratype, errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
return retcd;
|
|
}
|
|
|
|
return 1;
|
|
#else
|
|
(void)netcam;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
static int netcam_decode_vaapi(ctx_netcam *netcam)
|
|
{
|
|
#if ( MYFFVER >= 57083)
|
|
int retcd;
|
|
char errstr[128];
|
|
AVFrame *hw_frame = NULL;
|
|
|
|
hw_frame = myframe_alloc();
|
|
|
|
retcd = avcodec_receive_frame(netcam->codec_context, hw_frame);
|
|
if ((netcam->interrupted) || (netcam->finish) || (retcd < 0)) {
|
|
if (retcd == AVERROR(EAGAIN)) {
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Ignoring packet with invalid data")
|
|
,netcam->cameratype);
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Rec frame error: %s")
|
|
,netcam->cameratype, errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
myframe_free(hw_frame);
|
|
return retcd;
|
|
}
|
|
netcam->frame->format=AV_PIX_FMT_YUV420P;
|
|
retcd = av_hwframe_transfer_data(netcam->frame, hw_frame, 0);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error transferring HW decoded to system memory")
|
|
,netcam->cameratype);
|
|
return -1;
|
|
}
|
|
myframe_free(hw_frame);
|
|
|
|
return 1;
|
|
#else
|
|
(void)netcam;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
/* netcam_decode_video
|
|
*
|
|
* Return values:
|
|
* <0 error
|
|
* 0 invalid but continue
|
|
* 1 valid data
|
|
*/
|
|
|
|
static int netcam_decode_video(ctx_netcam *netcam)
|
|
{
|
|
#if (MYFFVER >= 57041)
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
/* The Invalid data problem comes frequently. Usually at startup of rtsp cameras.
|
|
* We now ignore those packets so this function would need to fail on a different error.
|
|
* We should consider adding a maximum count of these errors and reset every time
|
|
* we get a good image.
|
|
*/
|
|
if (netcam->finish) {
|
|
return 0; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
retcd = avcodec_send_packet(netcam->codec_context, netcam->packet_recv);
|
|
if ((netcam->interrupted) || (netcam->finish)) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Interrupted or finish on send")
|
|
,netcam->cameratype);
|
|
return -1;
|
|
}
|
|
if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Send ignoring packet with invalid data")
|
|
,netcam->cameratype);
|
|
return 0;
|
|
}
|
|
if (retcd < 0 && retcd != AVERROR_EOF) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error sending packet to codec: %s")
|
|
,netcam->cameratype, errstr);
|
|
return -1;
|
|
}
|
|
|
|
if (netcam->hw_type == AV_HWDEVICE_TYPE_VAAPI) {
|
|
retcd = netcam_decode_vaapi(netcam);
|
|
} else {
|
|
retcd = netcam_decode_sw(netcam);
|
|
}
|
|
|
|
return retcd;
|
|
|
|
#else
|
|
|
|
int retcd;
|
|
int check = 0;
|
|
char errstr[128];
|
|
|
|
(void)netcam_decode_sw;
|
|
(void)netcam_decode_vaapi;
|
|
|
|
if (netcam->finish) {
|
|
return 0; /* This just speeds up the shutdown time */
|
|
}
|
|
retcd = avcodec_decode_video2(netcam->codec_context, netcam->frame, &check, &netcam->packet_recv);
|
|
if ((netcam->interrupted) || (netcam->finish)) {
|
|
return -1;
|
|
}
|
|
if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Ignoring packet with invalid data"));
|
|
return 0;
|
|
}
|
|
|
|
if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Error decoding packet: %s"),errstr);
|
|
return -1;
|
|
}
|
|
|
|
if (check == 0 || retcd == 0) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
#endif
|
|
|
|
}
|
|
|
|
static int netcam_decode_packet(ctx_netcam *netcam)
|
|
{
|
|
|
|
int frame_size;
|
|
int retcd;
|
|
|
|
if (netcam->finish) {
|
|
return -1; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
if (netcam->packet_recv->stream_index == netcam->audio_stream_index) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error decoding video packet...it is audio")
|
|
,netcam->cameratype);
|
|
}
|
|
|
|
retcd = netcam_decode_video(netcam);
|
|
if (retcd <= 0) {
|
|
return retcd;
|
|
}
|
|
frame_size = myimage_get_buffer_size((enum AVPixelFormat) netcam->frame->format
|
|
,netcam->frame->width
|
|
,netcam->frame->height);
|
|
|
|
netcam_check_buffsize(netcam->img_recv, frame_size);
|
|
netcam_check_buffsize(netcam->img_latest, frame_size);
|
|
|
|
retcd = myimage_copy_to_buffer(netcam->frame
|
|
,(uint8_t *)netcam->img_recv->ptr
|
|
,(enum AVPixelFormat) netcam->frame->format
|
|
,netcam->frame->width
|
|
,netcam->frame->height
|
|
,frame_size);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error decoding video packet: Copying to buffer")
|
|
,netcam->cameratype);
|
|
return -1;
|
|
}
|
|
|
|
netcam->img_recv->used = frame_size;
|
|
|
|
return frame_size;
|
|
}
|
|
|
|
static void netcam_hwdecoders(ctx_netcam *netcam)
|
|
{
|
|
#if ( MYFFVER >= 57083)
|
|
/* High Res pass through does not decode images into frames*/
|
|
if (netcam->high_resolution && netcam->passthrough) {
|
|
return;
|
|
}
|
|
if ((netcam->hw_type == AV_HWDEVICE_TYPE_NONE) && (netcam->first_image)) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: HW Devices: ")
|
|
, netcam->cameratype);
|
|
while((netcam->hw_type = av_hwdevice_iterate_types(netcam->hw_type)) != AV_HWDEVICE_TYPE_NONE){
|
|
if (netcam->hw_type == AV_HWDEVICE_TYPE_VAAPI) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s (available)")
|
|
, netcam->cameratype
|
|
, av_hwdevice_get_type_name(netcam->hw_type));
|
|
} else {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s (not implemented)")
|
|
, netcam->cameratype
|
|
, av_hwdevice_get_type_name(netcam->hw_type));
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
#else
|
|
(void)netcam;
|
|
return;
|
|
#endif
|
|
|
|
}
|
|
|
|
static enum AVPixelFormat netcam_getfmt_vaapi(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
|
|
{
|
|
#if ( MYFFVER >= 57083)
|
|
const enum AVPixelFormat *p;
|
|
(void)avctx;
|
|
|
|
for (p = pix_fmts; *p != -1; p++) {
|
|
if (*p == AV_PIX_FMT_VAAPI) {
|
|
return *p;
|
|
}
|
|
}
|
|
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Failed to get vaapi pix format"));
|
|
return AV_PIX_FMT_NONE;
|
|
#else
|
|
(void)avctx;
|
|
(void)pix_fmts;
|
|
return AV_PIX_FMT_NONE;
|
|
#endif
|
|
}
|
|
|
|
static void netcam_decoder_error(ctx_netcam *netcam, int retcd, const char* fnc_nm)
|
|
{
|
|
char errstr[128];
|
|
int indx;
|
|
|
|
if (netcam->interrupted) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Interrupted"),netcam->cameratype);
|
|
} else {
|
|
if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s: %s"),netcam->cameratype,fnc_nm, errstr);
|
|
} else {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s: Failed"), netcam->cameratype,fnc_nm);
|
|
}
|
|
}
|
|
|
|
if (mystrne(netcam->decoder_nm,"NULL")) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Decoder %s did not work.")
|
|
,netcam->cameratype, netcam->decoder_nm);
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Ignoring and removing the user requested decoder %s")
|
|
,netcam->cameratype, netcam->decoder_nm);
|
|
|
|
for (indx = 0; indx < netcam->params->params_count; indx++) {
|
|
if (mystreq(netcam->params->params_array[indx].param_name,"decoder") ) {
|
|
myfree(&netcam->params->params_array[indx].param_value);
|
|
netcam->params->params_array[indx].param_value = (char*)mymalloc(5);
|
|
snprintf(netcam->params->params_array[indx].param_value, 5, "%s","NULL");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (netcam->high_resolution) {
|
|
util_parms_update(netcam->params, netcam->conf->netcam_high_params);
|
|
} else {
|
|
util_parms_update(netcam->params, netcam->conf->netcam_params);
|
|
}
|
|
|
|
myfree(&netcam->decoder_nm);
|
|
netcam->decoder_nm = (char*)mymalloc(5);
|
|
snprintf(netcam->decoder_nm, 5, "%s","NULL");
|
|
}
|
|
|
|
}
|
|
|
|
static int netcam_init_vaapi(ctx_netcam *netcam)
|
|
{
|
|
#if ( MYFFVER >= 57083)
|
|
int retcd;
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Initializing vaapi decoder"),netcam->cameratype);
|
|
|
|
netcam->hw_type = av_hwdevice_find_type_by_name("vaapi");
|
|
if (netcam->hw_type == AV_HWDEVICE_TYPE_NONE) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("%s: Unable to find vaapi hw device")
|
|
, netcam->cameratype);
|
|
netcam_decoder_error(netcam, 0, "av_hwdevice");
|
|
return -1;
|
|
}
|
|
|
|
netcam->codec_context = avcodec_alloc_context3(netcam->decoder);
|
|
if ((netcam->codec_context == NULL) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, 0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(netcam->codec_context,netcam->strm->codecpar);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
netcam->hw_pix_fmt = AV_PIX_FMT_VAAPI;
|
|
netcam->codec_context->get_format = netcam_getfmt_vaapi;
|
|
av_opt_set_int(netcam->codec_context, "refcounted_frames", 1, 0);
|
|
netcam->codec_context->sw_pix_fmt = AV_PIX_FMT_YUV420P;
|
|
netcam->codec_context->hwaccel_flags=
|
|
AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH |
|
|
AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
|
|
|
retcd = av_hwdevice_ctx_create(&netcam->hw_device_ctx, netcam->hw_type, NULL, NULL, 0);
|
|
if (retcd < 0) {
|
|
netcam_decoder_error(netcam, retcd, "hwctx");
|
|
return -1;
|
|
}
|
|
netcam->codec_context->hw_device_ctx = av_buffer_ref(netcam->hw_device_ctx);
|
|
|
|
return 0;
|
|
#else
|
|
(void)netcam;
|
|
(void)netcam_getfmt_vaapi;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
static int netcam_init_swdecoder(ctx_netcam *netcam)
|
|
{
|
|
#if ( MYFFVER >= 57041)
|
|
int retcd;
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Initializing decoder"),netcam->cameratype);
|
|
|
|
if (mystrne(netcam->decoder_nm,"NULL")) {
|
|
netcam->decoder = avcodec_find_decoder_by_name(netcam->decoder_nm);
|
|
if (netcam->decoder == NULL) {
|
|
netcam_decoder_error(netcam, 0, "avcodec_find_decoder_by_name");
|
|
} else {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO,_("%s: Using decoder %s")
|
|
,netcam->cameratype, netcam->decoder_nm);
|
|
}
|
|
}
|
|
if (netcam->decoder == NULL) {
|
|
netcam->decoder = avcodec_find_decoder(netcam->strm->codecpar->codec_id);
|
|
}
|
|
if ((netcam->decoder == NULL) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, 0, "avcodec_find_decoder");
|
|
return -1;
|
|
}
|
|
|
|
netcam->codec_context = avcodec_alloc_context3(netcam->decoder);
|
|
if ((netcam->codec_context == NULL) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, 0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(netcam->codec_context, netcam->strm->codecpar);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
netcam->codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
|
|
netcam->codec_context->err_recognition = AV_EF_EXPLODE;
|
|
|
|
return 0;
|
|
#else
|
|
int retcd;
|
|
netcam->codec_context = netcam->strm->codec;
|
|
netcam->decoder = avcodec_find_decoder(netcam->codec_context->codec_id);
|
|
if ((netcam->decoder == NULL) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, 0, "avcodec_find_decoder");
|
|
return -1;
|
|
}
|
|
retcd = avcodec_open2(netcam->codec_context, netcam->decoder, NULL);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "avcodec_open2");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
#endif
|
|
|
|
}
|
|
|
|
static int netcam_open_codec(ctx_netcam *netcam)
|
|
{
|
|
#if ( MYFFVER >= 57041)
|
|
|
|
int retcd;
|
|
|
|
if (netcam->finish) {
|
|
return -1; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
netcam_hwdecoders(netcam);
|
|
|
|
retcd = av_find_best_stream(netcam->format_context
|
|
, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam->audio_stream_index = -1;
|
|
} else {
|
|
netcam->audio_stream_index = retcd;
|
|
}
|
|
|
|
netcam->decoder = NULL;
|
|
retcd = av_find_best_stream(netcam->format_context
|
|
, AVMEDIA_TYPE_VIDEO, -1, -1, &netcam->decoder, 0);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "av_find_best_stream");
|
|
return -1;
|
|
}
|
|
netcam->video_stream_index = retcd;
|
|
netcam->strm = netcam->format_context->streams[netcam->video_stream_index];
|
|
|
|
if (mystrceq(netcam->decoder_nm,"vaapi")) {
|
|
if (netcam_init_vaapi(netcam) < 0) {
|
|
netcam_decoder_error(netcam, retcd, "hwvaapi_init");
|
|
return -1;
|
|
}
|
|
} else {
|
|
netcam_init_swdecoder(netcam);
|
|
}
|
|
|
|
retcd = avcodec_open2(netcam->codec_context, netcam->decoder, NULL);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "avcodec_open2");
|
|
return -1;
|
|
}
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Decoder opened"),netcam->cameratype);
|
|
|
|
return 0;
|
|
#else
|
|
int retcd;
|
|
|
|
(void)netcam_init_vaapi;
|
|
|
|
if (netcam->finish) {
|
|
/* This just speeds up the shutdown time */
|
|
return -1;
|
|
}
|
|
|
|
netcam_hwdecoders(netcam);
|
|
netcam->decoder = NULL;
|
|
|
|
retcd = av_find_best_stream(netcam->format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
netcam_decoder_error(netcam, retcd, "av_find_best_stream");
|
|
return -1;
|
|
}
|
|
netcam->video_stream_index = retcd;
|
|
netcam->strm = netcam->format_context->streams[netcam->video_stream_index];
|
|
|
|
retcd = netcam_init_swdecoder(netcam);
|
|
|
|
return retcd;
|
|
#endif
|
|
|
|
}
|
|
|
|
static ctx_netcam *netcam_new_context(void)
|
|
{
|
|
ctx_netcam *ret;
|
|
|
|
/* Note that mymalloc will exit on any problem. */
|
|
ret =(ctx_netcam*) mymalloc(sizeof(ctx_netcam));
|
|
|
|
memset(ret, 0, sizeof(ctx_netcam));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int netcam_interrupt(void *ctx)
|
|
{
|
|
/* Must return as an int since this is a callback to a C function */
|
|
ctx_netcam *netcam = (ctx_netcam *)ctx;
|
|
|
|
if (netcam->finish) {
|
|
netcam->interrupted = true;
|
|
return true;
|
|
}
|
|
|
|
if (netcam->status == NETCAM_CONNECTED) {
|
|
return false;
|
|
} else if (netcam->status == NETCAM_READINGIMAGE) {
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptcurrenttime);
|
|
if ((netcam->interruptcurrenttime.tv_sec - netcam->interruptstarttime.tv_sec ) > netcam->interruptduration){
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera reading (%s) timed out")
|
|
, netcam->cameratype, netcam->camera_name);
|
|
netcam->interrupted = true;
|
|
return true;
|
|
} else{
|
|
return false;
|
|
}
|
|
} else {
|
|
/* This is for NOTCONNECTED and RECONNECTING status. We give these
|
|
* options more time because all the ffmpeg calls that are inside the
|
|
* netcam_connect function will use the same start time. Otherwise we
|
|
* would need to reset the time before each call to a ffmpeg function.
|
|
*/
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptcurrenttime);
|
|
if ((netcam->interruptcurrenttime.tv_sec - netcam->interruptstarttime.tv_sec ) > netcam->interruptduration){
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera (%s) timed out")
|
|
, netcam->cameratype, netcam->camera_name);
|
|
netcam->interrupted = true;
|
|
return true;
|
|
} else{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* should not be possible to get here */
|
|
return false;
|
|
}
|
|
|
|
static int netcam_open_sws(ctx_netcam *netcam)
|
|
{
|
|
|
|
if (netcam->finish) {
|
|
return -1; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
netcam->swsframe_in = myframe_alloc();
|
|
if (netcam->swsframe_in == NULL) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Unable to allocate swsframe_in.")
|
|
, netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
netcam->swsframe_out = myframe_alloc();
|
|
if (netcam->swsframe_out == NULL) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Unable to allocate swsframe_out.")
|
|
, netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* The scaling context is used to change dimensions to config file and
|
|
* also if the format sent by the camera is not YUV420.
|
|
*/
|
|
if (netcam_check_pixfmt(netcam) != 0) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Pixel format %d will be converted.")
|
|
, netcam->cameratype, netcam->frame->format);
|
|
}
|
|
|
|
netcam->swsctx = sws_getContext(
|
|
netcam->frame->width
|
|
,netcam->frame->height
|
|
,(enum AVPixelFormat)netcam->frame->format
|
|
,netcam->imgsize.width
|
|
,netcam->imgsize.height
|
|
,MY_PIX_FMT_YUV420P
|
|
,SWS_BICUBIC,NULL,NULL,NULL);
|
|
if (netcam->swsctx == NULL) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Unable to allocate scaling context.")
|
|
, netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
netcam->swsframe_size = myimage_get_buffer_size(
|
|
MY_PIX_FMT_YUV420P
|
|
,netcam->imgsize.width
|
|
,netcam->imgsize.height);
|
|
if (netcam->swsframe_size <= 0) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Error determining size of frame out")
|
|
, netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
/* the image buffers must be big enough to hold the final frame after resizing */
|
|
netcam_check_buffsize(netcam->img_recv, netcam->swsframe_size);
|
|
netcam_check_buffsize(netcam->img_latest, netcam->swsframe_size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int netcam_resize(ctx_netcam *netcam)
|
|
{
|
|
|
|
int retcd;
|
|
char errstr[128];
|
|
uint8_t *buffer_out;
|
|
|
|
if (netcam->finish) {
|
|
return -1; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
if (netcam->swsctx == NULL) {
|
|
if (netcam_open_sws(netcam) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
retcd=myimage_fill_arrays(
|
|
netcam->swsframe_in
|
|
,(uint8_t*)netcam->img_recv->ptr
|
|
,(enum AVPixelFormat)netcam->frame->format
|
|
,netcam->frame->width
|
|
,netcam->frame->height);
|
|
if (retcd < 0) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error allocating picture in: %s")
|
|
, netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
buffer_out=(uint8_t *)av_malloc(netcam->swsframe_size*sizeof(uint8_t));
|
|
|
|
retcd=myimage_fill_arrays(
|
|
netcam->swsframe_out
|
|
,buffer_out
|
|
,MY_PIX_FMT_YUV420P
|
|
,netcam->imgsize.width
|
|
,netcam->imgsize.height);
|
|
if (retcd < 0) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error allocating picture out: %s")
|
|
, netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
retcd = sws_scale(
|
|
netcam->swsctx
|
|
,(const uint8_t* const *)netcam->swsframe_in->data
|
|
,netcam->swsframe_in->linesize
|
|
,0
|
|
,netcam->frame->height
|
|
,netcam->swsframe_out->data
|
|
,netcam->swsframe_out->linesize);
|
|
if (retcd < 0) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error resizing/reformatting: %s")
|
|
, netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
retcd=myimage_copy_to_buffer(
|
|
netcam->swsframe_out
|
|
,(uint8_t *)netcam->img_recv->ptr
|
|
,MY_PIX_FMT_YUV420P
|
|
,netcam->imgsize.width
|
|
,netcam->imgsize.height
|
|
,netcam->swsframe_size);
|
|
if (retcd < 0) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Error putting frame into output buffer: %s")
|
|
, netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
netcam->img_recv->used = netcam->swsframe_size;
|
|
|
|
av_free(buffer_out);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int netcam_read_image(ctx_netcam *netcam)
|
|
{
|
|
|
|
int size_decoded, retcd, errcnt, nodata;
|
|
bool haveimage;
|
|
char errstr[128];
|
|
netcam_buff *xchg;
|
|
|
|
if (netcam->finish) {
|
|
return -1; /* This just speeds up the shutdown time */
|
|
}
|
|
|
|
netcam->packet_recv = mypacket_alloc(netcam->packet_recv);
|
|
|
|
netcam->interrupted=false;
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptstarttime);
|
|
netcam->interruptduration = 10;
|
|
|
|
netcam->status = NETCAM_READINGIMAGE;
|
|
netcam->img_recv->used = 0;
|
|
size_decoded = 0;
|
|
errcnt = 0;
|
|
haveimage = false;
|
|
nodata = 0;
|
|
|
|
while ((!haveimage) && (!netcam->interrupted)) {
|
|
retcd = av_read_frame(netcam->format_context, netcam->packet_recv);
|
|
if (retcd < 0 ) {
|
|
errcnt++;
|
|
}
|
|
if ((netcam->interrupted) || (errcnt > 1)) {
|
|
if (netcam->interrupted) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Interrupted")
|
|
,netcam->cameratype);
|
|
} else {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: av_read_frame: %s" )
|
|
,netcam->cameratype,errstr);
|
|
}
|
|
netcam_free_pkt(netcam);
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
} else {
|
|
errcnt = 0;
|
|
if ((netcam->packet_recv->stream_index == netcam->video_stream_index) ||
|
|
(netcam->packet_recv->stream_index == netcam->audio_stream_index)) {
|
|
/* For a high resolution pass-through we don't decode the image */
|
|
if ((netcam->high_resolution && netcam->passthrough) ||
|
|
(netcam->packet_recv->stream_index != netcam->video_stream_index)) {
|
|
if (netcam->packet_recv->data != NULL) {
|
|
size_decoded = 1;
|
|
}
|
|
} else {
|
|
size_decoded = netcam_decode_packet(netcam);
|
|
}
|
|
}
|
|
if (size_decoded > 0) {
|
|
haveimage = true;
|
|
} else if (size_decoded == 0) {
|
|
/* Did not fail, just didn't get anything. Try again */
|
|
netcam_free_pkt(netcam);
|
|
netcam->packet_recv = mypacket_alloc(netcam->packet_recv);
|
|
|
|
/* The 1000 is arbitrary */
|
|
nodata++;
|
|
if (nodata > 1000) {
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
netcam_free_pkt(netcam);
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->img_recv->image_time);
|
|
netcam->last_stream_index = netcam->packet_recv->stream_index;
|
|
netcam->last_pts = netcam->packet_recv->pts;
|
|
|
|
if (!netcam->first_image) {
|
|
netcam->status = NETCAM_CONNECTED;
|
|
}
|
|
|
|
/* Skip resize/pix format for high pass-through */
|
|
if (!(netcam->high_resolution && netcam->passthrough) &&
|
|
(netcam->packet_recv->stream_index == netcam->video_stream_index)) {
|
|
|
|
if ((netcam->imgsize.width != netcam->frame->width) ||
|
|
(netcam->imgsize.height != netcam->frame->height) ||
|
|
(netcam_check_pixfmt(netcam) != 0)) {
|
|
if (netcam_resize(netcam) < 0) {
|
|
netcam_free_pkt(netcam);
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
pthread_mutex_lock(&netcam->mutex);
|
|
netcam->idnbr++;
|
|
if (netcam->passthrough) {
|
|
netcam_pktarray_add(netcam);
|
|
}
|
|
if (!(netcam->high_resolution && netcam->passthrough) &&
|
|
(netcam->packet_recv->stream_index == netcam->video_stream_index)) {
|
|
xchg = netcam->img_latest;
|
|
netcam->img_latest = netcam->img_recv;
|
|
netcam->img_recv = xchg;
|
|
}
|
|
pthread_mutex_unlock(&netcam->mutex);
|
|
|
|
netcam_free_pkt(netcam);
|
|
|
|
if (netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.den > 0) {
|
|
netcam->src_fps = (
|
|
(netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.num /
|
|
netcam->format_context->streams[netcam->video_stream_index]->avg_frame_rate.den) +
|
|
0.5);
|
|
if (netcam->capture_rate < 1) {
|
|
netcam->capture_rate = netcam->src_fps + 1;
|
|
if (netcam->pts_adj == false) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: capture_rate not specified in netcam_params. Using %d")
|
|
,netcam->cameratype,netcam->capture_rate);
|
|
}
|
|
}
|
|
} else {
|
|
if (netcam->capture_rate < 1) {
|
|
netcam->capture_rate = netcam->conf->framerate;
|
|
if (netcam->pts_adj == false) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: capture_rate not specified in netcam_params. Using framerate %d")
|
|
,netcam->cameratype, netcam->capture_rate);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netcam_ntc(ctx_netcam *netcam)
|
|
{
|
|
|
|
if ((netcam->finish) || (!netcam->first_image)) {
|
|
return 0;
|
|
}
|
|
|
|
/* High Res pass through does not decode images into frames*/
|
|
if (netcam->high_resolution && netcam->passthrough) {
|
|
return 0;
|
|
}
|
|
|
|
if ((netcam->imgsize.width != netcam->frame->width) ||
|
|
(netcam->imgsize.height != netcam->frame->height)) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "");
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************");
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The network camera is sending pictures at %dx%d")
|
|
, netcam->frame->width,netcam->frame->height);
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("resolution but config is %dx%d. If possible change")
|
|
, netcam->imgsize.width,netcam->imgsize.height);
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("the netcam or config so that the image height and"));
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("width are the same to lower the CPU usage."));
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************");
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "");
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void netcam_set_options(ctx_netcam *netcam)
|
|
{
|
|
|
|
int indx;
|
|
char *tmpval;
|
|
|
|
/* The log messages are a bit short in this function intentionally.
|
|
* The function name is printed in each message so that is being
|
|
* considered as part of the message.
|
|
*/
|
|
|
|
tmpval = (char*)mymalloc(PATH_MAX);
|
|
|
|
if (mystreq(netcam->service, "rtsp") ||
|
|
mystreq(netcam->service, "rtsps") ||
|
|
mystreq(netcam->service, "rtmp")) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: Setting rtsp/rtmp")
|
|
,netcam->cameratype);
|
|
util_parms_add_default(netcam->params,"rtsp_transport","tcp");
|
|
//util_parms_add_default(netcam->params,"allowed_media_types", "video");
|
|
|
|
} else if (mystreq(netcam->service, "http") ||
|
|
mystreq(netcam->service, "https")) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Setting input_format mjpeg"),netcam->cameratype);
|
|
netcam->format_context->iformat = av_find_input_format("mjpeg");
|
|
util_parms_add_default(netcam->params,"reconnect_on_network_error","1");
|
|
util_parms_add_default(netcam->params,"reconnect_at_eof","1");
|
|
util_parms_add_default(netcam->params,"reconnect","1");
|
|
util_parms_add_default(netcam->params,"multiple_requests","1");
|
|
util_parms_add_default(netcam->params,"reconnect_streamed","1");
|
|
|
|
} else if (mystreq(netcam->service, "v4l2")) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Setting input_format video4linux2"),netcam->cameratype);
|
|
netcam->format_context->iformat = av_find_input_format("video4linux2");
|
|
|
|
sprintf(tmpval,"%d",netcam->conf->framerate);
|
|
util_parms_add_default(netcam->params,"framerate", tmpval);
|
|
|
|
sprintf(tmpval,"%dx%d",netcam->conf->width, netcam->conf->height);
|
|
util_parms_add_default(netcam->params,"video_size", tmpval);
|
|
|
|
/* Allow a bit more time for the v4l2 device to start up */
|
|
//netcam->motapp-> ->watchdog = 60;
|
|
netcam->interruptduration = 55;
|
|
|
|
} else if (mystreq(netcam->service, "file")) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Setting up movie file"),netcam->cameratype);
|
|
|
|
} else {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: Setting up %s")
|
|
,netcam->cameratype, netcam->service);
|
|
}
|
|
|
|
free(tmpval);
|
|
|
|
/* Write the options to the context, while skipping the Motion ones */
|
|
for (indx = 0; indx < netcam->params->params_count; indx++) {
|
|
if (mystrne(netcam->params->params_array[indx].param_name,"decoder") &&
|
|
mystrne(netcam->params->params_array[indx].param_name,"capture_rate")) {
|
|
av_dict_set(&netcam->opts
|
|
, netcam->params->params_array[indx].param_name
|
|
, netcam->params->params_array[indx].param_value
|
|
, 0);
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s: option: %s = %s")
|
|
,netcam->cameratype
|
|
,netcam->params->params_array[indx].param_name
|
|
,netcam->params->params_array[indx].param_value
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void netcam_set_path (ctx_dev *cam, ctx_netcam *netcam )
|
|
{
|
|
|
|
char userpass[PATH_MAX];
|
|
ctx_url url;
|
|
int retcd;
|
|
|
|
netcam->path = NULL;
|
|
|
|
memset(&url, 0, sizeof(url));
|
|
memset(userpass,0,PATH_MAX);
|
|
|
|
if (netcam->high_resolution) {
|
|
netcam_url_parse(&url, cam->conf->netcam_high_url.c_str());
|
|
} else {
|
|
netcam_url_parse(&url, cam->conf->netcam_url.c_str());
|
|
}
|
|
|
|
if (cam->conf->netcam_userpass != "") {
|
|
cam->conf->netcam_userpass.copy(userpass, PATH_MAX);
|
|
} else if (url.userpass != NULL) {
|
|
retcd = snprintf(userpass,PATH_MAX,"%s",url.userpass);
|
|
if ((retcd <0) || (retcd>=PATH_MAX)) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("Error getting userpass"));
|
|
}
|
|
}
|
|
|
|
if (mystreq(url.service, "v4l2")) {
|
|
netcam->path =(char*) mymalloc(strlen(url.path) + 1);
|
|
sprintf(netcam->path, "%s",url.path);
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up v4l2"));
|
|
} else if (mystreq(url.service, "file")) {
|
|
netcam->path =(char*) mymalloc(strlen(url.path) + 1);
|
|
sprintf(netcam->path, "%s",url.path);
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up file"));
|
|
} else {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up %s "),url.service);
|
|
if (userpass != NULL) {
|
|
netcam->path =(char*) mymalloc(strlen(url.service) + 3 + strlen(userpass)
|
|
+ 1 + strlen(url.host) + 6 + strlen(url.path) + 2 );
|
|
sprintf((char *)netcam->path, "%s://%s@%s:%d%s",
|
|
url.service, userpass, url.host, url.port, url.path);
|
|
} else {
|
|
netcam->path =(char*) mymalloc(strlen(url.service) + 3 + strlen(url.host)
|
|
+ 6 + strlen(url.path) + 2);
|
|
sprintf((char *)netcam->path, "%s://%s:%d%s", url.service,
|
|
url.host, url.port, url.path);
|
|
}
|
|
}
|
|
|
|
sprintf(netcam->service, "%s",url.service);
|
|
|
|
netcam_url_free(&url);
|
|
|
|
}
|
|
|
|
static void netcam_set_parms (ctx_dev *cam, ctx_netcam *netcam )
|
|
{
|
|
/* Set the parameters to be used with our camera */
|
|
int indx, val_len;
|
|
|
|
netcam->motapp = cam->motapp;
|
|
netcam->conf = cam->conf;
|
|
|
|
pthread_mutex_lock(&netcam->motapp->global_lock);
|
|
netcam->threadnbr = ++netcam->motapp->threads_running;
|
|
pthread_mutex_unlock(&netcam->motapp->global_lock);
|
|
|
|
if (netcam->high_resolution) {
|
|
netcam->imgsize.width = 0;
|
|
netcam->imgsize.height = 0;
|
|
snprintf(netcam->cameratype, 29, "%s",_("High"));
|
|
netcam->params = (ctx_params*)mymalloc(sizeof(ctx_params));
|
|
netcam->params->update_params = true;
|
|
util_parms_parse(netcam->params, cam->conf->netcam_high_params);
|
|
} else {
|
|
netcam->imgsize.width = cam->conf->width;
|
|
netcam->imgsize.height = cam->conf->height;
|
|
snprintf(netcam->cameratype, 29, "%s",_("Norm"));
|
|
netcam->params = (ctx_params*)mymalloc(sizeof(ctx_params));
|
|
netcam->params->update_params = true;
|
|
util_parms_parse(netcam->params, cam->conf->netcam_params);
|
|
}
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Setting up camera."),netcam->cameratype);
|
|
|
|
netcam->status = NETCAM_NOTCONNECTED;
|
|
cam->conf->device_name.copy(netcam->camera_name,PATH_MAX);
|
|
mycheck_passthrough(cam);
|
|
util_parms_add_default(netcam->params,"decoder","NULL");
|
|
netcam->img_recv =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff));
|
|
netcam->img_recv->ptr =(char*) mymalloc(NETCAM_BUFFSIZE);
|
|
netcam->img_latest =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff));
|
|
netcam->img_latest->ptr =(char*) mymalloc(NETCAM_BUFFSIZE);
|
|
netcam->pktarray_size = 0;
|
|
netcam->pktarray_index = -1;
|
|
netcam->pktarray = NULL;
|
|
netcam->packet_recv = NULL;
|
|
netcam->handler_finished = true;
|
|
netcam->first_image = true;
|
|
netcam->reconnect_count = 0;
|
|
netcam->src_fps = -1; /* Default to neg so we know it has not been set */
|
|
netcam->pts_adj = false;
|
|
netcam->capture_rate = -1;
|
|
netcam->video_stream_index = -1;
|
|
netcam->audio_stream_index = -1;
|
|
netcam->last_stream_index = -1;
|
|
|
|
for (indx = 0; indx < netcam->params->params_count; indx++) {
|
|
if (mystreq(netcam->params->params_array[indx].param_name,"decoder")) {
|
|
val_len = strlen(netcam->params->params_array[indx].param_value) + 1;
|
|
netcam->decoder_nm = (char*)mymalloc(val_len);
|
|
snprintf(netcam->decoder_nm, val_len
|
|
, "%s",netcam->params->params_array[indx].param_value);
|
|
}
|
|
if (mystreq(netcam->params->params_array[indx].param_name,"capture_rate")) {
|
|
if (mystreq(netcam->params->params_array[indx].param_value,"pts")) {
|
|
netcam->pts_adj = true;
|
|
} else {
|
|
netcam->capture_rate = atoi(netcam->params->params_array[indx].param_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If this is the norm and we have a highres, then disable passthru on the norm */
|
|
if ((!netcam->high_resolution) &&
|
|
(cam->conf->netcam_high_url != "")) {
|
|
netcam->passthrough = false;
|
|
} else {
|
|
netcam->passthrough = mycheck_passthrough(cam);
|
|
}
|
|
|
|
snprintf(netcam->threadname, 15, "%s",_("Unknown"));
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptstarttime);
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptcurrenttime);
|
|
|
|
netcam->interruptduration = 5;
|
|
netcam->interrupted = false;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->frame_curr_tm);
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->frame_prev_tm);
|
|
|
|
netcam_set_path(cam, netcam);
|
|
|
|
}
|
|
|
|
static void netcam_set_dimensions (ctx_dev *cam)
|
|
{
|
|
|
|
cam->imgs.width = 0;
|
|
cam->imgs.height = 0;
|
|
cam->imgs.size_norm = 0;
|
|
cam->imgs.motionsize = 0;
|
|
|
|
cam->imgs.width_high = 0;
|
|
cam->imgs.height_high = 0;
|
|
cam->imgs.size_high = 0;
|
|
|
|
if (cam->conf->width % 8) {
|
|
MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO
|
|
,_("Image width (%d) requested is not modulo 8.")
|
|
, cam->conf->width);
|
|
cam->conf->width = cam->conf->width - (cam->conf->width % 8) + 8;
|
|
MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Adjusting width to next higher multiple of 8 (%d).")
|
|
, cam->conf->width);
|
|
}
|
|
if (cam->conf->height % 8) {
|
|
MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO
|
|
,_("Image height (%d) requested is not modulo 8."), cam->conf->height);
|
|
cam->conf->height = cam->conf->height - (cam->conf->height % 8) + 8;
|
|
MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO
|
|
,_("Adjusting height to next higher multiple of 8 (%d)."), cam->conf->height);
|
|
}
|
|
|
|
/* Fill in camera details into context structure. */
|
|
cam->imgs.width = cam->conf->width;
|
|
cam->imgs.height = cam->conf->height;
|
|
cam->imgs.size_norm = (cam->conf->width * cam->conf->height * 3) / 2;
|
|
cam->imgs.motionsize = cam->conf->width * cam->conf->height;
|
|
|
|
}
|
|
|
|
static int netcam_copy_stream(ctx_netcam *netcam)
|
|
{
|
|
/* Make a static copy of the stream information for use in passthrough processing */
|
|
#if (MYFFVER >= 57041)
|
|
AVStream *transfer_stream, *stream_in;
|
|
int retcd, indx;
|
|
|
|
pthread_mutex_lock(&netcam->mutex_transfer);
|
|
if (netcam->transfer_format != NULL) {
|
|
avformat_close_input(&netcam->transfer_format);
|
|
}
|
|
netcam->transfer_format = avformat_alloc_context();
|
|
for (indx = 0; indx < (int)netcam->format_context->nb_streams; indx++) {
|
|
if ((netcam->audio_stream_index == indx) ||
|
|
(netcam->video_stream_index == indx)) {
|
|
transfer_stream = avformat_new_stream(netcam->transfer_format, NULL);
|
|
stream_in = netcam->format_context->streams[indx];
|
|
retcd = avcodec_parameters_copy(transfer_stream->codecpar, stream_in->codecpar);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Unable to copy codec parameters")
|
|
, netcam->cameratype);
|
|
pthread_mutex_unlock(&netcam->mutex_transfer);
|
|
return -1;
|
|
}
|
|
transfer_stream->time_base = stream_in->time_base;
|
|
transfer_stream->avg_frame_rate = stream_in->avg_frame_rate;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&netcam->mutex_transfer);
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Stream copied for pass-through"));
|
|
return 0;
|
|
#else
|
|
/* This is disabled in the mycheck_passthrough but we need it here for compiling */
|
|
if (netcam != NULL) {
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("ffmpeg too old"));
|
|
}
|
|
return -1;
|
|
#endif
|
|
|
|
}
|
|
|
|
static int netcam_open_context(ctx_netcam *netcam)
|
|
{
|
|
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
if (netcam->finish) {
|
|
return -1;
|
|
}
|
|
|
|
if (netcam->path == NULL) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("Null path passed to connect"));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
netcam->opts = NULL;
|
|
netcam->format_context = avformat_alloc_context();
|
|
netcam->format_context->interrupt_callback.callback = netcam_interrupt;
|
|
netcam->format_context->interrupt_callback.opaque = netcam;
|
|
netcam->interrupted = false;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->interruptstarttime);
|
|
|
|
netcam->interruptduration = 20;
|
|
|
|
netcam_set_options(netcam);
|
|
|
|
retcd = avformat_open_input(&netcam->format_context, netcam->path, NULL, &netcam->opts);
|
|
if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Unable to open camera(%s): %s")
|
|
, netcam->cameratype, netcam->camera_name, errstr);
|
|
}
|
|
av_dict_free(&netcam->opts);
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
av_dict_free(&netcam->opts);
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Opened camera(%s)"), netcam->cameratype, netcam->camera_name);
|
|
|
|
|
|
/* fill out stream information */
|
|
retcd = avformat_find_stream_info(netcam->format_context, NULL);
|
|
if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Unable to find stream info: %s")
|
|
,netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
/* there is no way to set the avcodec thread names, but they inherit
|
|
* our thread name - so temporarily change our thread name to the
|
|
* desired name */
|
|
|
|
mythreadname_get(netcam->threadname);
|
|
mythreadname_set("av",netcam->threadnbr,netcam->camera_name);
|
|
retcd = netcam_open_codec(netcam);
|
|
mythreadname_set(NULL, 0, netcam->threadname);
|
|
if ((retcd < 0) || (netcam->interrupted) || (netcam->finish) ) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Unable to open codec context: %s")
|
|
,netcam->cameratype, errstr);
|
|
} else {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Connected and unable to open codec context: %s")
|
|
,netcam->cameratype, errstr);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
if (netcam->codec_context->width <= 0 ||
|
|
netcam->codec_context->height <= 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera image size is invalid"),netcam->cameratype);
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
if (netcam->high_resolution) {
|
|
netcam->imgsize.width = netcam->codec_context->width;
|
|
netcam->imgsize.height = netcam->codec_context->height;
|
|
}
|
|
|
|
netcam->frame = myframe_alloc();
|
|
if (netcam->frame == NULL) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Unable to allocate frame."),netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
if (netcam->passthrough) {
|
|
retcd = netcam_copy_stream(netcam);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Failed to copy stream for pass-through.")
|
|
,netcam->cameratype);
|
|
}
|
|
netcam->passthrough = false;
|
|
}
|
|
}
|
|
|
|
/* Validate that the previous steps opened the camera */
|
|
retcd = netcam_read_image(netcam);
|
|
if ((retcd < 0) || (netcam->interrupted)) {
|
|
if (netcam->status == NETCAM_NOTCONNECTED) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Failed to read first image"),netcam->cameratype);
|
|
}
|
|
netcam_close_context(netcam);
|
|
return -1;
|
|
}
|
|
|
|
netcam->connection_pts = AV_NOPTS_VALUE;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int netcam_connect(ctx_netcam *netcam)
|
|
{
|
|
|
|
if (netcam_open_context(netcam) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (netcam_ntc(netcam) < 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
if (netcam_read_image(netcam) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* We use the status for determining whether to grab a image from
|
|
* the Motion loop(see "next" function). When we are initially starting,
|
|
* we open and close the context and during this process we do not want the
|
|
* Motion loop to start quite yet on this first image so we do
|
|
* not set the status to connected
|
|
*/
|
|
if (!netcam->first_image) {
|
|
netcam->status = NETCAM_CONNECTED;
|
|
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera (%s) connected")
|
|
, netcam->cameratype,netcam->camera_name);
|
|
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Netcam capture FPS is %d.")
|
|
, netcam->cameratype, netcam->capture_rate);
|
|
|
|
if (netcam->src_fps > 0) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Camera source is %d FPS")
|
|
, netcam->cameratype, netcam->src_fps);
|
|
} else {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Unable to determine the camera source FPS.")
|
|
, netcam->cameratype);
|
|
}
|
|
|
|
if (netcam->capture_rate < netcam->src_fps) {
|
|
MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Capture FPS less than camera FPS. Decoding errors will occur.")
|
|
, netcam->cameratype);
|
|
MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s: Capture FPS should be greater than camera FPS.")
|
|
, netcam->cameratype);
|
|
}
|
|
|
|
if (netcam->audio_stream_index != -1) {
|
|
/* The following is not technically precise but we want to convey in simple terms
|
|
* that the capture rate must go faster to account for the additional packets read
|
|
* for the audio stream. The technically correct process is that our wait timer in
|
|
* the handler is only triggered when the last packet is a video stream
|
|
*/
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: An audio stream was detected. Capture_rate increased to compensate.")
|
|
,netcam->cameratype);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void netcam_shutdown(ctx_netcam *netcam)
|
|
{
|
|
|
|
if (netcam) {
|
|
netcam_close_context(netcam);
|
|
|
|
netcam->status = NETCAM_NOTCONNECTED;
|
|
|
|
myfree(&netcam->path);
|
|
|
|
if (netcam->img_latest != NULL) {
|
|
free(netcam->img_latest->ptr);
|
|
free(netcam->img_latest);
|
|
netcam->img_latest = NULL;
|
|
}
|
|
|
|
if (netcam->img_recv != NULL) {
|
|
free(netcam->img_recv->ptr);
|
|
free(netcam->img_recv);
|
|
netcam->img_recv = NULL;
|
|
}
|
|
|
|
myfree(&netcam->decoder_nm);
|
|
util_parms_free(netcam->params);
|
|
myfree(&netcam->params);
|
|
}
|
|
|
|
}
|
|
|
|
static void netcam_handler_wait(ctx_netcam *netcam)
|
|
{
|
|
int64_t usec_ltncy;
|
|
AVRational tbase;
|
|
struct timespec tmp_tm;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tmp_tm);
|
|
|
|
if (netcam->capture_rate < 1) {
|
|
netcam->capture_rate = 1;
|
|
}
|
|
tbase = netcam->format_context->streams[netcam->video_stream_index]->time_base;
|
|
if (tbase.num == 0) {
|
|
tbase.num = 1;
|
|
}
|
|
|
|
/* FPS calculation from last frame capture */
|
|
netcam->frame_curr_tm = tmp_tm;
|
|
usec_ltncy = (1000000 / netcam->capture_rate) -
|
|
((netcam->frame_curr_tm.tv_sec - netcam->frame_prev_tm.tv_sec) * 1000000) -
|
|
((netcam->frame_curr_tm.tv_nsec - netcam->frame_prev_tm.tv_nsec)/1000);
|
|
|
|
/* Adjust to clock and pts timer */
|
|
if (netcam->pts_adj == true) {
|
|
if (netcam->connection_pts == AV_NOPTS_VALUE) {
|
|
netcam->connection_pts = netcam->last_pts;
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->connection_tm);
|
|
return;
|
|
}
|
|
if (netcam->last_pts != AV_NOPTS_VALUE) {
|
|
usec_ltncy +=
|
|
+ (av_rescale(netcam->last_pts, 1000000, tbase.den/tbase.num)
|
|
- av_rescale(netcam->connection_pts, 1000000, tbase.den/tbase.num))
|
|
-(((tmp_tm.tv_sec - netcam->connection_tm.tv_sec) * 1000000) +
|
|
((tmp_tm.tv_nsec - netcam->connection_tm.tv_nsec) / 1000));
|
|
}
|
|
}
|
|
|
|
if ((usec_ltncy > 0) && (usec_ltncy < 1000000L)) {
|
|
SLEEP(0, usec_ltncy * 1000);
|
|
}
|
|
|
|
}
|
|
|
|
static void netcam_handler_reconnect(ctx_netcam *netcam)
|
|
{
|
|
|
|
int retcd, indx;
|
|
|
|
if ((netcam->status == NETCAM_CONNECTED) ||
|
|
(netcam->status == NETCAM_READINGIMAGE)) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Reconnecting with camera...."),netcam->cameratype);
|
|
}
|
|
netcam->status = NETCAM_RECONNECTING;
|
|
|
|
/* When doing passthrough movies, any movies in progress
|
|
* must end and start again because the cache'd packets and timing
|
|
* on them gets all messed up. So we loop through the list of cameras
|
|
* and find the pointer that matches our passed in netcam.
|
|
*/
|
|
if (netcam->passthrough == true) {
|
|
for (indx=0; indx<netcam->motapp->cam_cnt; indx++) {
|
|
if ((netcam->motapp->cam_list[indx]->netcam == netcam) ||
|
|
(netcam->motapp->cam_list[indx]->netcam_high == netcam)) {
|
|
netcam->motapp->cam_list[indx]->event_stop = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The retry count of 100 is arbritrary.
|
|
* We want to try many times quickly to not lose too much information
|
|
* before we go into the long wait phase
|
|
*/
|
|
retcd = netcam_connect(netcam);
|
|
if (retcd < 0) {
|
|
if (netcam->reconnect_count < 100) {
|
|
netcam->reconnect_count++;
|
|
} else if ((netcam->reconnect_count >= 100) && (netcam->reconnect_count <= 199)) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera did not reconnect."), netcam->cameratype);
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Checking for camera every 10 seconds."),netcam->cameratype);
|
|
netcam->reconnect_count++;
|
|
SLEEP(10,0);
|
|
} else if (netcam->reconnect_count >= 200) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera did not reconnect."), netcam->cameratype);
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Checking for camera every 10 minutes."),netcam->cameratype);
|
|
SLEEP(600,0);
|
|
} else {
|
|
netcam->reconnect_count++;
|
|
SLEEP(600,0);
|
|
}
|
|
} else {
|
|
netcam->reconnect_count = 0;
|
|
}
|
|
|
|
}
|
|
|
|
static void *netcam_handler(void *arg)
|
|
{
|
|
|
|
ctx_netcam *netcam =(ctx_netcam *) arg;
|
|
|
|
netcam->handler_finished = false;
|
|
|
|
mythreadname_set("nc",netcam->threadnbr, netcam->camera_name);
|
|
|
|
pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)netcam->threadnbr));
|
|
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Camera handler thread [%d] started")
|
|
,netcam->cameratype, netcam->threadnbr);
|
|
|
|
while (!netcam->finish) {
|
|
if (!netcam->format_context) { /* We must have disconnected. Try to reconnect */
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->frame_prev_tm);
|
|
netcam_handler_reconnect(netcam);
|
|
continue;
|
|
} else { /* We think we are connected...*/
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->frame_prev_tm);
|
|
if (netcam_read_image(netcam) < 0) {
|
|
if (!netcam->finish) { /* Nope. We are not or got bad image. Reconnect*/
|
|
netcam_handler_reconnect(netcam);
|
|
}
|
|
continue;
|
|
}
|
|
if (netcam->last_stream_index == netcam->video_stream_index) {
|
|
netcam_handler_wait(netcam);
|
|
}
|
|
}
|
|
}
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Loop finished."),netcam->cameratype);
|
|
netcam_shutdown(netcam);
|
|
|
|
/* Our thread is finished - decrement motion's thread count. */
|
|
pthread_mutex_lock(&netcam->motapp->global_lock);
|
|
netcam->motapp->threads_running--;
|
|
pthread_mutex_unlock(&netcam->motapp->global_lock);
|
|
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Exiting"),netcam->cameratype);
|
|
netcam->handler_finished = true;
|
|
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
static int netcam_start_handler(ctx_netcam *netcam)
|
|
{
|
|
|
|
int retcd, wait_counter;
|
|
pthread_attr_t handler_attribute;
|
|
|
|
pthread_mutex_init(&netcam->mutex, NULL);
|
|
pthread_mutex_init(&netcam->mutex_pktarray, NULL);
|
|
pthread_mutex_init(&netcam->mutex_transfer, NULL);
|
|
|
|
pthread_attr_init(&handler_attribute);
|
|
pthread_attr_setdetachstate(&handler_attribute, PTHREAD_CREATE_DETACHED);
|
|
|
|
retcd = pthread_create(&netcam->thread_id, &handler_attribute, &netcam_handler, netcam);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ALR, TYPE_NETCAM, SHOW_ERRNO
|
|
,_("%s: Error starting handler thread"),netcam->cameratype);
|
|
pthread_attr_destroy(&handler_attribute);
|
|
return -1;
|
|
}
|
|
pthread_attr_destroy(&handler_attribute);
|
|
|
|
|
|
/* Now give a few tries to check that an image has been captured.
|
|
* This ensures that by the time the setup routine exits, the
|
|
* handler is completely set up and has images available
|
|
*/
|
|
wait_counter = 60;
|
|
while (wait_counter > 0) {
|
|
pthread_mutex_lock(&netcam->mutex);
|
|
if (netcam->img_latest->ptr != NULL ) {
|
|
wait_counter = -1;
|
|
}
|
|
pthread_mutex_unlock(&netcam->mutex);
|
|
|
|
if (wait_counter > 0 ) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Waiting for first image from the handler."),netcam->cameratype);
|
|
SLEEP(0,5000000);
|
|
wait_counter--;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
void netcam_cleanup(ctx_dev *cam)
|
|
{
|
|
int wait_counter;
|
|
int indx_cam, indx_max;
|
|
ctx_netcam *netcam;
|
|
|
|
indx_cam = 1;
|
|
if (cam->netcam_high) {
|
|
indx_max = 2;
|
|
} else {
|
|
indx_max = 1;
|
|
}
|
|
|
|
while (indx_cam <= indx_max) {
|
|
if (indx_cam == 1) {
|
|
netcam = cam->netcam;
|
|
} else {
|
|
netcam = cam->netcam_high;
|
|
}
|
|
|
|
if (netcam) {
|
|
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Shutting down network camera."),netcam->cameratype);
|
|
netcam->finish = true;
|
|
netcam->interruptduration = 0;
|
|
wait_counter = 0;
|
|
while ((!netcam->handler_finished) && (wait_counter < 10)) {
|
|
SLEEP(1,0);
|
|
wait_counter++;
|
|
}
|
|
if (!netcam->handler_finished) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: No response from handler thread."),netcam->cameratype);
|
|
/* Last resort. Kill the thread.*/
|
|
pthread_cancel(netcam->thread_id);
|
|
pthread_kill(netcam->thread_id, SIGVTALRM);
|
|
pthread_mutex_lock(&netcam->motapp->global_lock);
|
|
netcam->motapp->threads_running--;
|
|
pthread_mutex_unlock(&netcam->motapp->global_lock);
|
|
}
|
|
netcam_shutdown(netcam);
|
|
|
|
pthread_mutex_destroy(&netcam->mutex);
|
|
pthread_mutex_destroy(&netcam->mutex_pktarray);
|
|
pthread_mutex_destroy(&netcam->mutex_transfer);
|
|
|
|
myfree(&netcam);
|
|
|
|
if (indx_cam == 1) {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("Norm: Shut down complete."));
|
|
} else {
|
|
MOTION_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("High: Shut down complete."));
|
|
}
|
|
}
|
|
indx_cam++;
|
|
}
|
|
cam->netcam = NULL;
|
|
cam->netcam_high = NULL;
|
|
cam->device_status = STATUS_CLOSED;
|
|
|
|
}
|
|
|
|
void netcam_start(ctx_dev *cam)
|
|
{
|
|
int indx_cam, indx_max;
|
|
ctx_netcam *netcam;
|
|
|
|
MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO,_("Opening Netcam"));
|
|
|
|
cam->netcam = NULL;
|
|
cam->netcam_high = NULL;
|
|
|
|
netcam_set_dimensions(cam);
|
|
|
|
indx_cam = 1;
|
|
if (cam->conf->netcam_high_url != "") {
|
|
indx_max = 2;
|
|
} else {
|
|
indx_max = 1;
|
|
}
|
|
|
|
while (indx_cam <= indx_max) {
|
|
|
|
if (indx_cam == 1) {
|
|
cam->netcam = netcam_new_context();
|
|
if (cam->netcam == NULL) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("unable to create rtsp context"));
|
|
netcam_cleanup(cam);
|
|
return;
|
|
}
|
|
netcam = cam->netcam;
|
|
netcam->high_resolution = false; /* Set flag for this being the normal resolution camera */
|
|
} else {
|
|
cam->netcam_high = netcam_new_context();
|
|
if (cam->netcam_high == NULL) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("unable to create rtsp high context"));
|
|
netcam_cleanup(cam);
|
|
return;
|
|
}
|
|
netcam = cam->netcam_high;
|
|
netcam->high_resolution = true; /* Set flag for this being the high resolution camera */
|
|
}
|
|
|
|
netcam_null_context(netcam);
|
|
netcam_set_parms(cam, netcam);
|
|
if (netcam_connect(netcam) != 0) {
|
|
netcam_cleanup(cam);
|
|
return;
|
|
}
|
|
if (netcam_read_image(netcam) != 0) {
|
|
MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO
|
|
,_("Failed trying to read first image"));
|
|
netcam->status = NETCAM_NOTCONNECTED;
|
|
netcam_cleanup(cam);
|
|
return;
|
|
}
|
|
/* When running dual, there seems to be contamination across norm/high with codec functions. */
|
|
netcam_close_context(netcam); /* Close in this thread to open it again within handler thread */
|
|
netcam->status = NETCAM_RECONNECTING; /* Set as reconnecting to avoid excess messages when starting */
|
|
netcam->first_image = false; /* Set flag that we are not processing our first image */
|
|
|
|
/* For normal resolution, we resize the image to the config parms so we do not need
|
|
* to set the dimension parameters here (it is done in the set_parms). For high res
|
|
* we must get the dimensions from the first image captured
|
|
*/
|
|
if (netcam->high_resolution) {
|
|
cam->imgs.width_high = netcam->imgsize.width;
|
|
cam->imgs.height_high = netcam->imgsize.height;
|
|
}
|
|
|
|
if (netcam_start_handler(netcam) != 0 ) {
|
|
netcam_cleanup(cam);
|
|
return;
|
|
}
|
|
|
|
indx_cam++;
|
|
}
|
|
|
|
cam->device_status = STATUS_OPENED;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* netcam_next (Called from the motion loop thread) */
|
|
int netcam_next(ctx_dev *cam, ctx_image_data *img_data)
|
|
{
|
|
if ((cam == NULL) || (cam->netcam == NULL)) {
|
|
return CAPTURE_FAILURE;
|
|
}
|
|
|
|
if ((cam->netcam->status == NETCAM_RECONNECTING) ||
|
|
(cam->netcam->status == NETCAM_NOTCONNECTED)) {
|
|
return CAPTURE_ATTEMPTED;
|
|
}
|
|
pthread_mutex_lock(&cam->netcam->mutex);
|
|
netcam_pktarray_resize(cam, false);
|
|
memcpy(img_data->image_norm
|
|
, cam->netcam->img_latest->ptr
|
|
, cam->netcam->img_latest->used);
|
|
img_data->idnbr_norm = cam->netcam->idnbr;
|
|
pthread_mutex_unlock(&cam->netcam->mutex);
|
|
|
|
if (cam->netcam_high != NULL) {
|
|
if ((cam->netcam_high->status == NETCAM_RECONNECTING) ||
|
|
(cam->netcam_high->status == NETCAM_NOTCONNECTED)) {
|
|
return CAPTURE_ATTEMPTED;
|
|
}
|
|
|
|
pthread_mutex_lock(&cam->netcam_high->mutex);
|
|
netcam_pktarray_resize(cam, true);
|
|
if (!cam->netcam_high->passthrough) {
|
|
memcpy(img_data->image_high
|
|
,cam->netcam_high->img_latest->ptr
|
|
,cam->netcam_high->img_latest->used);
|
|
}
|
|
img_data->idnbr_high = cam->netcam_high->idnbr;
|
|
pthread_mutex_unlock(&cam->netcam_high->mutex);
|
|
}
|
|
|
|
rotate_map(cam, img_data);
|
|
|
|
return CAPTURE_SUCCESS;
|
|
}
|
|
|