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

1746 lines
50 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 "netcam.hpp"
#include "dbse.hpp"
#include "alg_sec.hpp"
#include "movie.hpp"
int movie_interrupt(void *ctx)
{
cls_movie *movie = (cls_movie *)ctx;
clock_gettime(CLOCK_MONOTONIC, &movie->cb_cr_ts);
if ((movie->cb_cr_ts.tv_sec - movie->cb_st_ts.tv_sec ) > movie->cb_dur) {
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO,_("Movie timed out"));
return 1;
}
return 0;
}
void cls_movie::free_pkt()
{
av_packet_free(&pkt);
pkt = nullptr;
}
void cls_movie::free_nal()
{
if (nal_info) {
free(nal_info);
nal_info = nullptr;
nal_info_len = 0;
}
}
void cls_movie::encode_nal()
{
// h264_v4l2m2m has NAL units separated from the first frame, which makes
// some players very unhappy.
if ((pkt->pts == 0) && (!(pkt->flags & AV_PKT_FLAG_KEY))) {
free_nal();
nal_info_len = pkt->size;
nal_info =(char*)mymalloc((uint)nal_info_len);
if (nal_info) {
memcpy(nal_info, &pkt->data[0], (uint)nal_info_len);
} else {
nal_info_len = 0;
}
} else if (nal_info) {
int old_size = pkt->size;
av_grow_packet(pkt, nal_info_len);
memmove(&pkt->data[nal_info_len], &pkt->data[0],(uint)old_size);
memcpy(&pkt->data[0], nal_info, (uint)nal_info_len);
free_nal();
}
}
int cls_movie::timelapse_exists(const char *fname)
{
struct stat statbuf;
if (stat(fname, &statbuf) == 0) {
return 1;
} else {
return 0;
}
}
int cls_movie::timelapse_append(AVPacket *p_pkt)
{
FILE *file;
file = myfopen(full_nm.c_str(), "abe");
if (file == nullptr) {
return -1;
}
fwrite(p_pkt->data, 1, (uint)p_pkt->size, file);
myfclose(file);
return 0;
}
void cls_movie::free_context()
{
if (picture != nullptr) {
av_frame_free(&picture);
picture = nullptr;
}
if (ctx_codec != nullptr) {
avcodec_free_context(&ctx_codec);
ctx_codec = nullptr;
}
if (oc != nullptr) {
avformat_free_context(oc);
oc = nullptr;
}
}
int cls_movie::get_oformat()
{
if (tlapse == TIMELAPSE_APPEND) {
oc->oformat = av_guess_format("mpeg2video", nullptr, nullptr);
oc->video_codec_id = MY_CODEC_ID_MPEG2VIDEO;
full_nm += ".mpg";
movie_nm += ".mpg";
if (oc->oformat == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error setting timelapse append for container %s")
, container.c_str());
free_context();
return -1;
}
return 0;
}
if (container == "mov") {
oc->oformat = av_guess_format("mov", nullptr, nullptr);
full_nm += ".mov";
movie_nm += ".mov";
oc->video_codec_id = MY_CODEC_ID_H264;
}
if (container == "flv") {
oc->oformat = av_guess_format("flv", nullptr, nullptr);
full_nm += ".flv";
movie_nm += ".flv";
oc->video_codec_id = MY_CODEC_ID_FLV1;
}
if (container == "ogg") {
oc->oformat = av_guess_format("ogg", nullptr, nullptr);
full_nm += ".ogg";
movie_nm += ".ogg";
oc->video_codec_id = MY_CODEC_ID_THEORA;
}
if (container == "webm") {
oc->oformat = av_guess_format("webm", nullptr, nullptr);
full_nm += ".webm";
movie_nm += ".webm";
oc->video_codec_id = MY_CODEC_ID_VP8;
}
if (container == "mp4") {
oc->oformat = av_guess_format("mp4", nullptr, nullptr);
full_nm += ".mp4";
movie_nm += ".mp4";
oc->video_codec_id = MY_CODEC_ID_H264;
}
if (container == "mkv") {
oc->oformat = av_guess_format("matroska", nullptr, nullptr);
full_nm += ".mkv";
movie_nm += ".mkv";
oc->video_codec_id = MY_CODEC_ID_H264;
}
if (container == "hevc") {
oc->video_codec_id = MY_CODEC_ID_HEVC;
oc->oformat = av_guess_format("mp4", nullptr, nullptr);
full_nm += ".mp4";
movie_nm += ".mp4";
oc->video_codec_id = MY_CODEC_ID_HEVC;
}
if (oc->oformat == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("container option value %s is not supported")
, container.c_str());
free_context();
return -1;
}
if (oc->oformat->video_codec == MY_CODEC_ID_NONE) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get the container"));
free_context();
return -1;
}
return 0;
}
int cls_movie::encode_video()
{
int retcd = 0;
char errstr[128];
retcd = avcodec_send_frame(ctx_codec, picture);
if (retcd < 0 ) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error sending frame for encoding:%s"),errstr);
return -1;
}
retcd = avcodec_receive_packet(ctx_codec, pkt);
if (retcd == AVERROR(EAGAIN)) {
//Buffered packet. Throw special return code
free_pkt();
return -2;
}
if (retcd < 0 ) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error receiving encoded packet video:%s"),errstr);
//Packet is freed upon failure of encoding
return -1;
}
if (preferred_codec == "h264_v4l2m2m") {
encode_nal();
}
return 0;
}
int cls_movie::set_pts(const struct timespec *ts1)
{
int64_t pts_interval;
if (tlapse != TIMELAPSE_NONE) {
last_pts++;
picture->pts = last_pts;
} else {
pts_interval = ((1000000L * (ts1->tv_sec - start_time.tv_sec)) + (ts1->tv_nsec/1000) - (start_time.tv_nsec/1000));
if (pts_interval < 0) {
/* This can occur when we have pre-capture frames. Reset start time of video. */
reset_start_time(ts1);
pts_interval = 0;
}
if (last_pts < 0) {
// This is the very first frame, ensure PTS is zero
picture->pts = 0;
} else {
picture->pts = base_pts +
av_rescale_q(pts_interval
, av_make_q(1, 1000000L)
, strm_video->time_base);
}
if (test_mode == true) {
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO
,_("PTS %" PRId64 " Base PTS %" PRId64 " ms interval %" PRId64 " timebase %d-%d")
,picture->pts,base_pts,pts_interval
,strm_video->time_base.num,strm_video->time_base.den);
}
if (picture->pts <= last_pts) {
//We have a problem with our motion loop timing and sending frames or the rounding into the PTS.
if (test_mode == true) {
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO, _("BAD TIMING!! Frame skipped."));
}
return -1;
}
last_pts = picture->pts;
}
return 0;
}
int cls_movie::set_quality()
{
int quality;
opts = 0;
quality = cam->cfg->movie_quality;
if (quality > 100) {
quality = 100;
}
if (ctx_codec->codec_id == MY_CODEC_ID_H264 ||
ctx_codec->codec_id == MY_CODEC_ID_HEVC) {
if (quality <= 0) {
quality = 45; // default to 45%
}
if (preferred_codec == "h264_v4l2m2m") {
// bit_rate = width * height * fps * quality_factor
quality = (int)(((int64_t)width * height * fps * quality) >> 7);
// Clip bit rate to min
if (quality < 4000) {
// magic number
quality = 4000;
}
ctx_codec->profile = FF_PROFILE_H264_HIGH;
ctx_codec->bit_rate = quality;
av_dict_set(&opts, "preset", "ultrafast", 0);
av_dict_set(&opts, "tune", "zerolatency", 0);
} else {
/* Control other H264 encoders quality is via CRF. To get the profiles
* to work (main), (high), we are setting this via the opt instead of
* dictionary. The ultrafast is not used because at that level, the
* profile reverts to (baseline) and a bit more efficiency is in
* (main) or (high) so we choose next fastest option (superfast)
*/
char crf[10];
quality = (int)(( (100-quality) * 51)/100);
snprintf(crf, 10, "%d", quality);
if (ctx_codec->codec_id == MY_CODEC_ID_H264) {
av_opt_set(ctx_codec->priv_data, "profile", "high", 0);
}
av_opt_set(ctx_codec->priv_data, "crf", crf, 0);
av_opt_set(ctx_codec->priv_data, "tune", "zerolatency", 0);
av_opt_set(ctx_codec->priv_data, "preset", "superfast",0);
}
} else {
/* The selection of 8000 is a subjective number based upon viewing output files */
if (quality > 0) {
quality =(int)(((100-quality)*(100-quality)*(100-quality) * 8000) / 1000000) + 1;
ctx_codec->flags |= MY_CODEC_FLAG_QSCALE;
ctx_codec->global_quality=quality;
}
}
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO
,_("%s codec vbr/crf/bit_rate: %d"), codec->name, quality);
return 0;
}
int cls_movie::set_codec_preferred()
{
codec = nullptr;
if (preferred_codec != "") {
codec = avcodec_find_encoder_by_name(preferred_codec.c_str());
if (codec == nullptr) {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO
,_("Failed to find user requested codec %s")
, preferred_codec.c_str());
codec = avcodec_find_encoder(oc->video_codec_id);
} else {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO
,_("Using codec %s"), preferred_codec.c_str());
}
} else {
codec = avcodec_find_encoder(oc->video_codec_id);
}
if (codec == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("codec for container %s not found"), container.c_str());
free_context();
return -1;
}
return 0;
}
int cls_movie::set_codec()
{
int retcd;
char errstr[128];
int chkrate;
if (set_codec_preferred() != 0) {
return -1;
}
strm_video = avformat_new_stream(oc, codec);
if (!strm_video) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc stream"));
free_context();
return -1;
}
ctx_codec = avcodec_alloc_context3(codec);
if (ctx_codec == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Failed to allocate codec context!"));
free_context();
return -1;
}
if (tlapse != TIMELAPSE_NONE) {
ctx_codec->gop_size = 1;
} else {
if (fps <= 5) {
ctx_codec->gop_size = 1;
} else if (fps > 30) {
ctx_codec->gop_size = 15;
} else {
ctx_codec->gop_size = (fps / 2);
}
gop_cnt = ctx_codec->gop_size - 1;
}
/* For certain containers, setting the fps to very low numbers results in
** a very poor quality playback. We can set the FPS to a higher number and
** then let the PTS display the frames correctly.
*/
if ((tlapse == TIMELAPSE_NONE) && (fps <= 5)) {
if ((container == "flv") ||
(container == "mp4") ||
(container == "hevc")) {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO
, "Low fps. Encoding %d frames into a %d frames container."
, fps, 10);
fps = 10;
}
}
ctx_codec->codec_id = codec->id;
ctx_codec->codec_type = AVMEDIA_TYPE_VIDEO;
ctx_codec->bit_rate = cam->cfg->movie_bps;
ctx_codec->width = width;
ctx_codec->height = height;
ctx_codec->time_base.num = 1;
ctx_codec->time_base.den = fps;
ctx_codec->pix_fmt = MY_PIX_FMT_YUV420P;
ctx_codec->max_b_frames = 0;
ctx_codec->flags |= MY_CODEC_FLAG_GLOBAL_HEADER;
if (set_quality() < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to set quality"));
return -1;
}
retcd = avcodec_open2(ctx_codec, codec, &opts);
if (retcd < 0) {
if (codec->supported_framerates) {
const AVRational *p_fps = codec->supported_framerates;
while (p_fps->num) {
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO
,_("Reported FPS Supported %d/%d"), p_fps->num, p_fps->den);
p_fps++;
}
}
chkrate = 1;
while ((chkrate < 36) && (retcd != 0)) {
ctx_codec->time_base.den = chkrate;
retcd = avcodec_open2(ctx_codec, codec, &opts);
chkrate++;
}
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not open codec %s"),errstr);
av_dict_free(&opts);
free_context();
return -1;
}
}
av_dict_free(&opts);
return 0;
}
int cls_movie::set_stream()
{
int retcd;
char errstr[128];
retcd = avcodec_parameters_from_context(strm_video->codecpar,ctx_codec);
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Failed to copy decoder parameters!: %s"), errstr);
free_context();
return -1;
}
strm_video->time_base = av_make_q(1, fps);
return 0;
}
/*Special allocation of video buffer for v4l2m2m codec*/
int cls_movie::alloc_video_buffer(AVFrame *frame, int align)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((enum AVPixelFormat)frame->format);
int ret, i, padded_height;
int plane_padding = FFMAX(16 + 16/*STRIDE_ALIGN*/, align);
if (!desc) {
return AVERROR(EINVAL);
}
if ((ret = av_image_check_size(
(uint)frame->width, (uint)frame->height
, 0, nullptr)) < 0) {
return ret;
}
if (!frame->linesize[0]) {
if (align <= 0) {
align = 32; /* STRIDE_ALIGN. Should be av_cpu_max_align() */
}
for(i=1; i<=align; i+=i) {
ret = av_image_fill_linesizes(frame->linesize,(enum AVPixelFormat) frame->format,
FFALIGN(frame->width, i));
if (ret < 0) {
return ret;
}
if (!(frame->linesize[0] & (align-1))) {
break;
}
}
for (i = 0; i < 4 && frame->linesize[i]; i++)
frame->linesize[i] = FFALIGN(frame->linesize[i], align);
}
padded_height = FFALIGN(frame->height, 32);
ret = av_image_fill_pointers(frame->data
,(enum AVPixelFormat) frame->format
, padded_height
, nullptr
, frame->linesize);
if (ret < 0) {
return ret;
}
frame->buf[0] = av_buffer_alloc((uint)(ret + 4*plane_padding));
if (!frame->buf[0]) {
ret = AVERROR(ENOMEM);
av_frame_unref(frame);
return ret;
}
frame->buf[1] = av_buffer_alloc((uint)(ret + 4*plane_padding));
if (!frame->buf[1]) {
ret = AVERROR(ENOMEM);
av_frame_unref(frame);
return ret;
}
frame->data[0] = frame->buf[0]->data;
frame->data[1] = frame->buf[1]->data;
frame->data[2] = frame->data[1] + ((frame->width * padded_height) / 4);
frame->extended_data = frame->data;
return 0;
}
int cls_movie::set_picture()
{
int retcd;
char errstr[128];
picture = av_frame_alloc();
if (!picture) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("could not alloc frame"));
free_context();
return -1;
}
if (cam->cfg->movie_quality) {
picture->quality = (int)(FF_LAMBDA_MAX *
(float)((100-cam->cfg->movie_quality)/100))+1;
}
picture->linesize[0] = ctx_codec->width;
picture->linesize[1] = ctx_codec->width / 2;
picture->linesize[2] = ctx_codec->width / 2;
picture->format = ctx_codec->pix_fmt;
picture->width = ctx_codec->width;
picture->height = ctx_codec->height;
if (preferred_codec == "h264_v4l2m2m") {
retcd = alloc_video_buffer(picture, 32);
if (retcd) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("could not alloc buffers %s"), errstr);
free_context();
return -1;
}
}
return 0;
}
int cls_movie::set_outputfile()
{
int retcd;
char errstr[128];
/* Open the output file, if needed. */
if ((timelapse_exists(full_nm.c_str()) == 0) || (tlapse != TIMELAPSE_APPEND)) {
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
retcd = avio_open(&oc->pb, full_nm.c_str()
, MY_FLAG_WRITE|AVIO_FLAG_NONBLOCK);
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO
,_("avio_open: %s File %s")
, errstr, full_nm.c_str());
if (errno == ENOENT) {
if (mycreate_path(full_nm.c_str()) == -1) {
remove(full_nm.c_str());
free_context();
return -1;
}
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
retcd = avio_open(&oc->pb, full_nm.c_str(), MY_FLAG_WRITE| AVIO_FLAG_NONBLOCK);
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO
,_("error %s opening file %s")
, errstr, full_nm.c_str());
remove(full_nm.c_str());
free_context();
return -1;
}
} else {
MOTPLS_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO
,_("Error opening file %s")
, full_nm.c_str());
remove(full_nm.c_str());
free_context();
return -1;
}
}
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
retcd = avformat_write_header(oc, nullptr);
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Could not write movie header %s"),errstr);
if ((container == "mp4") && (strm_audio != nullptr)) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
, _("Ensure audio codec is permitted with a MP4 container."));
}
remove(full_nm.c_str());
free_context();
return -1;
}
/* TIMELAPSE_APPEND uses standard file IO so we close it */
if (tlapse == TIMELAPSE_APPEND) {
av_write_trailer(oc);
avio_close(oc->pb);
}
}
return 0;
}
int cls_movie::flush_codec()
{
int retcd;
int recv_cd = 0;
char errstr[128];
if (passthrough) {
return 0;
}
retcd = 0;
recv_cd = 0;
if (tlapse == TIMELAPSE_NONE) {
retcd = avcodec_send_frame(ctx_codec, nullptr);
if (retcd < 0 ) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error entering draining mode:%s"),errstr);
return -1;
}
while (recv_cd != AVERROR_EOF){
pkt = mypacket_alloc(pkt);
recv_cd = avcodec_receive_packet(ctx_codec, pkt);
if (recv_cd != AVERROR_EOF) {
if (recv_cd < 0) {
av_strerror(recv_cd, errstr, sizeof(errstr));
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error draining codec:%s"),errstr);
free_pkt();
return -1;
}
// v4l2_m2m encoder uses pts 0 and size 0 to indicate AVERROR_EOF
if ((pkt->pts == 0) || (pkt->size == 0)) {
recv_cd = AVERROR_EOF;
free_pkt();
continue;
}
retcd = av_write_frame(oc, pkt);
if (retcd < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Error writing draining video frame"));
return -1;
}
}
free_pkt();
}
}
return 0;
}
int cls_movie::put_frame(const struct timespec *ts1)
{
int retcd;
pkt = mypacket_alloc(pkt);
retcd = set_pts(ts1);
if (retcd < 0) {
//If there is an error, it has already been reported.
free_pkt();
return 0;
}
retcd = encode_video();
if (retcd != 0) {
if (retcd != -2) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error while encoding picture"));
}
free_pkt();
return retcd;
}
if (tlapse == TIMELAPSE_APPEND) {
retcd = timelapse_append(pkt);
} else {
retcd = av_write_frame(oc, pkt);
}
free_pkt();
if (retcd < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error while writing video frame"));
return -1;
}
return retcd;
}
/* Reset the written flag and movie start time at opening of each event */
void cls_movie::passthru_reset()
{
int indx;
pthread_mutex_lock(&netcam_data->mutex_pktarray);
for(indx = 0; indx < netcam_data->pktarray_size; indx++) {
netcam_data->pktarray[indx].iswritten = false;
}
pthread_mutex_unlock(&netcam_data->mutex_pktarray);
}
int cls_movie::passthru_pktpts()
{
int64_t ts_interval, base_pdts;
AVRational tmpbase;
int indx;
if (pkt->stream_index == netcam_data->audio_stream_index) {
tmpbase = strm_audio->time_base;
indx = netcam_data->audio_stream_index;
base_pdts = pass_audio_base;
} else {
tmpbase = strm_video->time_base;
indx = netcam_data->video_stream_index;
base_pdts = pass_video_base;
}
if (pkt->pts != AV_NOPTS_VALUE) {
if (pkt->pts < base_pdts) {
ts_interval = 0;
} else {
ts_interval = pkt->pts - base_pdts;
}
pkt->pts = av_rescale_q(ts_interval
, netcam_data->transfer_format->streams[indx]->time_base, tmpbase);
}
if (pkt->dts != AV_NOPTS_VALUE) {
if (pkt->dts < base_pdts) {
ts_interval = 0;
} else {
ts_interval = pkt->dts - base_pdts;
}
pkt->dts = av_rescale_q(ts_interval
, netcam_data->transfer_format->streams[indx]->time_base, tmpbase);
}
ts_interval = pkt->duration;
pkt->duration = av_rescale_q(ts_interval
, netcam_data->transfer_format->streams[indx]->time_base, tmpbase);
/*
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO
,_("base PTS %" PRId64 " new PTS %" PRId64 " srcbase %d-%d newbase %d-%d")
,ts_interval, pkt->duration
,netcam_data->transfer_format->streams[indx]->time_base.num
,netcam_data->transfer_format->streams[indx]->time_base.den
,tmpbase.num, tmpbase.den);
*/
return 0;
}
void cls_movie::passthru_write(int indx)
{
/* Write the packet in the buffer at indx to file */
char errstr[128];
int retcd;
pkt = mypacket_alloc(pkt);
netcam_data->pktarray[indx].iswritten = true;
retcd = av_packet_ref(pkt, netcam_data->pktarray[indx].packet);
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO, "av_copy_packet: %s",errstr);
free_pkt();
return;
}
retcd = passthru_pktpts();
if (retcd < 0) {
free_pkt();
return;
}
retcd = av_interleaved_write_frame(oc, pkt);
free_pkt();
if (retcd < 0) {
av_strerror(retcd, errstr, sizeof(errstr));
MOTPLS_LOG(DBG, TYPE_ENCODER, NO_ERRNO
,_("Error while writing video frame: %s"),errstr);
return;
}
}
void cls_movie::passthru_minpts()
{
int indx, indx_audio, indx_video;
pass_audio_base = 0;
pass_video_base = 0;
pthread_mutex_lock(&netcam_data->mutex_pktarray);
indx_audio = netcam_data->audio_stream_index;
indx_video = netcam_data->video_stream_index;
for (indx = 0; indx < netcam_data->pktarray_size; indx++) {
if ((netcam_data->pktarray[indx].packet->stream_index == indx_audio) &&
(netcam_data->pktarray[indx].packet->pts != AV_NOPTS_VALUE)) {
pass_audio_base = netcam_data->pktarray[indx].packet->pts;
};
if ((netcam_data->pktarray[indx].packet->stream_index == indx_video) &&
(netcam_data->pktarray[indx].packet->pts != AV_NOPTS_VALUE)) {
pass_video_base = netcam_data->pktarray[indx].packet->pts;
};
}
for (indx = 0; indx < netcam_data->pktarray_size; indx++) {
if ((netcam_data->pktarray[indx].packet->stream_index == indx_audio) &&
(netcam_data->pktarray[indx].packet->pts != AV_NOPTS_VALUE) &&
(netcam_data->pktarray[indx].packet->pts < pass_audio_base)) {
pass_audio_base = netcam_data->pktarray[indx].packet->pts;
};
if ((netcam_data->pktarray[indx].packet->stream_index == indx_audio) &&
(netcam_data->pktarray[indx].packet->dts != AV_NOPTS_VALUE) &&
(netcam_data->pktarray[indx].packet->dts < pass_audio_base)) {
pass_audio_base = netcam_data->pktarray[indx].packet->dts;
};
if ((netcam_data->pktarray[indx].packet->stream_index == indx_video) &&
(netcam_data->pktarray[indx].packet->pts != AV_NOPTS_VALUE) &&
(netcam_data->pktarray[indx].packet->pts < pass_video_base)) {
pass_video_base = netcam_data->pktarray[indx].packet->pts;
};
if ((netcam_data->pktarray[indx].packet->stream_index == indx_video) &&
(netcam_data->pktarray[indx].packet->dts != AV_NOPTS_VALUE) &&
(netcam_data->pktarray[indx].packet->dts < pass_video_base)) {
pass_video_base = netcam_data->pktarray[indx].packet->dts;
};
}
pthread_mutex_unlock(&netcam_data->mutex_pktarray);
if (pass_audio_base < 0) {
pass_audio_base = 0;
}
if (pass_video_base < 0) {
pass_video_base = 0;
}
}
int cls_movie::passthru_put(ctx_image_data *img_data)
{
int64_t idnbr_image, idnbr_lastwritten, idnbr_stop, idnbr_firstkey;
int indx, indx_lastwritten, indx_firstkey, indx_video;
if (netcam_data == nullptr) {
return -1;
}
if ((netcam_data->status == NETCAM_NOTCONNECTED ) ||
(netcam_data->status == NETCAM_RECONNECTING ) ) {
return 0;
}
if (high_resolution) {
idnbr_image = img_data->idnbr_high;
} else {
idnbr_image = img_data->idnbr_norm;
}
pthread_mutex_lock(&netcam_data->mutex_pktarray);
idnbr_lastwritten = 0;
idnbr_firstkey = idnbr_image;
idnbr_stop = 0;
indx_lastwritten = -1;
indx_firstkey = -1;
indx_video = netcam_data->video_stream_index;
for(indx = 0; indx < netcam_data->pktarray_size; indx++) {
if ((netcam_data->pktarray[indx].iswritten) &&
(netcam_data->pktarray[indx].idnbr > idnbr_lastwritten) &&
(netcam_data->pktarray[indx].packet->stream_index == indx_video)) {
idnbr_lastwritten=netcam_data->pktarray[indx].idnbr;
indx_lastwritten = indx;
}
if ((netcam_data->pktarray[indx].idnbr > idnbr_stop) &&
(netcam_data->pktarray[indx].idnbr <= idnbr_image)&&
(netcam_data->pktarray[indx].packet->stream_index == indx_video)) {
idnbr_stop=netcam_data->pktarray[indx].idnbr;
}
if ((netcam_data->pktarray[indx].iskey) &&
(netcam_data->pktarray[indx].idnbr <= idnbr_firstkey)&&
(netcam_data->pktarray[indx].packet->stream_index == indx_video)) {
idnbr_firstkey=netcam_data->pktarray[indx].idnbr;
indx_firstkey = indx;
}
}
if (idnbr_stop == 0) {
pthread_mutex_unlock(&netcam_data->mutex_pktarray);
return 0;
}
if (indx_lastwritten != -1) {
indx = indx_lastwritten;
} else if (indx_firstkey != -1) {
indx = indx_firstkey;
} else {
indx = 0;
}
while (true){
if ((!netcam_data->pktarray[indx].iswritten) &&
(netcam_data->pktarray[indx].packet->size > 0) &&
(netcam_data->pktarray[indx].idnbr > idnbr_lastwritten) &&
(netcam_data->pktarray[indx].idnbr <= idnbr_image)) {
passthru_write(indx);
}
if (netcam_data->pktarray[indx].idnbr == idnbr_stop) {
break;
}
indx++;
if (indx == netcam_data->pktarray_size ) {
indx = 0;
}
}
pthread_mutex_unlock(&netcam_data->mutex_pktarray);
return 0;
}
int cls_movie::passthru_streams_video(AVStream *stream_in)
{
int retcd;
strm_video = avformat_new_stream(oc, nullptr);
if (strm_video == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc video stream"));
return -1;
}
retcd = avcodec_parameters_copy(strm_video->codecpar, stream_in->codecpar);
if (retcd < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to copy video codec parameters"));
return -1;
}
strm_video->codecpar->codec_tag = 0;
strm_video->time_base = stream_in->time_base;
strm_video->avg_frame_rate = stream_in->avg_frame_rate;
MOTPLS_LOG(DBG, TYPE_ENCODER, NO_ERRNO
, _("video timebase %d/%d fps %d/%d")
, strm_video->time_base.num
, strm_video->time_base.den
, strm_video->avg_frame_rate.num
, strm_video->avg_frame_rate.den);
return 0;
}
int cls_movie::passthru_streams_audio( AVStream *stream_in)
{
int retcd;
strm_audio = avformat_new_stream(oc, nullptr);
if (!strm_audio) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not alloc audio stream"));
return -1;
}
retcd = avcodec_parameters_copy(strm_audio->codecpar, stream_in->codecpar);
if (retcd < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Unable to copy audio codec parameters"));
return -1;
}
strm_audio->codecpar->codec_tag = 0;
strm_audio->time_base = stream_in->time_base;
strm_audio->r_frame_rate = stream_in->time_base;
strm_audio->avg_frame_rate= stream_in->time_base;
strm_audio->codecpar->format = stream_in->codecpar->format;
strm_audio->codecpar->sample_rate = stream_in->codecpar->sample_rate;
strm_audio->avg_frame_rate = stream_in->avg_frame_rate;
MOTPLS_LOG(DBG, TYPE_ENCODER, NO_ERRNO
, _("audio timebase %d/%d")
, strm_audio->time_base.num
, strm_audio->time_base.den);
return 0;
}
int cls_movie::passthru_streams()
{
int retcd, indx;
AVStream *stream_in;
if (netcam_data->handler_stop == true) {
return -1;
}
pthread_mutex_lock(&netcam_data->mutex_transfer);
for (indx= 0; indx < (int)netcam_data->transfer_format->nb_streams; indx++) {
stream_in = netcam_data->transfer_format->streams[indx];
if (stream_in->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
retcd = passthru_streams_video(stream_in);
} else if (stream_in->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
retcd = passthru_streams_audio(stream_in);
}
if (retcd < 0) {
pthread_mutex_unlock(&netcam_data->mutex_transfer);
return retcd;
}
}
pthread_mutex_unlock(&netcam_data->mutex_transfer);
return 0;
}
int cls_movie::passthru_check()
{
if ((netcam_data->status == NETCAM_NOTCONNECTED ) ||
(netcam_data->status == NETCAM_RECONNECTING )) {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO
,_("rtsp camera not ready for pass-through."));
return -1;
}
if (netcam_data == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("RTSP context not available."));
return -1;
}
passthru_reset();
return 0;
}
int cls_movie::passthru_open()
{
int retcd;
retcd = passthru_check();
if (retcd < 0) {
return retcd;
}
oc = avformat_alloc_context();
if (!oc) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not allocate output context"));
free_context();
return -1;
}
oc->interrupt_callback.callback = movie_interrupt;
oc->interrupt_callback.opaque = this;
cb_dur = 3;
if ((container != "mp4") &&
(container != "mov") &&
(container != "mkv")) {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO
,_("Changing to MP4 container for pass-through."));
container = "mp4";
}
passthru_minpts();
retcd = get_oformat();
if (retcd < 0 ) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get output format!"));
return -1;
}
retcd = passthru_streams();
if (retcd < 0 ) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get streams!"));
return -1;
}
retcd = set_outputfile();
if (retcd < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not create output file"));
return -1;
}
if (strm_audio != nullptr) {
MOTPLS_LOG(DBG, TYPE_ENCODER, NO_ERRNO
, _("Timebase after open audio: %d/%d video: %d/%d")
, strm_audio->time_base.num
, strm_audio->time_base.den
, strm_video->time_base.num
, strm_video->time_base.den);
}
MOTPLS_LOG(INF, TYPE_ENCODER, NO_ERRNO, "Pass-through stream opened");
return 0;
}
void cls_movie::put_pix_yuv420(ctx_image_data *img_data)
{
unsigned char *image;
if (high_resolution) {
image = img_data->image_high;
} else {
image = img_data->image_norm;
}
// Usual setup for image pointers
picture->data[0] = image;
picture->data[1] = image + (ctx_codec->width * ctx_codec->height);
picture->data[2] = picture->data[1] + ((ctx_codec->width * ctx_codec->height) / 4);
}
void cls_movie::on_movie_start()
{
MOTPLS_LOG(DBG, TYPE_EVENTS, NO_ERRNO, _("Creating movie: %s"),full_nm.c_str());
if (cam->cfg->on_movie_start != "") {
util_exec_command(cam, cam->cfg->on_movie_start.c_str(), full_nm.c_str());
}
}
void cls_movie::on_movie_end()
{
MOTPLS_LOG(DBG, TYPE_EVENTS, NO_ERRNO, _("Finished movie: %s"),full_nm.c_str());
if (cam->cfg->on_movie_end != "") {
util_exec_command(cam, cam->cfg->on_movie_end.c_str(), full_nm.c_str());
}
}
int cls_movie::movie_open()
{
if (passthrough) {
if (passthru_open() < 0 ) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not setup passthrough!"));
free_context();
return -1;
}
return 0;
}
oc = avformat_alloc_context();
if (oc == nullptr) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not allocate output context"));
free_context();
return -1;
}
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
cb_dur = 3;
oc->interrupt_callback.callback = movie_interrupt;
oc->interrupt_callback.opaque = this;
if (get_oformat() < 0 ) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not get codec!"));
free_context();
return -1;
}
if (set_codec() < 0 ) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Failed to allocate codec!"));
return -1;
}
if (set_stream() < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not set the stream"));
return -1;
}
if (set_picture() < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not set the picture"));
return -1;
}
if (set_outputfile() < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Could not open output file"));
return -1;
}
return 0;
}
void cls_movie::stop()
{
timespec *ts;
if (is_running == false) {
return;
}
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
if (movie_type == "extpipe") {
if (extpipe_stream != nullptr) {
fflush(extpipe_stream);
pclose(extpipe_stream);
extpipe_stream = nullptr;
}
} else {
if (flush_codec() < 0) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO, _("Error flushing codec"));
}
if (oc != nullptr) {
if (oc->pb != nullptr) {
if (tlapse != TIMELAPSE_APPEND) {
av_write_trailer(oc);
}
if (!(oc->oformat->flags & AVFMT_NOFILE)) {
if (tlapse != TIMELAPSE_APPEND) {
avio_close(oc->pb);
}
}
}
}
free_context();
free_nal();
}
if (movie_type == "motion") {
ts = &cam->imgs.image_motion.imgts;
} else {
ts = &cam->current_image->imgts;
}
if ((movie_type == "norm") || (movie_type == "motion") || (movie_type == "extpipe")) {
cam->filetype = FTYPE_MOVIE;
on_movie_end();
cam->motapp->dbse->exec(cam, full_nm, "movie_end");
if ((cam->cfg->movie_retain == "secondary") &&
(cam->algsec->detected == false) &&
(cam->algsec->method != "none")) {
if (remove(full_nm.c_str()) != 0) {
MOTPLS_LOG(ERR, TYPE_EVENTS, SHOW_ERRNO
, _("Unable to remove file %s"), full_nm.c_str());
} else {
cam->motapp->dbse->movielist_add(cam, this, ts);
}
} else {
cam->motapp->dbse->movielist_add(cam, this, ts);
}
} else if (movie_type == "timelapse") {
cam->filetype = FTYPE_MOVIE_TIMELAPSE;
on_movie_end();
cam->motapp->dbse->exec(cam, full_nm, "movie_end");
} else {
MOTPLS_LOG(ERR, TYPE_EVENTS, NO_ERRNO,_("Invalid movie type"));
}
is_running = false;
}
int cls_movie::extpipe_put()
{
int retcd;
retcd = 0;
if (fileno(extpipe_stream) > 0) {
if ((cam->imgs.size_high > 0) && (cam->movie_passthrough == false)) {
if (!fwrite(cam->current_image->image_high
, (uint)cam->imgs.size_high, 1, extpipe_stream)) {
MOTPLS_LOG(ERR, TYPE_EVENTS, SHOW_ERRNO
, _("Error writing in pipe , state error %d")
, ferror(extpipe_stream));
retcd = -1;
}
} else {
if (!fwrite(cam->current_image->image_norm
, (uint)cam->imgs.size_norm, 1, extpipe_stream)) {
MOTPLS_LOG(ERR, TYPE_EVENTS, SHOW_ERRNO
,_("Error writing in pipe , state error %d")
, ferror(extpipe_stream));
retcd = -1;
}
}
}
return retcd;
}
int cls_movie::put_image(ctx_image_data *img_data, const struct timespec *ts1)
{
int retcd = 0;
int cnt = 0;
if (is_running == false) {
return 0;
}
clock_gettime(CLOCK_MONOTONIC, &cb_st_ts);
if (movie_type == "extpipe") {
extpipe_put();
return 0;
}
if (passthrough) {
retcd = passthru_put(img_data);
return retcd;
}
if (picture) {
put_pix_yuv420(img_data);
gop_cnt ++;
if (gop_cnt == ctx_codec->gop_size ) {
picture->pict_type = AV_PICTURE_TYPE_I;
myframe_key(picture);
gop_cnt = 0;
} else {
picture->pict_type = AV_PICTURE_TYPE_P;
myframe_interlaced(picture);
}
/* A return code of -2 is thrown by the put_frame
* when a image is buffered. For timelapse, we absolutely
* never want a frame buffered so we keep sending back the
* the same pic until it flushes or fails in a different way
*/
retcd = put_frame(ts1);
while ((retcd == -2) && (tlapse != TIMELAPSE_NONE)) {
retcd = put_frame(ts1);
cnt++;
if (cnt > 50) {
MOTPLS_LOG(ERR, TYPE_ENCODER, NO_ERRNO
,_("Excessive attempts to clear buffered packet"));
retcd = -1;
}
}
//non timelapse buffered is ok
if (retcd == -2) {
retcd = 0;
MOTPLS_LOG(DBG, TYPE_ENCODER, NO_ERRNO, _("Buffered packet"));
}
}
return retcd;
}
void cls_movie::reset_start_time(const struct timespec *ts1)
{
int64_t one_frame_interval = av_rescale_q(1,av_make_q(1, fps), strm_video->time_base);
if (one_frame_interval <= 0) {
one_frame_interval = 1;
}
base_pts = last_pts + one_frame_interval;
start_time.tv_sec = ts1->tv_sec;
start_time.tv_nsec = ts1->tv_nsec;
}
void cls_movie::init_container()
{
int codenbr;
size_t col_pos;
if (cam->cfg->movie_container == "test") {
MOTPLS_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "Running test of the various output formats.");
codenbr = cam->event_curr_nbr % 10;
if (codenbr == 1) {
container = "flv";
} else if (codenbr == 2) {
container = "ogg";
} else if (codenbr == 3) {
container = "webm";
} else if (codenbr == 4) {
container = "mp4";
} else if (codenbr == 5) {
container = "mkv";
} else if (codenbr == 6) {
container = "hevc";
} else if (codenbr == 7) {
container = "flv";
} else if (codenbr == 8) {
container = "ogg";
} else if (codenbr == 9) {
container = "webm";
} else {
container = "mkv";
}
} else {
container = cam->cfg->movie_container;
}
col_pos = container.find(":");
if (col_pos == std::string::npos) {
preferred_codec = "";
} else {
preferred_codec = container.substr(col_pos+1);
container = container.substr(0,col_pos);
}
}
void cls_movie::start_norm()
{
char tmp[PATH_MAX];
if (cam->cfg->movie_output == false) {
is_running = false;
return;
}
init_container();
mystrftime(cam, tmp, sizeof(tmp), cam->cfg->movie_filename.c_str(), nullptr);
movie_nm = tmp;
movie_dir = cam->cfg->target_dir;
if (container =="test") {
full_nm = movie_dir + "/" + container + "_" + movie_nm;
} else {
full_nm = movie_dir + "/" + movie_nm;
}
if (cam->imgs.size_high > 0) {
width = cam->imgs.width_high;
height = cam->imgs.height_high;
high_resolution = true;
netcam_data = cam->netcam_high;
} else {
width = cam->imgs.width;
height = cam->imgs.height;
high_resolution = false;
netcam_data = cam->netcam;
}
pkt = nullptr;
tlapse = TIMELAPSE_NONE;
fps = cam->lastrate;
start_time.tv_sec = cam->current_image->imgts.tv_sec;
start_time.tv_nsec = cam->current_image->imgts.tv_nsec;
last_pts = -1;
base_pts = 0;
gop_cnt = 0;
if (cam->cfg->movie_container == "test") {
test_mode = true;
} else {
test_mode = false;
}
motion_images = false;
passthrough = cam->movie_passthrough;
if (movie_open() < 0) {
MOTPLS_LOG(ERR, TYPE_EVENTS, NO_ERRNO
,_("Error initializing movie."));
return;
}
cam->filetype = FTYPE_MOVIE;
on_movie_start();
cam->motapp->dbse->exec(cam, full_nm, "movie_start");
is_running = true;
}
void cls_movie::start_motion()
{
char tmp[PATH_MAX];
ctx_image_data save_data;
if (cam->cfg->movie_output_motion == false) {
is_running = false;
return;
}
init_container();
memcpy(&save_data, cam->current_image, sizeof(ctx_image_data));
memcpy(cam->current_image, &cam->imgs.image_motion, sizeof(ctx_image_data));
mystrftime(cam, tmp, sizeof(tmp)
, cam->cfg->movie_filename.c_str(), nullptr);
memcpy(cam->current_image, &save_data, sizeof(ctx_image_data));
movie_nm.assign(tmp).append("m");
movie_dir = cam->cfg->target_dir;
if (container =="test") {
full_nm = movie_dir + "/" + container + "_" + movie_nm;
} else {
full_nm = movie_dir + "/" + movie_nm;
}
pkt = nullptr;
width = cam->imgs.width;
height = cam->imgs.height;
netcam_data = nullptr;
tlapse = TIMELAPSE_NONE;
fps = cam->lastrate;
start_time.tv_sec = cam->imgs.image_motion.imgts.tv_sec;
start_time.tv_nsec = cam->imgs.image_motion.imgts.tv_nsec;
last_pts = -1;
base_pts = 0;
gop_cnt = 0;
if (container == "test") {
test_mode = true;
} else {
test_mode = false;
}
motion_images = true;
passthrough = false;
high_resolution = false;
if (movie_open() < 0) {
MOTPLS_LOG(ERR, TYPE_EVENTS, NO_ERRNO
,_("Error initializing movie."));
return;
}
cam->filetype = FTYPE_MOVIE;
on_movie_start();
cam->motapp->dbse->exec(cam, full_nm, "movie_start");
is_running = true;
}
void cls_movie::start_timelapse()
{
char tmp[PATH_MAX];
mystrftime(cam, tmp, sizeof(tmp), cam->cfg->timelapse_filename.c_str(), nullptr);
movie_nm = tmp;
movie_dir = cam->cfg->target_dir.c_str();
full_nm = movie_dir + "/" + movie_nm;
if ((cam->imgs.size_high > 0) && (cam->movie_passthrough == false)) {
width = cam->imgs.width_high;
height = cam->imgs.height_high;
high_resolution = true;
} else {
width = cam->imgs.width;
height = cam->imgs.height;
high_resolution = false;
}
pkt = nullptr;
fps = cam->cfg->timelapse_fps;
start_time.tv_sec = cam->current_image->imgts.tv_sec;
start_time.tv_nsec = cam->current_image->imgts.tv_nsec;
last_pts = -1;
base_pts = 0;
test_mode = false;
gop_cnt = 0;
motion_images = false;
passthrough = false;
netcam_data = nullptr;
if (cam->cfg->timelapse_container == "mpg") {
MOTPLS_LOG(NTC, TYPE_EVENTS, NO_ERRNO, _("Timelapse using mpg container."));
MOTPLS_LOG(NTC, TYPE_EVENTS, NO_ERRNO, _("Events will be appended to file"));
tlapse = TIMELAPSE_APPEND;
container = "mpg";
} else {
MOTPLS_LOG(NTC, TYPE_EVENTS, NO_ERRNO, _("Timelapse using mkv container."));
MOTPLS_LOG(NTC, TYPE_EVENTS, NO_ERRNO, _("Events will be trigger new files"));
tlapse = TIMELAPSE_NEW;
container = "mkv";
}
if (movie_open() < 0) {
MOTPLS_LOG(ERR, TYPE_EVENTS, NO_ERRNO
,_("Error initializing movie."));
return;
}
cam->filetype = FTYPE_MOVIE_TIMELAPSE;
on_movie_start();
cam->motapp->dbse->exec(cam, full_nm, "movie_start");
is_running = true;
}
void cls_movie::start_extpipe()
{
char tmp[PATH_MAX];
if (cam->cfg->movie_extpipe_use == false) {
is_running = false;
return;
}
mystrftime(cam, tmp, sizeof(tmp), cam->cfg->movie_filename.c_str(), nullptr);
movie_nm = tmp;
movie_dir = cam->cfg->target_dir;
if (cam->cfg->movie_output) {
MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO
, _("Requested extpipe in addition to movie_output."));
MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO
, _("Adjusting file name of extpipe output."));
full_nm = movie_dir + "/" + movie_nm + "p";
} else {
full_nm = movie_dir + "/" + movie_nm;
}
if (mycreate_path(full_nm.c_str()) == -1) {
MOTPLS_LOG(ERR, TYPE_EVENTS, SHOW_ERRNO, _("create path failed"));
return;
}
memset(&tmp,0,PATH_MAX);
mystrftime(cam, tmp, sizeof(tmp)
, cam->cfg->movie_extpipe.c_str(), full_nm.c_str());
MOTPLS_LOG(NTC, TYPE_EVENTS, NO_ERRNO, _("extpipe cmd: %s"), tmp);
extpipe_stream = popen(tmp, "we");
if (extpipe_stream == nullptr) {
MOTPLS_LOG(ERR, TYPE_EVENTS, SHOW_ERRNO, _("popen failed"));
return;
}
setbuf(extpipe_stream, nullptr);
cam->filetype = FTYPE_MOVIE;
on_movie_start();
cam->motapp->dbse->exec(cam, full_nm, "movie_start");
is_running = true;
}
void cls_movie::start()
{
if (movie_type == "norm") {
start_norm();
} else if (movie_type == "motion") {
start_motion();
} else if (movie_type == "timelapse") {
start_timelapse();
} else if (movie_type == "extpipe") {
start_extpipe();
} else {
MOTPLS_LOG(ERR, TYPE_EVENTS, NO_ERRNO,_("Invalid movie type"));
}
}
void cls_movie::init_vars()
{
cb_st_ts.tv_nsec = 0;
cb_st_ts.tv_sec = 0;
cb_cr_ts = cb_st_ts;
cb_dur =0;
full_nm = "";
movie_nm = "";
movie_dir = "";
oc = nullptr;
strm_video = nullptr;
strm_audio = nullptr;
ctx_codec = nullptr;
codec = nullptr;
pkt = nullptr;
picture = nullptr;
opts = nullptr;
netcam_data = nullptr;
width = 640;
height = 480;
tlapse = TIMELAPSE_NONE;
fps = 5;
last_pts = 0;
base_pts = 0;
pass_audio_base = 0;
pass_video_base = 0;
test_mode = false;
gop_cnt = 5;
start_time.tv_nsec = 0;
start_time.tv_sec = 0;
high_resolution = false;
motion_images = false;
passthrough = false;
nal_info = nullptr;
nal_info_len = 0;
extpipe_stream = nullptr;
container = "";
preferred_codec = "";
}
cls_movie::cls_movie(cls_camera *p_cam, std::string pmovie_type)
{
cam = p_cam;
is_running = false;
movie_type = pmovie_type;
init_vars();
}
cls_movie::~cls_movie()
{
}