Files
motion/src/netcam.cpp
2024-08-16 20:32:44 -06:00

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 = &params->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 = &params->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();
}