mirror of
https://github.com/Motion-Project/motion.git
synced 2026-06-12 07:44:34 -04:00
2310 lines
69 KiB
C++
2310 lines
69 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/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#include "motionplus.hpp"
|
|
#include "camera.hpp"
|
|
#include "conf.hpp"
|
|
#include "logger.hpp"
|
|
#include "util.hpp"
|
|
#include "rotate.hpp"
|
|
#include "netcam.hpp"
|
|
#include "movie.hpp"
|
|
|
|
static void *netcam_handler(void *arg)
|
|
{
|
|
((cls_netcam *)arg)->handler();
|
|
return nullptr;
|
|
}
|
|
|
|
enum AVPixelFormat netcam_getfmt_vaapi(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
|
|
{
|
|
const enum AVPixelFormat *p;
|
|
(void)avctx;
|
|
|
|
for (p = pix_fmts; *p != -1; p++) {
|
|
if (*p == AV_PIX_FMT_VAAPI) {
|
|
return *p;
|
|
}
|
|
}
|
|
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Failed to get vaapi pix format"));
|
|
return AV_PIX_FMT_NONE;
|
|
}
|
|
|
|
enum AVPixelFormat netcam_getfmt_cuda(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
|
|
{
|
|
const enum AVPixelFormat *p;
|
|
(void)avctx;
|
|
|
|
for (p = pix_fmts; *p != -1; p++) {
|
|
if (*p == AV_PIX_FMT_CUDA) return *p;
|
|
}
|
|
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Failed to get cuda pix format"));
|
|
return AV_PIX_FMT_NONE;
|
|
}
|
|
|
|
enum AVPixelFormat netcam_getfmt_drm(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts)
|
|
{
|
|
const enum AVPixelFormat *p;
|
|
(void)avctx;
|
|
|
|
for (p = pix_fmts; *p != -1; p++) {
|
|
if (*p == AV_PIX_FMT_DRM_PRIME) return *p;
|
|
}
|
|
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Failed to get drm pix format"));
|
|
return AV_PIX_FMT_NONE;
|
|
}
|
|
|
|
int netcam_interrupt(void *ctx)
|
|
{
|
|
cls_netcam *netcam = (cls_netcam *)ctx;
|
|
|
|
if (netcam->handler_stop) {
|
|
netcam->interrupted = true;
|
|
return true;
|
|
}
|
|
|
|
if (netcam->status == NETCAM_CONNECTED) {
|
|
return false;
|
|
} else if (netcam->status == NETCAM_READINGIMAGE) {
|
|
clock_gettime(CLOCK_MONOTONIC, &netcam->icur_tm);
|
|
if ((netcam->icur_tm.tv_sec -
|
|
netcam->ist_tm.tv_sec ) > netcam->idur){
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera reading (%s) timed out")
|
|
, netcam->cameratype.c_str(), netcam->camera_name.c_str());
|
|
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
|
|
* 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->icur_tm);
|
|
if ((netcam->icur_tm.tv_sec - netcam->ist_tm.tv_sec ) > netcam->idur){
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera (%s) timed out")
|
|
, netcam->cameratype.c_str(), netcam->camera_name.c_str());
|
|
netcam->interrupted = true;
|
|
return true;
|
|
} else{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* should not be possible to get here */
|
|
return false;
|
|
}
|
|
|
|
bool netcam_filelist_cmp(const ctx_filelist_item &a, const ctx_filelist_item &b)
|
|
{
|
|
return a.filenm < b.filenm;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void cls_netcam::filelist_load()
|
|
{
|
|
DIR *d;
|
|
struct dirent *dir_ent;
|
|
struct stat sbuf;
|
|
ctx_filelist_item fileitm;
|
|
int retcd;
|
|
size_t chkloc;
|
|
|
|
filenbr++;
|
|
if ((filenbr == (int)filelist.size()) ||
|
|
(path == "")) {
|
|
filelist.clear();
|
|
retcd = stat(filedir.c_str(), &sbuf);
|
|
if ((sbuf.st_mode & S_IFREG ) && (retcd == 0)) {
|
|
MOTPLS_LOG(DBG, TYPE_NETCAM, NO_ERRNO
|
|
, _("File specified: %s"),filedir.c_str());
|
|
fileitm.fullnm = filedir;
|
|
chkloc = fileitm.fullnm.find_last_of("/");
|
|
if (chkloc == std::string::npos) {
|
|
fileitm.filenm = fileitm.fullnm;
|
|
} else {
|
|
fileitm.filenm = fileitm.fullnm.substr(chkloc+1);
|
|
}
|
|
chkloc = fileitm.filenm.find_last_of(".");
|
|
if (chkloc == std::string::npos) {
|
|
fileitm.displaynm = fileitm.filenm;
|
|
} else {
|
|
fileitm.displaynm = fileitm.filenm.substr(0,chkloc);
|
|
}
|
|
filelist.push_back(fileitm);
|
|
} else if ((sbuf.st_mode & S_IFDIR ) && (retcd == 0)) {
|
|
MOTPLS_LOG(DBG, TYPE_NETCAM, NO_ERRNO
|
|
, _("Directory specified: %s"),filedir.c_str());
|
|
d = opendir(filedir.c_str());
|
|
if (d != NULL) {
|
|
while ((dir_ent=readdir(d)) != NULL) {
|
|
fileitm.fullnm = filedir;
|
|
fileitm.fullnm += dir_ent->d_name;
|
|
fileitm.filenm = dir_ent->d_name;
|
|
chkloc = fileitm.filenm.find_last_of(".");
|
|
if (chkloc == std::string::npos) {
|
|
fileitm.displaynm = fileitm.filenm;
|
|
} else {
|
|
fileitm.displaynm = fileitm.filenm.substr(0,chkloc);
|
|
}
|
|
retcd = stat(fileitm.fullnm.c_str(), &sbuf);
|
|
if ((sbuf.st_mode & S_IFREG) && (retcd == 0)) {
|
|
filelist.push_back(fileitm);
|
|
}
|
|
}
|
|
} else {
|
|
MOTPLS_LOG(DBG, TYPE_NETCAM, SHOW_ERRNO
|
|
, _("Directory did not open: %s"),filedir.c_str());
|
|
}
|
|
closedir(d);
|
|
std::sort(filelist.begin()
|
|
, filelist.end()
|
|
, netcam_filelist_cmp);
|
|
}
|
|
filenbr = 0;
|
|
}
|
|
if (filelist.size() == 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("Directory/file not found: %s"), filedir.c_str());
|
|
} else {
|
|
path = filelist[(uint)filenbr].fullnm;
|
|
}
|
|
MOTPLS_LOG(DBG, TYPE_NETCAM, NO_ERRNO
|
|
, _("Netcam Path: %s"),path.c_str());
|
|
|
|
}
|
|
|
|
void cls_netcam::check_buffsize(netcam_buff_ptr buff, size_t numbytes)
|
|
{
|
|
int min_size_to_alloc;
|
|
int real_alloc;
|
|
uint new_size;
|
|
|
|
if ((buff->size - buff->used) >= numbytes) {
|
|
return;
|
|
}
|
|
|
|
min_size_to_alloc = (int)(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 = (uint)buff->size + (uint)real_alloc;
|
|
|
|
buff->ptr =(char*) myrealloc(buff->ptr, new_size,"check_buf_size");
|
|
buff->size = new_size;
|
|
}
|
|
|
|
char *cls_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 (len > 0) {
|
|
if ((match =(char*) mymalloc(uint(len + 1))) != NULL) {
|
|
strncpy(match, input + m.rm_so, (uint)len);
|
|
match[len] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
void cls_netcam::url_invalid(ctx_url *parse_url)
|
|
{
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("Invalid URL. Can not parse values."));
|
|
|
|
parse_url->port = 0;
|
|
parse_url->host = "????";
|
|
parse_url->service = "????";
|
|
parse_url->path = "INVALID";
|
|
parse_url->userpass = "INVALID";
|
|
|
|
}
|
|
|
|
void cls_netcam::url_parse(ctx_url *parse_url, std::string text_url)
|
|
{
|
|
char *s;
|
|
int i, retcd;
|
|
std::string regstr;
|
|
|
|
regex_t pattbuf;
|
|
regmatch_t matches[10];
|
|
|
|
if (text_url.substr(0,4) == "file") {
|
|
regstr = "(file)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))";
|
|
} else if (text_url.substr(0,4) == "v4l2") {
|
|
regstr = "(v4l2)://(((.*):(.*))@)?([/:])?(:([0-9]+))?($|(/[^*]*))";
|
|
} else {
|
|
regstr = "(.*)://(((.*):(.*))@)?"
|
|
"([^/:]|[-_.a-z0-9]+)(:([0-9]+))?($|(/[^*]*))";
|
|
}
|
|
|
|
parse_url->host = "";
|
|
parse_url->path = "";
|
|
parse_url->port = 0;
|
|
parse_url->service = "";
|
|
parse_url->userpass = "";
|
|
|
|
retcd = regcomp(&pattbuf, regstr.c_str(), REG_EXTENDED | REG_ICASE);
|
|
if (retcd != 0) {
|
|
url_invalid(parse_url);
|
|
return;
|
|
}
|
|
|
|
retcd = regexec(&pattbuf, text_url.c_str(), 10, matches, 0);
|
|
if (retcd == REG_NOMATCH) {
|
|
regfree(&pattbuf);
|
|
url_invalid(parse_url);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if ((s = url_match(matches[i], text_url.c_str())) != NULL) {
|
|
//MOTPLS_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);
|
|
break;
|
|
case 9:
|
|
parse_url->path = s;
|
|
break;
|
|
/* Other components ignored */
|
|
default:
|
|
break;
|
|
}
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
if (((parse_url->port == 0) && (parse_url->service != "")) ||
|
|
((parse_url->port > 65535) && (parse_url->service!= ""))) {
|
|
if (parse_url->service == "http") {
|
|
parse_url->port = 80;
|
|
} else if (parse_url->service == "https") {
|
|
parse_url->port = 443;
|
|
} else if (parse_url->service == "ftp") {
|
|
parse_url->port = 21;
|
|
} else if (parse_url->service == "rtmp") {
|
|
parse_url->port = 1935;
|
|
} else if (parse_url->service == "rtsp") {
|
|
parse_url->port = 554;
|
|
}
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("Using port number %d"), parse_url->port);
|
|
}
|
|
|
|
regfree(&pattbuf);
|
|
}
|
|
|
|
void cls_netcam::free_pkt()
|
|
{
|
|
av_packet_free(&packet_recv);
|
|
packet_recv = nullptr;
|
|
}
|
|
|
|
int cls_netcam::check_pixfmt()
|
|
{
|
|
int retcd = -1;
|
|
if (((enum AVPixelFormat)frame->format == MY_PIX_FMT_YUV420P) ||
|
|
((enum AVPixelFormat)frame->format == MY_PIX_FMT_YUVJ420P)) {
|
|
retcd = 0;
|
|
}
|
|
return retcd;
|
|
}
|
|
|
|
void cls_netcam::pktarray_free()
|
|
{
|
|
int indx;
|
|
pthread_mutex_lock(&mutex_pktarray);
|
|
if (pktarray_size > 0) {
|
|
for(indx = 0; indx < pktarray_size; indx++) {
|
|
av_packet_free(&pktarray[indx].packet);
|
|
pktarray[indx].packet = NULL;
|
|
}
|
|
}
|
|
myfree(pktarray);
|
|
pktarray_size = 0;
|
|
pktarray_index = -1;
|
|
pthread_mutex_unlock(&mutex_pktarray);
|
|
}
|
|
|
|
void cls_netcam::context_null()
|
|
{
|
|
swsctx = nullptr;
|
|
swsframe_in = nullptr;
|
|
swsframe_out = nullptr;
|
|
frame = nullptr;
|
|
codec_context = nullptr;
|
|
format_context = nullptr;
|
|
transfer_format = nullptr;
|
|
hw_device_ctx = nullptr;
|
|
}
|
|
|
|
void cls_netcam::context_close()
|
|
{
|
|
if (swsctx != nullptr) sws_freeContext(swsctx);
|
|
if (swsframe_in != nullptr) av_frame_free(&swsframe_in);
|
|
if (swsframe_out != nullptr) av_frame_free(&swsframe_out);
|
|
if (frame != nullptr) av_frame_free(&frame);
|
|
if (pktarray != nullptr) pktarray_free();
|
|
if (codec_context != nullptr) avcodec_free_context(&codec_context);
|
|
if (format_context != nullptr) avformat_close_input(&format_context);
|
|
if (transfer_format != nullptr) avformat_close_input(&transfer_format);
|
|
if (hw_device_ctx != nullptr) av_buffer_unref(&hw_device_ctx);
|
|
context_null();
|
|
}
|
|
|
|
void cls_netcam::pktarray_resize()
|
|
{
|
|
/* This is called from next and is on the motion loop thread
|
|
* The 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_packet_item *tmp;
|
|
int newsize;
|
|
|
|
if (high_resolution) {
|
|
idnbr_last = cam->imgs.image_ring[cam->imgs.ring_out].idnbr_high;
|
|
idnbr_first = cam->imgs.image_ring[cam->imgs.ring_in].idnbr_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;
|
|
}
|
|
|
|
if (!passthrough) {
|
|
return;
|
|
}
|
|
|
|
/* The 30 is arbitrary */
|
|
/* Double the size plus double last diff so we don't catch our tail */
|
|
newsize =(int)(((idnbr_first - idnbr_last) * 1 ) +
|
|
((idnbr - idnbr_last ) * 2));
|
|
if (newsize < 30) {
|
|
newsize = 30;
|
|
}
|
|
|
|
pthread_mutex_lock(&mutex_pktarray);
|
|
if ((pktarray_size < newsize) || (pktarray_size < 30)) {
|
|
tmp =(ctx_packet_item*) mymalloc((uint)newsize * sizeof(ctx_packet_item));
|
|
if (pktarray_size > 0 ) {
|
|
memcpy(tmp, pktarray, sizeof(ctx_packet_item) * (uint)pktarray_size);
|
|
}
|
|
for(indx = pktarray_size; indx < newsize; indx++) {
|
|
tmp[indx].packet = nullptr;
|
|
tmp[indx].packet = mypacket_alloc(tmp[indx].packet);
|
|
tmp[indx].idnbr = 0;
|
|
tmp[indx].iskey = false;
|
|
tmp[indx].iswritten = false;
|
|
}
|
|
|
|
myfree(pktarray);
|
|
pktarray = tmp;
|
|
pktarray_size = newsize;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Resized packet array to %d")
|
|
, cameratype.c_str(), newsize);
|
|
}
|
|
pthread_mutex_unlock(&mutex_pktarray);
|
|
}
|
|
|
|
void cls_netcam::pktarray_add()
|
|
{
|
|
int indx_next;
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
pthread_mutex_lock(&mutex_pktarray);
|
|
|
|
if (pktarray_size == 0) {
|
|
pthread_mutex_unlock(&mutex_pktarray);
|
|
return;
|
|
}
|
|
|
|
/* Recall pktarray_size is one based but pktarray is zero based */
|
|
if (pktarray_index == (pktarray_size-1)) {
|
|
indx_next = 0;
|
|
} else {
|
|
indx_next = pktarray_index + 1;
|
|
}
|
|
|
|
pktarray[indx_next].idnbr = idnbr;
|
|
|
|
av_packet_free(&pktarray[indx_next].packet);
|
|
pktarray[indx_next].packet = nullptr;
|
|
pktarray[indx_next].packet = mypacket_alloc(pktarray[indx_next].packet);
|
|
|
|
retcd = av_packet_ref(pktarray[indx_next].packet, packet_recv);
|
|
if ((interrupted) || (retcd < 0)) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:av_copy_packet:%s ,Interrupt:%s")
|
|
,cameratype.c_str()
|
|
,errstr, interrupted ? _("true"):_("false"));
|
|
av_packet_free(&pktarray[indx_next].packet);
|
|
pktarray[indx_next].packet = nullptr;
|
|
}
|
|
|
|
if (pktarray[indx_next].packet->flags & AV_PKT_FLAG_KEY) {
|
|
pktarray[indx_next].iskey = true;
|
|
} else {
|
|
pktarray[indx_next].iskey = false;
|
|
}
|
|
pktarray[indx_next].iswritten = false;
|
|
|
|
pktarray_index = indx_next;
|
|
pthread_mutex_unlock(&mutex_pktarray);
|
|
}
|
|
|
|
int cls_netcam::decode_sw()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
retcd = avcodec_receive_frame(codec_context, frame);
|
|
if ((interrupted) || (handler_stop) || (retcd < 0)) {
|
|
if (retcd == AVERROR(EAGAIN)) {
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Ignoring packet with invalid data")
|
|
,cameratype.c_str());
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Rec frame error:%s")
|
|
,cameratype.c_str(), errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
return retcd;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int cls_netcam::decode_vaapi()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
AVFrame *hw_frame = nullptr;
|
|
|
|
hw_frame = av_frame_alloc();
|
|
|
|
retcd = av_hwframe_get_buffer(codec_context->hw_frames_ctx, hw_frame, 0);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Error getting hw frame buffer")
|
|
, cameratype.c_str());
|
|
av_frame_free(&hw_frame);
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_receive_frame(codec_context, hw_frame);
|
|
if ((interrupted) || (handler_stop) || (retcd < 0)) {
|
|
if (retcd == AVERROR(EAGAIN)) {
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Ignoring packet with invalid data")
|
|
,cameratype.c_str());
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Rec frame error:%s")
|
|
,cameratype.c_str(), errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
av_frame_free(&hw_frame);
|
|
return retcd;
|
|
}
|
|
|
|
retcd = av_hwframe_transfer_data(frame, hw_frame, 0);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error transferring HW decoded to system memory")
|
|
,cameratype.c_str());
|
|
av_frame_free(&hw_frame);
|
|
return -1;
|
|
}
|
|
av_frame_free(&hw_frame);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int cls_netcam::decode_cuda()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
AVFrame *hw_frame = nullptr;
|
|
|
|
hw_frame = av_frame_alloc();
|
|
|
|
retcd = avcodec_receive_frame(codec_context, hw_frame);
|
|
if ((interrupted) || (handler_stop) || (retcd < 0) ){
|
|
if (retcd == AVERROR(EAGAIN)){
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Ignoring packet with invalid data")
|
|
,cameratype.c_str());
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Rec frame error:%s")
|
|
,cameratype.c_str(), errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
av_frame_free(&hw_frame);
|
|
return retcd;
|
|
}
|
|
frame->format=AV_PIX_FMT_NV12;
|
|
|
|
retcd = av_hwframe_transfer_data(frame, hw_frame, 0);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error transferring HW decoded to system memory")
|
|
,cameratype.c_str());
|
|
av_frame_free(&hw_frame);
|
|
return -1;
|
|
}
|
|
|
|
av_frame_free(&hw_frame);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int cls_netcam::decode_drm()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
AVFrame *hw_frame = nullptr;
|
|
|
|
hw_frame = av_frame_alloc();
|
|
|
|
retcd = avcodec_receive_frame(codec_context, hw_frame);
|
|
if ((interrupted) || (handler_stop) || (retcd < 0) ){
|
|
if (retcd == AVERROR(EAGAIN)){
|
|
retcd = 0;
|
|
} else if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Ignoring packet with invalid data")
|
|
,cameratype.c_str());
|
|
retcd = 0;
|
|
} else if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Rec frame error:%s")
|
|
,cameratype.c_str(), errstr);
|
|
retcd = -1;
|
|
} else {
|
|
retcd = -1;
|
|
}
|
|
av_frame_free(&hw_frame);
|
|
return retcd;
|
|
}
|
|
frame->format=AV_PIX_FMT_NV12;
|
|
|
|
retcd = av_hwframe_transfer_data(frame, hw_frame, 0);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error transferring HW decoded to system memory")
|
|
,cameratype.c_str());
|
|
av_frame_free(&hw_frame);
|
|
return -1;
|
|
}
|
|
|
|
av_frame_free(&hw_frame);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int cls_netcam::decode_video()
|
|
{
|
|
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 (handler_stop) {
|
|
return 0;
|
|
}
|
|
|
|
retcd = avcodec_send_packet(codec_context, packet_recv);
|
|
if ((interrupted) || (handler_stop)) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Interrupted or handler_stop on send")
|
|
,cameratype.c_str());
|
|
return -1;
|
|
}
|
|
if (retcd == AVERROR_INVALIDDATA) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Send ignoring packet with invalid data")
|
|
,cameratype.c_str());
|
|
return 0;
|
|
}
|
|
if (retcd < 0 && retcd != AVERROR_EOF) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error sending packet to codec:%s")
|
|
,cameratype.c_str(), errstr);
|
|
if (service == "file") {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (hw_type == AV_HWDEVICE_TYPE_VAAPI) {
|
|
retcd = decode_vaapi();
|
|
} else if (hw_type == AV_HWDEVICE_TYPE_CUDA) {
|
|
retcd = decode_cuda();
|
|
} else if (hw_type == AV_HWDEVICE_TYPE_DRM) {
|
|
retcd = decode_drm();
|
|
} else {
|
|
retcd = decode_sw();
|
|
}
|
|
|
|
return retcd;
|
|
}
|
|
|
|
int cls_netcam::decode_packet()
|
|
{
|
|
int frame_size;
|
|
int retcd;
|
|
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
if (packet_recv->stream_index == audio_stream_index) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error decoding video packet...it is audio")
|
|
,cameratype.c_str());
|
|
}
|
|
|
|
retcd = decode_video();
|
|
if (retcd <= 0) {
|
|
return retcd;
|
|
}
|
|
frame_size = av_image_get_buffer_size(
|
|
(enum AVPixelFormat) frame->format
|
|
, frame->width, frame->height, 1);
|
|
|
|
check_buffsize(img_recv, (uint)frame_size);
|
|
check_buffsize(img_latest, (uint)frame_size);
|
|
|
|
retcd = av_image_copy_to_buffer(
|
|
(uint8_t *)img_recv->ptr
|
|
, frame_size
|
|
, (const uint8_t * const*)frame
|
|
, frame->linesize
|
|
, (enum AVPixelFormat)frame->format
|
|
, frame->width, frame->height, 1);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error decoding video packet:Copying to buffer")
|
|
,cameratype.c_str());
|
|
return -1;
|
|
}
|
|
|
|
img_recv->used = (uint)frame_size;
|
|
|
|
return frame_size;
|
|
}
|
|
|
|
void cls_netcam::hwdecoders()
|
|
{
|
|
/* High Res pass through does not decode images into frames*/
|
|
if (high_resolution && passthrough) {
|
|
return;
|
|
}
|
|
if ((hw_type == AV_HWDEVICE_TYPE_NONE) && (first_image)) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:HW Devices:")
|
|
, cameratype.c_str());
|
|
while((hw_type = av_hwdevice_iterate_types(hw_type)) != AV_HWDEVICE_TYPE_NONE){
|
|
if ((hw_type == AV_HWDEVICE_TYPE_VAAPI) ||
|
|
(hw_type == AV_HWDEVICE_TYPE_CUDA) ||
|
|
(hw_type == AV_HWDEVICE_TYPE_DRM)) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s(available)")
|
|
, cameratype.c_str()
|
|
, av_hwdevice_get_type_name(hw_type));
|
|
} else {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: %s(not implemented)")
|
|
, cameratype.c_str()
|
|
, av_hwdevice_get_type_name(hw_type));
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void cls_netcam::decoder_error(int retcd, const char* fnc_nm)
|
|
{
|
|
char errstr[128];
|
|
p_lst *lst = ¶ms->params_array;
|
|
p_it it;
|
|
|
|
if (interrupted) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Interrupted"),cameratype.c_str());
|
|
} else {
|
|
if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:%s:%s"),cameratype.c_str()
|
|
,fnc_nm, errstr);
|
|
} else {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:%s:Failed"), cameratype.c_str()
|
|
,fnc_nm);
|
|
}
|
|
}
|
|
|
|
if (decoder_nm != "NULL") {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Decoder %s did not work.")
|
|
,cameratype.c_str(), decoder_nm.c_str());
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Ignoring and removing the user requested decoder %s")
|
|
,cameratype.c_str(), decoder_nm.c_str());
|
|
|
|
for (it = lst->begin(); it != lst->end(); it++) {
|
|
if (it->param_name == "decoder") {
|
|
it->param_value = "NULL";
|
|
break;
|
|
}
|
|
}
|
|
|
|
util_parms_update(params, cfg_params);
|
|
|
|
decoder_nm = "NULL";
|
|
}
|
|
}
|
|
|
|
int cls_netcam::init_vaapi()
|
|
{
|
|
int retcd, indx;
|
|
AVPixelFormat *pixelformats = nullptr;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Initializing vaapi decoder")
|
|
,cameratype.c_str());
|
|
|
|
hw_type = av_hwdevice_find_type_by_name("vaapi");
|
|
if (hw_type == AV_HWDEVICE_TYPE_NONE) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("%s:Unable to find vaapi hw device")
|
|
, cameratype.c_str());
|
|
decoder_error(0, "av_hwdevice");
|
|
return -1;
|
|
}
|
|
|
|
codec_context = avcodec_alloc_context3(decoder);
|
|
if ((codec_context == nullptr) || (interrupted)) {
|
|
decoder_error(0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(codec_context,strm->codecpar);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
hw_pix_fmt = AV_PIX_FMT_VAAPI;
|
|
codec_context->get_format = netcam_getfmt_vaapi;
|
|
av_opt_set_int(codec_context, "refcounted_frames", 1, 0);
|
|
codec_context->sw_pix_fmt = AV_PIX_FMT_YUV420P;
|
|
codec_context->pix_fmt= AV_PIX_FMT_YUV420P;
|
|
codec_context->hwaccel_flags=
|
|
AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH |
|
|
AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
|
|
|
retcd = av_hwdevice_ctx_create(&hw_device_ctx, hw_type, NULL, NULL, 0);
|
|
if (retcd < 0) {
|
|
decoder_error(retcd, "hwctx");
|
|
return -1;
|
|
}
|
|
|
|
codec_context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
|
|
|
AVBufferRef *hw_frames_ref = nullptr;
|
|
AVHWFramesContext *frames_ctx = nullptr;
|
|
|
|
hw_frames_ref = av_hwframe_ctx_alloc(
|
|
codec_context->hw_device_ctx);
|
|
if (hw_frames_ref == nullptr) {
|
|
decoder_error(retcd, "initvaapi 2");
|
|
return -1;
|
|
}
|
|
|
|
frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data);
|
|
frames_ctx->format = AV_PIX_FMT_VAAPI;
|
|
frames_ctx->sw_format = AV_PIX_FMT_YUV420P;
|
|
frames_ctx->width = codec_context->width;
|
|
frames_ctx->height = codec_context->height;
|
|
frames_ctx->initial_pool_size = 20;
|
|
retcd = av_hwframe_ctx_init(hw_frames_ref);
|
|
if (retcd < 0) {
|
|
decoder_error(retcd, "initvaapi 2");
|
|
av_buffer_unref(&hw_frames_ref);
|
|
return -1;
|
|
}
|
|
codec_context->hw_frames_ctx = av_buffer_ref(hw_frames_ref);
|
|
if (codec_context->hw_frames_ctx == nullptr) {
|
|
decoder_error(retcd, "initvaapi 2");
|
|
av_buffer_unref(&hw_frames_ref);
|
|
return -1;
|
|
}
|
|
av_buffer_unref(&hw_frames_ref);
|
|
|
|
retcd = av_hwframe_transfer_get_formats(
|
|
codec_context->hw_frames_ctx
|
|
, AV_HWFRAME_TRANSFER_DIRECTION_FROM
|
|
, &pixelformats
|
|
, 0);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error enumerating HW pixel types")
|
|
,cameratype.c_str());
|
|
decoder_error(retcd, "initvaapi 3");
|
|
return -1;
|
|
}
|
|
/* Note that while these are listed as available, there
|
|
is currently no way to get them from HW decoder without
|
|
triggering a segfault in vaapi driver.
|
|
*/
|
|
const AVPixFmtDescriptor *descr;
|
|
for (indx=0; pixelformats[indx] != AV_PIX_FMT_NONE; indx++) {
|
|
descr = av_pix_fmt_desc_get(pixelformats[indx]);
|
|
MOTPLS_LOG(DBG, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Available HW pixel type:%s")
|
|
, cameratype.c_str()
|
|
,descr->name);
|
|
}
|
|
av_freep(&pixelformats);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::init_cuda()
|
|
{
|
|
int retcd;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Initializing cuda decoder"),cameratype.c_str());
|
|
|
|
hw_type = av_hwdevice_find_type_by_name("cuda");
|
|
if (hw_type == AV_HWDEVICE_TYPE_NONE){
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("%s:Unable to find cuda hw device")
|
|
, cameratype.c_str());
|
|
decoder_error(0, "av_hwdevice");
|
|
return -1;
|
|
}
|
|
|
|
codec_context = avcodec_alloc_context3(decoder);
|
|
if ((codec_context == nullptr) || (interrupted)){
|
|
decoder_error(0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(codec_context,strm->codecpar);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
hw_pix_fmt = AV_PIX_FMT_CUDA;
|
|
codec_context->get_format = netcam_getfmt_cuda;
|
|
av_opt_set_int(codec_context, "refcounted_frames", 1, 0);
|
|
codec_context->sw_pix_fmt = AV_PIX_FMT_YUV420P;
|
|
codec_context->hwaccel_flags=
|
|
AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH |
|
|
AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
|
|
|
retcd = av_hwdevice_ctx_create(&hw_device_ctx, hw_type, NULL, NULL, 0);
|
|
if (retcd < 0){
|
|
decoder_error(retcd, "hwctx");
|
|
return -1;
|
|
}
|
|
codec_context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::init_drm()
|
|
{
|
|
int retcd;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Initializing drm decoder")
|
|
,cameratype.c_str());
|
|
|
|
hw_type = av_hwdevice_find_type_by_name("drm");
|
|
if (hw_type == AV_HWDEVICE_TYPE_NONE){
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO,_("%s:Unable to find drm hw device")
|
|
, cameratype.c_str());
|
|
decoder_error(0, "av_hwdevice");
|
|
return -1;
|
|
}
|
|
|
|
codec_context = avcodec_alloc_context3(decoder);
|
|
if ((codec_context == nullptr) || (interrupted)){
|
|
decoder_error(0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(codec_context,strm->codecpar);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
hw_pix_fmt = AV_PIX_FMT_DRM_PRIME;
|
|
codec_context->get_format = netcam_getfmt_drm;
|
|
av_opt_set_int(codec_context, "refcounted_frames", 1, 0);
|
|
codec_context->sw_pix_fmt = AV_PIX_FMT_YUV420P;
|
|
codec_context->hwaccel_flags=
|
|
AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH |
|
|
AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
|
|
|
retcd = av_hwdevice_ctx_create(&hw_device_ctx, hw_type, NULL, NULL, 0);
|
|
if (retcd < 0){
|
|
decoder_error(retcd, "hwctx");
|
|
return -1;
|
|
}
|
|
codec_context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::init_swdecoder()
|
|
{
|
|
int retcd;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Initializing decoder"),cameratype.c_str());
|
|
|
|
if (decoder_nm != "NULL") {
|
|
decoder = avcodec_find_decoder_by_name(
|
|
decoder_nm.c_str());
|
|
if (decoder == nullptr) {
|
|
decoder_error(0
|
|
, "avcodec_find_decoder_by_name");
|
|
} else {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Using decoder %s")
|
|
, cameratype.c_str()
|
|
, decoder_nm.c_str());
|
|
}
|
|
}
|
|
if (decoder == nullptr) {
|
|
decoder = avcodec_find_decoder(strm->codecpar->codec_id);
|
|
}
|
|
if ((decoder == nullptr) || (interrupted)) {
|
|
decoder_error(0, "avcodec_find_decoder");
|
|
return -1;
|
|
}
|
|
|
|
codec_context = avcodec_alloc_context3(decoder);
|
|
if ((codec_context == nullptr) || (interrupted)) {
|
|
decoder_error(0, "avcodec_alloc_context3");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_parameters_to_context(codec_context, strm->codecpar);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "avcodec_parameters_to_context");
|
|
return -1;
|
|
}
|
|
|
|
codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
|
|
codec_context->err_recognition = AV_EF_IGNORE_ERR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::open_codec()
|
|
{
|
|
int retcd;
|
|
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
hwdecoders();
|
|
|
|
retcd = av_find_best_stream(format_context
|
|
, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
audio_stream_index = -1;
|
|
} else {
|
|
audio_stream_index = retcd;
|
|
}
|
|
|
|
decoder = nullptr;
|
|
retcd = av_find_best_stream(format_context
|
|
, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "av_find_best_stream");
|
|
return -1;
|
|
}
|
|
video_stream_index = retcd;
|
|
strm = format_context->streams[video_stream_index];
|
|
|
|
if (decoder_nm == "vaapi") {
|
|
if (init_vaapi() < 0) {
|
|
decoder_error(retcd, "hwvaapi_init");
|
|
return -1;
|
|
}
|
|
} else if (decoder_nm == "cuda"){
|
|
if (init_cuda() <0 ) {
|
|
decoder_error(retcd, "hwcuda_init");
|
|
return -1;
|
|
}
|
|
} else if (decoder_nm == "drm"){
|
|
if (init_drm() < 0) {;
|
|
decoder_error(retcd, "hwdrm_init");
|
|
return -1;
|
|
}
|
|
} else {
|
|
init_swdecoder();
|
|
}
|
|
|
|
retcd = avcodec_open2(codec_context, decoder, nullptr);
|
|
if ((retcd < 0) || (interrupted)) {
|
|
decoder_error(retcd, "avcodec_open2");
|
|
return -1;
|
|
}
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Decoder opened"),cameratype.c_str());
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::open_sws()
|
|
{
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
swsframe_in = av_frame_alloc();
|
|
if (swsframe_in == nullptr) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Unable to allocate swsframe_in.")
|
|
, cameratype.c_str());
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
swsframe_out = av_frame_alloc();
|
|
if (swsframe_out == nullptr) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Unable to allocate swsframe_out.")
|
|
, cameratype.c_str());
|
|
}
|
|
context_close();
|
|
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 (check_pixfmt() != 0) {
|
|
const AVPixFmtDescriptor *descr;
|
|
descr = av_pix_fmt_desc_get((AVPixelFormat)frame->format);
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Pixel format %s will be converted.")
|
|
, cameratype.c_str(), descr->name);
|
|
}
|
|
|
|
swsctx = sws_getContext(
|
|
frame->width
|
|
,frame->height
|
|
,(enum AVPixelFormat)frame->format
|
|
,imgsize.width
|
|
,imgsize.height
|
|
,MY_PIX_FMT_YUV420P
|
|
,SWS_BICUBIC,nullptr,nullptr,nullptr);
|
|
if (swsctx == nullptr) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Unable to allocate scaling context.")
|
|
, cameratype.c_str());
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
swsframe_size = av_image_get_buffer_size(
|
|
MY_PIX_FMT_YUV420P, imgsize.width, imgsize.height, 1);
|
|
if (swsframe_size <= 0) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Error determining size of frame out")
|
|
, cameratype.c_str());
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
/* the image buffers must be big enough to hold the final frame after resizing */
|
|
check_buffsize(img_recv, (uint)swsframe_size);
|
|
check_buffsize(img_latest, (uint)swsframe_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::resize()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
uint8_t *buffer_out;
|
|
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
if (swsctx == nullptr) {
|
|
if (open_sws() < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
retcd = av_image_fill_arrays(
|
|
swsframe_in->data
|
|
, swsframe_in->linesize
|
|
, (uint8_t*)img_recv->ptr
|
|
, (enum AVPixelFormat)frame->format
|
|
, frame->width, frame->height, 1);
|
|
if (retcd < 0) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error allocating picture in:%s")
|
|
, cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
buffer_out=(uint8_t *)av_malloc((uint)swsframe_size*sizeof(uint8_t));
|
|
|
|
retcd = av_image_fill_arrays(
|
|
swsframe_out->data
|
|
, swsframe_out->linesize
|
|
, buffer_out, MY_PIX_FMT_YUV420P
|
|
, imgsize.width, imgsize.height, 1);
|
|
if (retcd < 0) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error allocating picture out:%s")
|
|
, cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
retcd = sws_scale(
|
|
swsctx
|
|
,(const uint8_t* const *)swsframe_in->data
|
|
,swsframe_in->linesize
|
|
,0
|
|
,frame->height
|
|
,swsframe_out->data
|
|
,swsframe_out->linesize);
|
|
if (retcd < 0) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error resizing/reformatting:%s")
|
|
, cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
retcd = av_image_copy_to_buffer(
|
|
(uint8_t *)img_recv->ptr
|
|
, swsframe_size
|
|
, (const uint8_t * const*)swsframe_out
|
|
, swsframe_out->linesize
|
|
, MY_PIX_FMT_YUV420P
|
|
, imgsize.width, imgsize.height, 1);
|
|
if (retcd < 0) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Error putting frame into output buffer:%s")
|
|
, cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
img_recv->used = (uint)swsframe_size;
|
|
|
|
av_free(buffer_out);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::read_image()
|
|
{
|
|
int size_decoded, retcd, errcnt, nodata;
|
|
bool haveimage;
|
|
char errstr[128];
|
|
netcam_buff *xchg;
|
|
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
packet_recv = mypacket_alloc(packet_recv);
|
|
|
|
interrupted=false;
|
|
clock_gettime(CLOCK_MONOTONIC, &ist_tm);
|
|
idur = 10;
|
|
|
|
status = NETCAM_READINGIMAGE;
|
|
img_recv->used = 0;
|
|
size_decoded = 0;
|
|
errcnt = 0;
|
|
haveimage = false;
|
|
nodata = 0;
|
|
|
|
while ((!haveimage) && (!interrupted)) {
|
|
retcd = av_read_frame(format_context, packet_recv);
|
|
if (retcd < 0 ) {
|
|
errcnt++;
|
|
}
|
|
if ((interrupted) || (errcnt > 1)) {
|
|
if (interrupted) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Interrupted")
|
|
,cameratype.c_str());
|
|
} else {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:av_read_frame:%s" )
|
|
,cameratype.c_str(),errstr);
|
|
}
|
|
free_pkt();
|
|
context_close();
|
|
return -1;
|
|
} else {
|
|
errcnt = 0;
|
|
if ((packet_recv->stream_index == video_stream_index) ||
|
|
(packet_recv->stream_index == audio_stream_index)) {
|
|
/* For a high resolution pass-through we don't decode the image */
|
|
if ((high_resolution && passthrough) ||
|
|
(packet_recv->stream_index != video_stream_index)) {
|
|
if (packet_recv->data != nullptr) {
|
|
size_decoded = 1;
|
|
}
|
|
} else {
|
|
size_decoded = decode_packet();
|
|
}
|
|
}
|
|
if (size_decoded > 0) {
|
|
haveimage = true;
|
|
} else if (size_decoded == 0) {
|
|
/* Did not fail, just didn't get anything. Try again */
|
|
free_pkt();
|
|
packet_recv = mypacket_alloc(packet_recv);
|
|
|
|
/* The 1000 is arbitrary */
|
|
nodata++;
|
|
if (nodata > 1000) {
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
free_pkt();
|
|
context_close();
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, &img_recv->image_time);
|
|
last_stream_index = packet_recv->stream_index;
|
|
last_pts = packet_recv->pts;
|
|
|
|
if (!first_image) {
|
|
status = NETCAM_CONNECTED;
|
|
}
|
|
|
|
/* Skip resize/pix format for high pass-through */
|
|
if (!(high_resolution && passthrough) &&
|
|
(packet_recv->stream_index == video_stream_index)) {
|
|
|
|
if ((imgsize.width != frame->width) ||
|
|
(imgsize.height != frame->height) ||
|
|
(check_pixfmt() != 0)) {
|
|
if (resize() < 0) {
|
|
free_pkt();
|
|
context_close();
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
pthread_mutex_lock(&mutex);
|
|
idnbr++;
|
|
if (passthrough) {
|
|
pktarray_add();
|
|
}
|
|
if (!(high_resolution && passthrough) &&
|
|
(packet_recv->stream_index == video_stream_index)) {
|
|
xchg = img_latest;
|
|
img_latest = img_recv;
|
|
img_recv = xchg;
|
|
}
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
free_pkt();
|
|
|
|
if (format_context->streams[video_stream_index]->avg_frame_rate.den > 0) {
|
|
src_fps = (int)(
|
|
(format_context->streams[video_stream_index]->avg_frame_rate.num /
|
|
format_context->streams[video_stream_index]->avg_frame_rate.den) +
|
|
0.5);
|
|
if (capture_rate < 1) {
|
|
capture_rate = src_fps + 1;
|
|
if (pts_adj == false) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:capture_rate not specified in params. Using %d")
|
|
,cameratype.c_str(),capture_rate);
|
|
}
|
|
}
|
|
} else {
|
|
if (capture_rate < 1) {
|
|
capture_rate = cfg_framerate;
|
|
if (pts_adj == false) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:capture_rate not specified.")
|
|
,cameratype.c_str());
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Using framerate %d")
|
|
,cameratype.c_str(), capture_rate);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::ntc()
|
|
{
|
|
if ((handler_stop) || (!first_image)) {
|
|
return 0;
|
|
}
|
|
|
|
/* High Res pass through does not decode images into frames*/
|
|
if (high_resolution && passthrough) {
|
|
return 0;
|
|
}
|
|
|
|
if ((imgsize.width == 0) || (imgsize.height == 0) ||
|
|
(frame->width == 0) || (frame->height == 0)){
|
|
return 0;
|
|
}
|
|
|
|
if ((imgsize.width != frame->width) ||
|
|
(imgsize.height != frame->height)) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "");
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************");
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("The network camera is sending pictures at %dx%d")
|
|
, frame->width,frame->height);
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("resolution but config is %dx%d. If possible change")
|
|
, imgsize.width,imgsize.height);
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("the netcam or config so that the image height and"));
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, _("width are the same to lower the CPU usage."));
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "******************************************************");
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO, "");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cls_netcam::set_options()
|
|
{
|
|
std::string tmp;
|
|
p_lst *lst = ¶ms->params_array;
|
|
p_it it;
|
|
|
|
if ((service == "rtsp") ||
|
|
(service == "rtsps") ||
|
|
(service == "rtmp")) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s:Setting rtsp/rtmp")
|
|
,cameratype.c_str());
|
|
util_parms_add_default(params,"rtsp_transport","tcp");
|
|
//util_parms_add_default(params,"allowed_media_types", "video");
|
|
|
|
} else if ((service == "http") ||
|
|
(service == "https")) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Setting input_format mjpeg")
|
|
,cameratype.c_str());
|
|
format_context->iformat = av_find_input_format("mjpeg");
|
|
util_parms_add_default(params,"reconnect_on_network_error","1");
|
|
util_parms_add_default(params,"reconnect_at_eof","1");
|
|
util_parms_add_default(params,"reconnect","1");
|
|
util_parms_add_default(params,"multiple_requests","1");
|
|
util_parms_add_default(params,"reconnect_streamed","1");
|
|
|
|
} else if (service == "v4l2") {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Setting input_format video4linux2")
|
|
,cameratype.c_str());
|
|
format_context->iformat = av_find_input_format("video4linux2");
|
|
|
|
tmp = std::to_string(cfg_framerate);
|
|
util_parms_add_default(params,"framerate", tmp);
|
|
|
|
tmp = std::to_string(cfg_width) + "x" +
|
|
std::to_string(cfg_height);
|
|
util_parms_add_default(params,"video_size", tmp);
|
|
|
|
idur = 55;
|
|
|
|
} else if (service == "file") {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Setting up movie file")
|
|
,cameratype.c_str());
|
|
|
|
} else {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s:Setting up %s")
|
|
, cameratype.c_str()
|
|
, service.c_str());
|
|
}
|
|
|
|
for (it = lst->begin(); it != lst->end(); it++) {
|
|
if ((it->param_name != "decoder") &&
|
|
(it->param_name != "capture_rate")) {
|
|
av_dict_set(&opts
|
|
, it->param_name.c_str(), it->param_value.c_str(), 0);
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO,_("%s:%s = %s")
|
|
,cameratype.c_str()
|
|
,it->param_name.c_str(),it->param_value.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cls_netcam::set_path ()
|
|
{
|
|
ctx_url url;
|
|
|
|
path = "";
|
|
|
|
if (high_resolution) {
|
|
url_parse(&url, cam->cfg->netcam_high_url);
|
|
} else {
|
|
url_parse(&url, cam->cfg->netcam_url);
|
|
}
|
|
|
|
if (cam->cfg->netcam_userpass != "") {
|
|
url.userpass = cam->cfg->netcam_userpass;
|
|
}
|
|
|
|
if (url.service == "v4l2") {
|
|
path = url.path;
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up v4l2"));
|
|
} else if (url.service == "file") {
|
|
filedir = url.path;
|
|
filelist_load();
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up file"));
|
|
} else {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("Setting up %s "),url.service.c_str());
|
|
if (url.userpass.length() > 0) {
|
|
path = url.service + "://" +
|
|
url.userpass + "@" + url.host +":"+
|
|
std::to_string(url.port) + url.path;
|
|
} else {
|
|
path = url.service + "://" +
|
|
url.host + ":" + std::to_string(url.port) + url.path;
|
|
}
|
|
}
|
|
|
|
service = url.service;
|
|
}
|
|
|
|
void cls_netcam::set_parms ()
|
|
{
|
|
p_it it;
|
|
|
|
motapp = cam->motapp;
|
|
params = new ctx_params;
|
|
|
|
pthread_mutex_init(&mutex, nullptr);
|
|
pthread_mutex_init(&mutex_pktarray, nullptr);
|
|
pthread_mutex_init(&mutex_transfer, nullptr);
|
|
|
|
context_null();
|
|
threadnbr = cam->device_id;
|
|
cfg_width = cam->cfg->width;
|
|
cfg_height = cam->cfg->height;
|
|
cfg_framerate = cam->cfg->framerate;
|
|
|
|
cam->imgs.width = cfg_width;
|
|
cam->imgs.height = cfg_height;
|
|
cam->imgs.size_norm = (cfg_width * cfg_height * 3) / 2;
|
|
cam->imgs.motionsize = cfg_width * cfg_height;
|
|
|
|
cam->imgs.width_high = 0;
|
|
cam->imgs.height_high = 0;
|
|
cam->imgs.size_high = 0;
|
|
|
|
if (high_resolution) {
|
|
imgsize.width = 0;
|
|
imgsize.height = 0;
|
|
cameratype = _("High");
|
|
cfg_params = cam->cfg->netcam_high_params;
|
|
params->update_params = true;
|
|
util_parms_parse(params
|
|
,"netcam_high_params", cfg_params);
|
|
} else {
|
|
imgsize.width = cfg_width;
|
|
imgsize.height = cfg_height;
|
|
cameratype = _("Norm");
|
|
cfg_params = cam->cfg->netcam_params;
|
|
params->update_params = true;
|
|
util_parms_parse(params
|
|
,"netcam_params", cfg_params);
|
|
}
|
|
camera_name = cam->cfg->device_name;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Setting up camera(%s).")
|
|
, cameratype.c_str(), camera_name.c_str());
|
|
|
|
status = NETCAM_NOTCONNECTED;
|
|
util_parms_add_default(params,"decoder","NULL");
|
|
img_recv =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff));
|
|
img_recv->ptr =(char*) mymalloc(NETCAM_BUFFSIZE);
|
|
img_latest =(netcam_buff_ptr) mymalloc(sizeof(netcam_buff));
|
|
img_latest->ptr =(char*) mymalloc(NETCAM_BUFFSIZE);
|
|
pktarray_size = 0;
|
|
pktarray_index = -1;
|
|
pktarray = nullptr;
|
|
packet_recv = nullptr;
|
|
first_image = true;
|
|
reconnect_count = 0;
|
|
src_fps = -1; /* Default to neg so we know it has not been set */
|
|
pts_adj = false;
|
|
capture_rate = -1;
|
|
video_stream_index = -1;
|
|
audio_stream_index = -1;
|
|
last_stream_index = -1;
|
|
strm = nullptr;
|
|
opts = nullptr;
|
|
decoder = nullptr;
|
|
idnbr = 0;
|
|
swsframe_size = 0;
|
|
hw_type = AV_HWDEVICE_TYPE_NONE;
|
|
hw_pix_fmt = AV_PIX_FMT_NONE;
|
|
connection_pts = 0;
|
|
last_pts = 0;
|
|
filenbr = 0;
|
|
filelist.clear();
|
|
filedir = "";
|
|
|
|
for (it = params->params_array.begin();
|
|
it != params->params_array.end(); it++) {
|
|
if (it->param_name == "decoder") {
|
|
decoder_nm = it->param_value;
|
|
}
|
|
if (it->param_name == "capture_rate") {
|
|
if (it->param_value == "pts") {
|
|
pts_adj = true;
|
|
} else {
|
|
capture_rate = mtoi(it->param_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If this is the norm and we have a highres, then disable passthru on the norm */
|
|
if ((high_resolution == false) &&
|
|
(cam->cfg->netcam_high_url != "")) {
|
|
passthrough = false;
|
|
} else {
|
|
passthrough = cam->movie_passthrough;
|
|
}
|
|
|
|
threadname = _("Unknown");
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &frame_curr_tm);
|
|
clock_gettime(CLOCK_MONOTONIC, &frame_prev_tm);
|
|
clock_gettime(CLOCK_MONOTONIC, &connection_tm);
|
|
clock_gettime(CLOCK_MONOTONIC, &ist_tm);
|
|
clock_gettime(CLOCK_MONOTONIC, &icur_tm);
|
|
|
|
idur = 5;
|
|
interrupted = false;
|
|
|
|
set_path();
|
|
}
|
|
|
|
/* Make a static copy of the stream information for use in passthrough processing */
|
|
int cls_netcam::copy_stream()
|
|
{
|
|
AVStream *transfer_stream, *stream_in;
|
|
int retcd, indx;
|
|
|
|
pthread_mutex_lock(&mutex_transfer);
|
|
if (transfer_format != nullptr) {
|
|
avformat_close_input(&transfer_format);
|
|
}
|
|
transfer_format = avformat_alloc_context();
|
|
for (indx = 0; indx < (int)format_context->nb_streams; indx++) {
|
|
if ((audio_stream_index == indx) ||
|
|
(video_stream_index == indx)) {
|
|
transfer_stream = avformat_new_stream(transfer_format, nullptr);
|
|
stream_in = format_context->streams[indx];
|
|
retcd = avcodec_parameters_copy(transfer_stream->codecpar, stream_in->codecpar);
|
|
if (retcd < 0) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Unable to copy codec parameters")
|
|
, cameratype.c_str());
|
|
pthread_mutex_unlock(&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(&mutex_transfer);
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO, _("Stream copied for pass-through"));
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::open_context()
|
|
{
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
if (handler_stop) {
|
|
return -1;
|
|
}
|
|
|
|
if (path == "") {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO, _("No path passed to connect"));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
opts = nullptr;
|
|
format_context = avformat_alloc_context();
|
|
format_context->interrupt_callback.callback = netcam_interrupt;
|
|
format_context->interrupt_callback.opaque = this;
|
|
interrupted = false;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ist_tm);
|
|
|
|
idur = 20;
|
|
|
|
set_options();
|
|
|
|
retcd = avformat_open_input(&format_context
|
|
, path.c_str(), nullptr, &opts);
|
|
if ((retcd < 0) || (interrupted) || (handler_stop) ) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Unable to open camera(%s):%s")
|
|
, cameratype.c_str()
|
|
, camera_name.c_str(), errstr);
|
|
}
|
|
av_dict_free(&opts);
|
|
context_close();
|
|
return -1;
|
|
}
|
|
av_dict_free(&opts);
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Opened camera(%s)"), cameratype.c_str()
|
|
, camera_name.c_str());
|
|
|
|
|
|
/* fill out stream information */
|
|
retcd = avformat_find_stream_info(format_context, nullptr);
|
|
if ((retcd < 0) || (interrupted) || (handler_stop) ) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Unable to find stream info:%s")
|
|
,cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
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(threadname);
|
|
mythreadname_set("av", threadnbr, camera_name.c_str());
|
|
retcd = open_codec();
|
|
mythreadname_set(nullptr, 0, threadname.c_str());
|
|
if ((retcd < 0) || (interrupted) || (handler_stop) ) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Unable to open codec context:%s")
|
|
,cameratype.c_str(), errstr);
|
|
} else {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Connected and unable to open codec context:%s")
|
|
,cameratype.c_str(), errstr);
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
if (codec_context->width <= 0 ||
|
|
codec_context->height <= 0) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera image size is invalid")
|
|
,cameratype.c_str());
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
if (high_resolution) {
|
|
imgsize.width = codec_context->width;
|
|
imgsize.height = codec_context->height;
|
|
}
|
|
|
|
frame = av_frame_alloc();
|
|
if (frame == nullptr) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Unable to allocate frame.")
|
|
,cameratype.c_str());
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
if (passthrough) {
|
|
retcd = copy_stream();
|
|
if ((retcd < 0) || (interrupted)) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Failed to copy stream for pass-through.")
|
|
,cameratype.c_str());
|
|
}
|
|
passthrough = false;
|
|
}
|
|
}
|
|
|
|
/* Validate that the previous steps opened the camera */
|
|
retcd = read_image();
|
|
if ((retcd < 0) || (interrupted)) {
|
|
if (status == NETCAM_NOTCONNECTED) {
|
|
MOTPLS_LOG(ERR, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Failed to read first image")
|
|
,cameratype.c_str());
|
|
}
|
|
context_close();
|
|
return -1;
|
|
}
|
|
|
|
connection_pts = AV_NOPTS_VALUE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cls_netcam::connect()
|
|
{
|
|
|
|
if (open_context() < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (ntc() < 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
if (read_image() < 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 (!first_image) {
|
|
status = NETCAM_CONNECTED;
|
|
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera (%s) connected")
|
|
, cameratype.c_str(),camera_name.c_str());
|
|
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Netcam capture FPS is %d.")
|
|
, cameratype.c_str(), capture_rate);
|
|
|
|
if (src_fps > 0) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Camera source is %d FPS")
|
|
, cameratype.c_str(), src_fps);
|
|
} else {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Unable to determine the camera source FPS.")
|
|
, cameratype.c_str());
|
|
}
|
|
|
|
if (capture_rate < src_fps) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Capture FPS less than camera FPS. Decoding errors will occur.")
|
|
, cameratype.c_str());
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
, _("%s:Capture FPS should be greater than camera FPS.")
|
|
, cameratype.c_str());
|
|
}
|
|
|
|
if (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
|
|
*/
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:An audio stream was detected. Capture_rate increased to compensate.")
|
|
,cameratype.c_str());
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cls_netcam::handler_wait()
|
|
{
|
|
int64_t usec_ltncy;
|
|
AVRational tbase;
|
|
struct timespec tmp_tm;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tmp_tm);
|
|
|
|
if (capture_rate < 1) {
|
|
capture_rate = 1;
|
|
}
|
|
tbase = format_context->streams[video_stream_index]->time_base;
|
|
if (tbase.num == 0) {
|
|
tbase.num = 1;
|
|
}
|
|
|
|
/* FPS calculation from last frame capture */
|
|
frame_curr_tm = tmp_tm;
|
|
usec_ltncy = (1000000 / capture_rate) -
|
|
((frame_curr_tm.tv_sec - frame_prev_tm.tv_sec) * 1000000) -
|
|
((frame_curr_tm.tv_nsec - frame_prev_tm.tv_nsec)/1000);
|
|
|
|
/* Adjust to clock and pts timer */
|
|
if (pts_adj == true) {
|
|
if (connection_pts == AV_NOPTS_VALUE) {
|
|
connection_pts = last_pts;
|
|
clock_gettime(CLOCK_MONOTONIC, &connection_tm);
|
|
return;
|
|
}
|
|
if (last_pts != AV_NOPTS_VALUE) {
|
|
usec_ltncy +=
|
|
+ (av_rescale(last_pts, 1000000, tbase.den/tbase.num)
|
|
- av_rescale(connection_pts, 1000000, tbase.den/tbase.num))
|
|
-(((tmp_tm.tv_sec - connection_tm.tv_sec) * 1000000) +
|
|
((tmp_tm.tv_nsec - connection_tm.tv_nsec) / 1000));
|
|
}
|
|
}
|
|
|
|
if ((usec_ltncy > 0) && (usec_ltncy < 1000000L)) {
|
|
SLEEP(0, usec_ltncy * 1000);
|
|
}
|
|
}
|
|
|
|
void cls_netcam::handler_reconnect()
|
|
{
|
|
int retcd;
|
|
|
|
if (service == "file") {
|
|
filelist_load();
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Processing file: %s")
|
|
,cameratype.c_str(), path.c_str());
|
|
} else if ((status == NETCAM_CONNECTED) ||
|
|
(status == NETCAM_READINGIMAGE)) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Reconnecting with camera....")
|
|
,cameratype.c_str());
|
|
}
|
|
status = NETCAM_RECONNECTING;
|
|
|
|
if (passthrough == true) {
|
|
cam->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 = connect();
|
|
if (retcd < 0) {
|
|
if (reconnect_count < 100) {
|
|
reconnect_count++;
|
|
} else if ((reconnect_count >= 100) && (reconnect_count <= 199)) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera did not reconnect.")
|
|
, cameratype.c_str());
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Checking for camera every 10 seconds.")
|
|
,cameratype.c_str());
|
|
reconnect_count++;
|
|
SLEEP(10,0);
|
|
} else if (reconnect_count >= 200) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera did not reconnect.")
|
|
, cameratype.c_str());
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Checking for camera every 10 minutes.")
|
|
,cameratype.c_str());
|
|
SLEEP(600,0);
|
|
} else {
|
|
reconnect_count++;
|
|
SLEEP(600,0);
|
|
}
|
|
} else {
|
|
reconnect_count = 0;
|
|
}
|
|
}
|
|
|
|
void cls_netcam::handler()
|
|
{
|
|
handler_finished = false;
|
|
|
|
mythreadname_set("nc", threadnbr, camera_name.c_str());
|
|
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera handler started")
|
|
,cameratype.c_str());
|
|
|
|
while (handler_stop == false) {
|
|
if (format_context == nullptr) { /* We must have disconnected. Try to reconnect */
|
|
clock_gettime(CLOCK_MONOTONIC, &frame_prev_tm);
|
|
handler_reconnect();
|
|
continue;
|
|
} else { /* We think we are connected...*/
|
|
clock_gettime(CLOCK_MONOTONIC, &frame_prev_tm);
|
|
if (read_image() < 0) {
|
|
if (handler_stop == false) { /* Nope. We are not or got bad image. Reconnect*/
|
|
handler_reconnect();
|
|
}
|
|
continue;
|
|
}
|
|
if (last_stream_index == video_stream_index) {
|
|
handler_wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Camera handler stopped"),cameratype.c_str());
|
|
handler_finished = true;
|
|
pthread_exit(nullptr);
|
|
|
|
}
|
|
|
|
void cls_netcam::handler_startup()
|
|
{
|
|
int wait_counter, retcd;
|
|
pthread_attr_t thread_attr;
|
|
|
|
if (handler_finished == true) {
|
|
handler_finished = false;
|
|
handler_stop = false;
|
|
pthread_attr_init(&thread_attr);
|
|
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
|
|
retcd = pthread_create(&handler_thread, &thread_attr, &netcam_handler, this);
|
|
if (retcd != 0) {
|
|
MOTPLS_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Unable to start camera thread."));
|
|
handler_finished = true;
|
|
handler_stop = true;
|
|
return;
|
|
}
|
|
pthread_attr_destroy(&thread_attr);
|
|
}
|
|
|
|
/* 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(&mutex);
|
|
if (img_latest->ptr != nullptr ) {
|
|
wait_counter = -1;
|
|
}
|
|
pthread_mutex_unlock(&mutex);
|
|
if (wait_counter > 0 ) {
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Waiting for first image from the handler.")
|
|
,cameratype.c_str());
|
|
SLEEP(0,5000000);
|
|
wait_counter--;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void cls_netcam::handler_shutdown()
|
|
{
|
|
int waitcnt;
|
|
|
|
MOTPLS_LOG(INF, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s:Shutting down network camera.")
|
|
,cameratype.c_str());
|
|
|
|
idur = 0;
|
|
handler_stop = true;
|
|
|
|
if (handler_finished == false) {
|
|
waitcnt = 0;
|
|
while ((handler_finished == false) && (waitcnt < cam->cfg->watchdog_tmo)){
|
|
SLEEP(1,0)
|
|
waitcnt++;
|
|
}
|
|
if (waitcnt == cam->cfg->watchdog_tmo) {
|
|
MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Normal shutdown of camera failed"));
|
|
if (cam->cfg->watchdog_kill > 0) {
|
|
MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
,_("Waiting additional %d seconds (watchdog_kill).")
|
|
,cam->cfg->watchdog_kill);
|
|
waitcnt = 0;
|
|
while ((handler_finished == false) && (waitcnt < cam->cfg->watchdog_kill)){
|
|
SLEEP(1,0)
|
|
waitcnt++;
|
|
}
|
|
if (waitcnt == cam->cfg->watchdog_kill) {
|
|
MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("No response to shutdown. Killing it."));
|
|
MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Memory leaks will occur."));
|
|
pthread_kill(handler_thread, SIGVTALRM);
|
|
}
|
|
} else {
|
|
MOTPLS_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("watchdog_kill set to terminate application."));
|
|
exit(1);
|
|
}
|
|
}
|
|
handler_finished = true;
|
|
}
|
|
|
|
context_close();
|
|
|
|
if (img_latest != nullptr) {
|
|
free(img_latest->ptr);
|
|
free(img_latest);
|
|
img_latest = nullptr;
|
|
}
|
|
|
|
if (img_recv != nullptr) {
|
|
free(img_recv->ptr);
|
|
free(img_recv);
|
|
img_recv = nullptr;
|
|
}
|
|
|
|
pthread_mutex_destroy(&mutex);
|
|
pthread_mutex_destroy(&mutex_pktarray);
|
|
pthread_mutex_destroy(&mutex_transfer);
|
|
|
|
if (params != nullptr) {
|
|
delete params;
|
|
params = nullptr;
|
|
}
|
|
|
|
cam->device_status = STATUS_CLOSED;
|
|
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("%s: Shut down complete.")
|
|
,cameratype.c_str());
|
|
|
|
}
|
|
|
|
int cls_netcam::next(ctx_image_data *img_data)
|
|
{
|
|
if ((status == NETCAM_RECONNECTING) ||
|
|
(status == NETCAM_NOTCONNECTED)) {
|
|
return CAPTURE_ATTEMPTED;
|
|
}
|
|
|
|
pthread_mutex_lock(&mutex);
|
|
pktarray_resize();
|
|
if (high_resolution == false) {
|
|
memcpy(img_data->image_norm
|
|
, img_latest->ptr
|
|
, img_latest->used);
|
|
img_data->idnbr_norm = idnbr;
|
|
} else {
|
|
img_data->idnbr_high = idnbr;
|
|
if (cam->netcam_high->passthrough == false) {
|
|
memcpy(img_data->image_high
|
|
, img_latest->ptr
|
|
, img_latest->used);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
return CAPTURE_SUCCESS;
|
|
}
|
|
|
|
cls_netcam::cls_netcam(cls_camera *p_cam, bool p_is_high)
|
|
{
|
|
int retcd;
|
|
|
|
cam = p_cam;
|
|
high_resolution = p_is_high;
|
|
handler_finished = true;
|
|
handler_stop = false;
|
|
|
|
if (high_resolution == false) {
|
|
MOTPLS_LOG(NTC, TYPE_VIDEO, NO_ERRNO,_("Norm: Opening Netcam"));
|
|
} else {
|
|
MOTPLS_LOG(NTC, TYPE_VIDEO, NO_ERRNO,_("High: Opening Netcam"));
|
|
}
|
|
|
|
set_parms();
|
|
if (service == "file") {
|
|
retcd = connect();
|
|
while ((retcd != 0) &&
|
|
(filenbr < (int)filelist.size())) {
|
|
filelist_load();
|
|
retcd = connect();
|
|
}
|
|
if (retcd != 0) {
|
|
handler_shutdown();
|
|
return;
|
|
}
|
|
} else {
|
|
if (connect() != 0) {
|
|
handler_shutdown();
|
|
return;
|
|
}
|
|
}
|
|
if (read_image() != 0) {
|
|
MOTPLS_LOG(NTC, TYPE_NETCAM, NO_ERRNO
|
|
,_("Failed trying to read first image"));
|
|
status = NETCAM_NOTCONNECTED;
|
|
handler_shutdown();
|
|
return;
|
|
}
|
|
/* When running dual, there seems to be contamination across norm/high with codec functions. */
|
|
context_close(); /* Close in this thread to open it again within handler thread */
|
|
status = NETCAM_RECONNECTING; /* Set as reconnecting to avoid excess messages when starting */
|
|
first_image = false; /* Set flag that we are not processing our first image */
|
|
|
|
if (high_resolution) {
|
|
cam->imgs.width_high = imgsize.width;
|
|
cam->imgs.height_high = imgsize.height;
|
|
}
|
|
|
|
handler_startup();
|
|
|
|
cam->device_status = STATUS_OPENED;
|
|
|
|
}
|
|
|
|
cls_netcam::~cls_netcam()
|
|
{
|
|
handler_shutdown();
|
|
}
|
|
|