mirror of
https://github.com/Motion-Project/motion.git
synced 2026-02-05 04:21:32 -05:00
This commit resolves some of the netcam v4l2 items and a few other small issues 1. Use the correct return codes for the new function rtsp_decode_video 2. Silence the notices on reconnecting 3. Revise the v4l2 to video4linux2 for old libav versions 4. Add additional options for the input_format 5. Do not permit users to specify zero for camera height or width 6. Eliminates pthread detection 7. Revise the ffmpeg messages to INF 8. Adds H264 palette option
930 lines
30 KiB
C
930 lines
30 KiB
C
/*
|
|
*
|
|
* ffmpeg.c
|
|
*
|
|
* This software is distributed under the GNU Public License version 2
|
|
* See also the file 'COPYING'.
|
|
*
|
|
* The contents of this file has been derived from output_example.c
|
|
* and apiexample.c from the FFmpeg distribution.
|
|
*
|
|
* This file has been modified so that only major versions greater than
|
|
* 53 are supported.
|
|
* Note that while the conditions are based upon LIBAVFORMAT, not all of the changes are
|
|
* specific to libavformat.h. Some changes could be related to other components of ffmpeg.
|
|
* This is for simplicity. The avformat version has historically changed at the same time
|
|
* as the other components so it is easier to have a single version number to track rather
|
|
* than the particular version numbers which are associated with each component.
|
|
* The libav variant also has different apis with the same major/minor version numbers.
|
|
* As such, it is occasionally necessary to look at the microversion number. Numbers
|
|
* greater than 100 for micro version indicate ffmpeg whereas numbers less than 100
|
|
* indicate libav
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
#include "ffmpeg.h"
|
|
#include "motion.h"
|
|
|
|
#ifdef HAVE_FFMPEG
|
|
|
|
/****************************************************************************
|
|
* The section below is the "my" section of functions.
|
|
* These are designed to be extremely simple version specific
|
|
* variants of the libav functions.
|
|
****************************************************************************/
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6))
|
|
|
|
#define MY_FLAG_READ AVIO_FLAG_READ
|
|
#define MY_FLAG_WRITE AVIO_FLAG_WRITE
|
|
#define MY_FLAG_READ_WRITE AVIO_FLAG_READ_WRITE
|
|
|
|
#else //Older versions
|
|
|
|
#define MY_FLAG_READ URL_RDONLY
|
|
#define MY_FLAG_WRITE URL_WRONLY
|
|
#define MY_FLAG_READ_WRITE URL_RDWR
|
|
|
|
#endif
|
|
/*********************************************/
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 56)
|
|
|
|
#define MY_CODEC_ID_MSMPEG4V2 AV_CODEC_ID_MSMPEG4V2
|
|
#define MY_CODEC_ID_FLV1 AV_CODEC_ID_FLV1
|
|
#define MY_CODEC_ID_FFV1 AV_CODEC_ID_FFV1
|
|
#define MY_CODEC_ID_NONE AV_CODEC_ID_NONE
|
|
#define MY_CODEC_ID_MPEG2VIDEO AV_CODEC_ID_MPEG2VIDEO
|
|
#define MY_CODEC_ID_H264 AV_CODEC_ID_H264
|
|
#define MY_CODEC_ID_HEVC AV_CODEC_ID_HEVC
|
|
|
|
#else
|
|
|
|
#define MY_CODEC_ID_MSMPEG4V2 CODEC_ID_MSMPEG4V2
|
|
#define MY_CODEC_ID_FLV1 CODEC_ID_FLV1
|
|
#define MY_CODEC_ID_FFV1 CODEC_ID_FFV1
|
|
#define MY_CODEC_ID_NONE CODEC_ID_NONE
|
|
#define MY_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO
|
|
#define MY_CODEC_ID_H264 CODEC_ID_H264
|
|
#define MY_CODEC_ID_HEVC CODEC_ID_H264
|
|
|
|
#endif
|
|
|
|
/*********************************************/
|
|
AVFrame *my_frame_alloc(void){
|
|
AVFrame *pic;
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 55)
|
|
pic = av_frame_alloc();
|
|
#else
|
|
pic = avcodec_alloc_frame();
|
|
#endif
|
|
return pic;
|
|
}
|
|
/*********************************************/
|
|
void my_frame_free(AVFrame *frame){
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 55)
|
|
av_frame_free(&frame);
|
|
#else
|
|
av_freep(&frame);
|
|
#endif
|
|
}
|
|
/*********************************************/
|
|
int my_image_get_buffer_size(enum MyPixelFormat pix_fmt, int width, int height){
|
|
int retcd = 0;
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 57)
|
|
int align = 1;
|
|
retcd = av_image_get_buffer_size(pix_fmt, width, height, align);
|
|
#else
|
|
retcd = avpicture_get_size(pix_fmt, width, height);
|
|
#endif
|
|
return retcd;
|
|
}
|
|
/*********************************************/
|
|
int my_image_copy_to_buffer(AVFrame *frame, uint8_t *buffer_ptr, enum MyPixelFormat pix_fmt,int width, int height,int dest_size){
|
|
int retcd = 0;
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 57)
|
|
int align = 1;
|
|
retcd = av_image_copy_to_buffer((uint8_t *)buffer_ptr,dest_size
|
|
,(const uint8_t * const*)frame,frame->linesize,pix_fmt,width,height,align);
|
|
#else
|
|
retcd = avpicture_layout((const AVPicture*)frame,pix_fmt,width,height
|
|
,(unsigned char *)buffer_ptr,dest_size);
|
|
#endif
|
|
return retcd;
|
|
}
|
|
/*********************************************/
|
|
int my_image_fill_arrays(AVFrame *frame,uint8_t *buffer_ptr,enum MyPixelFormat pix_fmt,int width,int height){
|
|
int retcd = 0;
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 57)
|
|
int align = 1;
|
|
retcd = av_image_fill_arrays(
|
|
frame->data
|
|
,frame->linesize
|
|
,buffer_ptr
|
|
,pix_fmt
|
|
,width
|
|
,height
|
|
,align
|
|
);
|
|
#else
|
|
retcd = avpicture_fill(
|
|
(AVPicture *)frame
|
|
,buffer_ptr
|
|
,pix_fmt
|
|
,width
|
|
,height);
|
|
#endif
|
|
return retcd;
|
|
}
|
|
/*********************************************/
|
|
void my_packet_unref(AVPacket pkt){
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 57)
|
|
av_packet_unref(&pkt);
|
|
#else
|
|
av_free_packet(&pkt);
|
|
#endif
|
|
}
|
|
/*********************************************/
|
|
void my_avcodec_close(AVCodecContext *codec_context){
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41))
|
|
avcodec_free_context(&codec_context);
|
|
#else
|
|
avcodec_close(codec_context);
|
|
#endif
|
|
}
|
|
/*********************************************/
|
|
|
|
/****************************************************************************
|
|
****************************************************************************
|
|
****************************************************************************/
|
|
static int ffmpeg_timelapse_exists(const char *fname){
|
|
FILE *file;
|
|
file = fopen(fname, "r");
|
|
if (file)
|
|
{
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_timelapse_append(struct ffmpeg *ffmpeg, AVPacket pkt){
|
|
FILE *file;
|
|
|
|
file = fopen(ffmpeg->oc->filename, "a");
|
|
if (!file) return -1;
|
|
|
|
fwrite(pkt.data,1,pkt.size,file);
|
|
|
|
fclose(file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_lockmgr_cb(void **arg, enum AVLockOp op){
|
|
pthread_mutex_t *mutex = *arg;
|
|
int err;
|
|
|
|
switch (op) {
|
|
case AV_LOCK_CREATE:
|
|
mutex = malloc(sizeof(*mutex));
|
|
if (!mutex)
|
|
return AVERROR(ENOMEM);
|
|
if ((err = pthread_mutex_init(mutex, NULL))) {
|
|
free(mutex);
|
|
return AVERROR(err);
|
|
}
|
|
*arg = mutex;
|
|
return 0;
|
|
case AV_LOCK_OBTAIN:
|
|
if ((err = pthread_mutex_lock(mutex)))
|
|
return AVERROR(err);
|
|
|
|
return 0;
|
|
case AV_LOCK_RELEASE:
|
|
if ((err = pthread_mutex_unlock(mutex)))
|
|
return AVERROR(err);
|
|
|
|
return 0;
|
|
case AV_LOCK_DESTROY:
|
|
if (mutex)
|
|
pthread_mutex_destroy(mutex);
|
|
free(mutex);
|
|
*arg = NULL;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void ffmpeg_free_context(struct ffmpeg *ffmpeg){
|
|
|
|
if (ffmpeg->picture != NULL){
|
|
my_frame_free(ffmpeg->picture);
|
|
ffmpeg->picture = NULL;
|
|
}
|
|
|
|
if (ffmpeg->ctx_codec != NULL){
|
|
my_avcodec_close(ffmpeg->ctx_codec);
|
|
ffmpeg->ctx_codec = NULL;
|
|
}
|
|
|
|
if (ffmpeg->oc != NULL){
|
|
avformat_free_context(ffmpeg->oc);
|
|
ffmpeg->oc = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
static int ffmpeg_get_oformat(struct ffmpeg *ffmpeg){
|
|
|
|
/* Only the newer codec and containers can handle the really fast FPS */
|
|
if (((strcmp(ffmpeg->codec_name, "msmpeg4") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "mpeg4") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "swf") == 0) ) && (ffmpeg->fps >50)){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s The frame rate specified is too high for the ffmpeg movie type specified. Choose a different ffmpeg container or lower framerate. ");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
if (ffmpeg->tlapse == TIMELAPSE_APPEND){
|
|
ffmpeg->oc->oformat = av_guess_format ("mpeg2video", NULL, NULL);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MPEG2VIDEO;
|
|
strncat(ffmpeg->filename, ".mpg", 4);
|
|
if (!ffmpeg->oc->oformat) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: ffmpeg_video_codec option value %s is not supported", ffmpeg->codec_name);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "mpeg4") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".avi", 4);
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "msmpeg4") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".avi", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MSMPEG4V2;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "swf") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("swf", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".swf", 4);
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "flv") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("flv", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".flv", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FLV1;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "ffv1") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".avi", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FFV1;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "mov") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("mov", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".mov", 4);
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "mp4") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".mp4", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "mkv") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("matroska", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".mkv", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264;
|
|
}
|
|
|
|
if (strcmp(ffmpeg->codec_name, "hevc") == 0) {
|
|
ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL);
|
|
strncat(ffmpeg->filename, ".mp4", 4);
|
|
if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_HEVC;
|
|
}
|
|
|
|
//Check for valid results
|
|
if (!ffmpeg->oc->oformat) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: codec option value %s is not supported", ffmpeg->codec_name);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
if (ffmpeg->oc->oformat->video_codec == MY_CODEC_ID_NONE) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not get the codec");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_encode_video(struct ffmpeg *ffmpeg){
|
|
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41))
|
|
//ffmpeg version 3.1 and after
|
|
int retcd = 0;
|
|
char errstr[128];
|
|
|
|
retcd = avcodec_send_frame(ffmpeg->ctx_codec, ffmpeg->picture);
|
|
if (retcd < 0 ){
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error sending frame for encoding:%s",errstr);
|
|
return -1;
|
|
}
|
|
retcd = avcodec_receive_packet(ffmpeg->ctx_codec, &ffmpeg->pkt);
|
|
if (retcd == AVERROR(EAGAIN)){
|
|
//Buffered packet. Throw special return code
|
|
my_packet_unref(ffmpeg->pkt);
|
|
return -2;
|
|
}
|
|
if (retcd < 0 ){
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error receiving encoded packet video:%s",errstr);
|
|
//Packet is freed upon failure of encoding
|
|
return -1;
|
|
}
|
|
if (ffmpeg->picture->key_frame == 1)
|
|
ffmpeg->pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
return 0;
|
|
|
|
#elif (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6))
|
|
|
|
int retcd = 0;
|
|
char errstr[128];
|
|
int got_packet_ptr;
|
|
|
|
retcd = avcodec_encode_video2(ffmpeg->ctx_codec, &ffmpeg->pkt, ffmpeg->picture, &got_packet_ptr);
|
|
if (retcd < 0 ){
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error encoding video:%s",errstr);
|
|
//Packet is freed upon failure of encoding
|
|
return -1;
|
|
}
|
|
if (got_packet_ptr == 0){
|
|
//Buffered packet. Throw special return code
|
|
my_packet_unref(ffmpeg->pkt);
|
|
return -2;
|
|
}
|
|
|
|
if (ffmpeg->picture->key_frame == 1)
|
|
ffmpeg->pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
int retcd = 0;
|
|
uint8_t *video_outbuf;
|
|
int video_outbuf_size;
|
|
|
|
video_outbuf_size = (ffmpeg->ctx_codec->width +16) * (ffmpeg->ctx_codec->height +16) * 1;
|
|
video_outbuf = mymalloc(video_outbuf_size);
|
|
|
|
retcd = avcodec_encode_video(ffmpeg->video_st->codec, video_outbuf, video_outbuf_size, ffmpeg->picture);
|
|
if (retcd < 0 ){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error encoding video");
|
|
my_packet_unref(ffmpeg->pkt);
|
|
return -1;
|
|
}
|
|
if (retcd == 0 ){
|
|
// No bytes encoded => buffered=>special handling
|
|
my_packet_unref(ffmpeg->pkt);
|
|
return -2;
|
|
}
|
|
|
|
ffmpeg->pkt.size = retcd;
|
|
ffmpeg->pkt.data = video_outbuf;
|
|
|
|
if (ffmpeg->picture->key_frame == 1)
|
|
ffmpeg->pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
free(video_outbuf);
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
static int ffmpeg_set_pts(struct ffmpeg *ffmpeg, const struct timeval *tv1){
|
|
|
|
int64_t pts_interval;
|
|
|
|
if (ffmpeg->tlapse != TIMELAPSE_NONE) {
|
|
ffmpeg->last_pts++;
|
|
ffmpeg->pkt.pts = ffmpeg->last_pts;
|
|
ffmpeg->pkt.dts = ffmpeg->last_pts;
|
|
} else {
|
|
pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec);
|
|
if (pts_interval < 0){
|
|
/* This can occur when we have pre-capture frames. Reset start time of video. */
|
|
ffmpeg->start_time.tv_sec = tv1->tv_sec ;
|
|
ffmpeg->start_time.tv_usec = tv1->tv_usec ;
|
|
pts_interval = 1;
|
|
}
|
|
ffmpeg->pkt.pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base) + 1;
|
|
|
|
if (ffmpeg->test_mode == 1){
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: ms interval %d PTS %d timebase %d-%d",pts_interval,ffmpeg->pkt.pts,ffmpeg->video_st->time_base.num,ffmpeg->video_st->time_base.den);
|
|
}
|
|
|
|
if (ffmpeg->pkt.pts <= ffmpeg->last_pts){
|
|
//We have a problem with our motion loop timing and sending frames. Increment by just 1 to at least keep frame in movie.
|
|
ffmpeg->pkt.pts = ffmpeg->last_pts + 1;
|
|
if (ffmpeg->test_mode == 1){
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: BAD TIMING!! PTS reset to incremental counter new PTS %d ",ffmpeg->pkt.pts);
|
|
}
|
|
}
|
|
ffmpeg->pkt.dts = ffmpeg->pkt.pts;
|
|
ffmpeg->last_pts = ffmpeg->pkt.pts;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_set_quality(struct ffmpeg *ffmpeg){
|
|
|
|
char crf[4];
|
|
|
|
ffmpeg->opts = 0;
|
|
if (ffmpeg->vbr > 100) ffmpeg->vbr = 100;
|
|
if (ffmpeg->ctx_codec->codec_id == MY_CODEC_ID_H264 ||
|
|
ffmpeg->ctx_codec->codec_id == MY_CODEC_ID_HEVC){
|
|
if (ffmpeg->vbr > 0) {
|
|
ffmpeg->vbr = (int)(( (100-ffmpeg->vbr) * 51)/100);
|
|
} else {
|
|
ffmpeg->vbr = 28;
|
|
}
|
|
snprintf(crf, 4, "%d",ffmpeg->vbr);
|
|
av_dict_set(&ffmpeg->opts, "preset", "ultrafast", 0);
|
|
av_dict_set(&ffmpeg->opts, "tune", "zerolatency", 0);
|
|
av_dict_set(&ffmpeg->opts, "crf", crf, 0);
|
|
} else {
|
|
/* The selection of 8000 in the else is a subjective number based upon viewing output files */
|
|
if (ffmpeg->vbr > 0){
|
|
ffmpeg->vbr =(int)(((100-ffmpeg->vbr)*(100-ffmpeg->vbr)*(100-ffmpeg->vbr) * 8000) / 1000000) + 1;
|
|
ffmpeg->ctx_codec->flags |= CODEC_FLAG_QSCALE;
|
|
ffmpeg->ctx_codec->global_quality=ffmpeg->vbr;
|
|
}
|
|
}
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s vbr/crf for codec: %d", ffmpeg->vbr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_set_codec(struct ffmpeg *ffmpeg){
|
|
|
|
int retcd;
|
|
char errstr[128];
|
|
int chkrate;
|
|
|
|
ffmpeg->codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec);
|
|
if (!ffmpeg->codec) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Codec %s not found", ffmpeg->codec_name);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41))
|
|
//If we provide the codec to this, it results in a memory leak. ffmpeg ticket: 5714
|
|
ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, NULL);
|
|
if (!ffmpeg->video_st) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not alloc stream");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
ffmpeg->ctx_codec = avcodec_alloc_context3(ffmpeg->codec);
|
|
if (ffmpeg->ctx_codec == NULL) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, "%s: Failed to allocate decoder!");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
#else
|
|
ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, ffmpeg->codec);
|
|
if (!ffmpeg->video_st) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not alloc stream");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
ffmpeg->ctx_codec = ffmpeg->video_st->codec;
|
|
#endif
|
|
|
|
|
|
if (ffmpeg->tlapse != TIMELAPSE_NONE) {
|
|
ffmpeg->ctx_codec->gop_size = 1;
|
|
} else {
|
|
if (ffmpeg->fps <= 5){
|
|
ffmpeg->ctx_codec->gop_size = 1;
|
|
} else if (ffmpeg->fps > 30){
|
|
ffmpeg->ctx_codec->gop_size = 15;
|
|
} else {
|
|
ffmpeg->ctx_codec->gop_size = (ffmpeg->fps / 2);
|
|
}
|
|
}
|
|
|
|
/* 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 ((ffmpeg->tlapse == TIMELAPSE_NONE) && (ffmpeg->fps <= 5)){
|
|
if ((strcmp(ffmpeg->codec_name, "msmpeg4") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "flv") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "mov") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "mp4") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "hevc") == 0) ||
|
|
(strcmp(ffmpeg->codec_name, "mpeg4") == 0)) {
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s: Low fps. Encoding %d frames into a %d frames container.", ffmpeg->fps, 10);
|
|
ffmpeg->fps = 10;
|
|
}
|
|
}
|
|
|
|
ffmpeg->ctx_codec->codec_id = ffmpeg->oc->oformat->video_codec;
|
|
ffmpeg->ctx_codec->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
ffmpeg->ctx_codec->bit_rate = ffmpeg->bps;
|
|
ffmpeg->ctx_codec->width = ffmpeg->width;
|
|
ffmpeg->ctx_codec->height = ffmpeg->height;
|
|
ffmpeg->ctx_codec->time_base.num = 1;
|
|
ffmpeg->ctx_codec->time_base.den = ffmpeg->fps;
|
|
ffmpeg->ctx_codec->pix_fmt = MY_PIX_FMT_YUV420P;
|
|
ffmpeg->ctx_codec->max_b_frames = 0;
|
|
if (strcmp(ffmpeg->codec_name, "ffv1") == 0){
|
|
ffmpeg->ctx_codec->strict_std_compliance = -2;
|
|
ffmpeg->ctx_codec->level = 3;
|
|
}
|
|
ffmpeg->ctx_codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
|
|
|
retcd = ffmpeg_set_quality(ffmpeg);
|
|
if (retcd < 0){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Unable to set quality");
|
|
return -1;
|
|
}
|
|
|
|
retcd = avcodec_open2(ffmpeg->ctx_codec, ffmpeg->codec, &ffmpeg->opts);
|
|
if (retcd < 0) {
|
|
if (ffmpeg->codec->supported_framerates) {
|
|
const AVRational *fps = ffmpeg->codec->supported_framerates;
|
|
while (fps->num) {
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s Reported FPS Supported %d/%d", fps->num, fps->den);
|
|
fps++;
|
|
}
|
|
}
|
|
chkrate = 1;
|
|
while ((chkrate < 36) && (retcd != 0)) {
|
|
ffmpeg->ctx_codec->time_base.den = chkrate;
|
|
retcd = avcodec_open2(ffmpeg->ctx_codec, ffmpeg->codec, &ffmpeg->opts);
|
|
chkrate++;
|
|
}
|
|
if (retcd < 0){
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not open codec %s",errstr);
|
|
av_dict_free(&ffmpeg->opts);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
av_dict_free(&ffmpeg->opts);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ffmpeg_set_stream(struct ffmpeg *ffmpeg){
|
|
|
|
#if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41))
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
retcd = avcodec_parameters_from_context(ffmpeg->video_st->codecpar,ffmpeg->ctx_codec);
|
|
if (retcd < 0) {
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Failed to copy decoder parameters!: %s", errstr);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
ffmpeg->video_st->time_base = (AVRational){1, ffmpeg->fps};
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int ffmpeg_set_picture(struct ffmpeg *ffmpeg){
|
|
|
|
ffmpeg->picture = my_frame_alloc();
|
|
if (!ffmpeg->picture) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: could not alloc frame");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
/* Take care of variable bitrate setting. */
|
|
if (ffmpeg->vbr)
|
|
ffmpeg->picture->quality = ffmpeg->vbr;
|
|
|
|
ffmpeg->picture->linesize[0] = ffmpeg->ctx_codec->width;
|
|
ffmpeg->picture->linesize[1] = ffmpeg->ctx_codec->width / 2;
|
|
ffmpeg->picture->linesize[2] = ffmpeg->ctx_codec->width / 2;
|
|
|
|
ffmpeg->picture->format = ffmpeg->ctx_codec->pix_fmt;
|
|
ffmpeg->picture->width = ffmpeg->ctx_codec->width;
|
|
ffmpeg->picture->height = ffmpeg->ctx_codec->height;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int ffmpeg_set_outputfile(struct ffmpeg *ffmpeg){
|
|
|
|
int retcd;
|
|
char errstr[128];
|
|
|
|
snprintf(ffmpeg->oc->filename, sizeof(ffmpeg->oc->filename), "%s", ffmpeg->filename);
|
|
/* Open the output file, if needed. */
|
|
if ((ffmpeg_timelapse_exists(ffmpeg->filename) == 0) || (ffmpeg->tlapse != TIMELAPSE_APPEND)) {
|
|
if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
|
|
if (avio_open(&ffmpeg->oc->pb, ffmpeg->filename, MY_FLAG_WRITE) < 0) {
|
|
if (errno == ENOENT) {
|
|
if (create_path(ffmpeg->filename) == -1) {
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
if (avio_open(&ffmpeg->oc->pb, ffmpeg->filename, MY_FLAG_WRITE) < 0) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: error opening file %s", ffmpeg->filename);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
/* Permission denied */
|
|
} else if (errno == EACCES) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO,"%s: Permission denied. %s",ffmpeg->filename);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
} else {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error opening file %s", ffmpeg->filename);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write the stream header, For the TIMELAPSE_APPEND
|
|
* we write the data via standard file I/O so we close the
|
|
* items here
|
|
*/
|
|
retcd = avformat_write_header(ffmpeg->oc, NULL);
|
|
if (retcd < 0){
|
|
av_strerror(retcd, errstr, sizeof(errstr));
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not write ffmpeg header %s",errstr);
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
|
|
av_write_trailer(ffmpeg->oc);
|
|
avio_close(ffmpeg->oc->pb);
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int ffmpeg_put_frame(struct ffmpeg *ffmpeg, const struct timeval *tv1){
|
|
int retcd;
|
|
|
|
av_init_packet(&ffmpeg->pkt);
|
|
ffmpeg->pkt.data = NULL;
|
|
ffmpeg->pkt.size = 0;
|
|
|
|
retcd = ffmpeg_encode_video(ffmpeg);
|
|
if (retcd != 0) return retcd;
|
|
|
|
retcd = ffmpeg_set_pts(ffmpeg, tv1);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error while setting PTS");
|
|
return -1;
|
|
}
|
|
|
|
if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
|
|
retcd = ffmpeg_timelapse_append(ffmpeg, ffmpeg->pkt);
|
|
} else {
|
|
retcd = av_write_frame(ffmpeg->oc, &ffmpeg->pkt);
|
|
}
|
|
my_packet_unref(ffmpeg->pkt);
|
|
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Error while writing video frame");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
return retcd;
|
|
|
|
}
|
|
|
|
void ffmpeg_avcodec_log(void *ignoreme ATTRIBUTE_UNUSED, int errno_flag ATTRIBUTE_UNUSED, const char *fmt, va_list vl){
|
|
|
|
char buf[1024];
|
|
char *end;
|
|
|
|
/* Flatten the message coming in from avcodec. */
|
|
vsnprintf(buf, sizeof(buf), fmt, vl);
|
|
end = buf + strlen(buf);
|
|
if (end > buf && end[-1] == '\n')
|
|
{
|
|
*--end = 0;
|
|
}
|
|
|
|
//We put the avcodec messages to INF level since their error are not necessarily our errors.
|
|
if (errno_flag <= AV_LOG_WARNING){
|
|
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf);
|
|
}
|
|
}
|
|
|
|
|
|
#endif /* HAVE_FFMPEG */
|
|
|
|
/****************************************************************************
|
|
****************************************************************************
|
|
****************************************************************************/
|
|
|
|
void ffmpeg_global_init(void){
|
|
#ifdef HAVE_FFMPEG
|
|
int ret;
|
|
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,
|
|
"%s: ffmpeg libavcodec version %d.%d.%d"
|
|
" libavformat version %d.%d.%d"
|
|
, LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO
|
|
, LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO);
|
|
|
|
av_register_all();
|
|
avcodec_register_all();
|
|
avformat_network_init();
|
|
avdevice_register_all();
|
|
av_log_set_callback((void *)ffmpeg_avcodec_log);
|
|
|
|
ret = av_lockmgr_register(ffmpeg_lockmgr_cb);
|
|
if (ret < 0)
|
|
{
|
|
MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "%s: av_lockmgr_register failed (%d)", ret);
|
|
exit(1);
|
|
}
|
|
|
|
#else /* No FFMPEG */
|
|
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
|
|
|
|
#endif /* HAVE_FFMPEG */
|
|
}
|
|
|
|
void ffmpeg_global_deinit(void) {
|
|
#ifdef HAVE_FFMPEG
|
|
|
|
avformat_network_deinit();
|
|
|
|
#else /* No FFMPEG */
|
|
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
|
|
|
|
#endif /* HAVE_FFMPEG */
|
|
}
|
|
|
|
int ffmpeg_open(struct ffmpeg *ffmpeg){
|
|
|
|
#ifdef HAVE_FFMPEG
|
|
|
|
int retcd;
|
|
|
|
ffmpeg->oc = avformat_alloc_context();
|
|
if (!ffmpeg->oc) {
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not allocate output context");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
retcd = ffmpeg_get_oformat(ffmpeg);
|
|
if (retcd < 0 ) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, "%s: Could not get codec!");
|
|
ffmpeg_free_context(ffmpeg);
|
|
return -1;
|
|
}
|
|
|
|
retcd = ffmpeg_set_codec(ffmpeg);
|
|
if (retcd < 0 ) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, "%s: Failed to allocate codec!");
|
|
return -1;
|
|
}
|
|
|
|
retcd = ffmpeg_set_stream(ffmpeg);
|
|
if (retcd < 0){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not set the stream");
|
|
return -1;
|
|
}
|
|
|
|
retcd = ffmpeg_set_picture(ffmpeg);
|
|
if (retcd < 0){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not set the stream");
|
|
return -1;
|
|
}
|
|
|
|
retcd = ffmpeg_set_outputfile(ffmpeg);
|
|
if (retcd < 0){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not set the stream");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
#else /* No FFMPEG */
|
|
|
|
if (ffmpeg) {
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
|
|
}else {
|
|
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
|
|
}
|
|
return -1;
|
|
|
|
#endif /* HAVE_FFMPEG */
|
|
|
|
}
|
|
|
|
void ffmpeg_close(struct ffmpeg *ffmpeg){
|
|
#ifdef HAVE_FFMPEG
|
|
|
|
if (ffmpeg != NULL) {
|
|
if (ffmpeg->tlapse != TIMELAPSE_APPEND) {
|
|
av_write_trailer(ffmpeg->oc);
|
|
}
|
|
if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
|
|
if (ffmpeg->tlapse != TIMELAPSE_APPEND) {
|
|
avio_close(ffmpeg->oc->pb);
|
|
}
|
|
}
|
|
ffmpeg_free_context(ffmpeg);
|
|
}
|
|
|
|
#else
|
|
if (ffmpeg != NULL) free(ffmpeg);
|
|
#endif // HAVE_FFMPEG
|
|
}
|
|
|
|
int ffmpeg_put_image(struct ffmpeg *ffmpeg, unsigned char *image, const struct timeval *tv1){
|
|
#ifdef HAVE_FFMPEG
|
|
int retcd = 0;
|
|
int cnt = 0;
|
|
|
|
if (ffmpeg->picture) {
|
|
|
|
/* Setup pointers and line widths. */
|
|
ffmpeg->picture->data[0] = image;
|
|
ffmpeg->picture->data[1] = image + (ffmpeg->ctx_codec->width * ffmpeg->ctx_codec->height);
|
|
ffmpeg->picture->data[2] = ffmpeg->picture->data[1] + ((ffmpeg->ctx_codec->width * ffmpeg->ctx_codec->height) / 4);
|
|
|
|
ffmpeg->gop_cnt ++;
|
|
if (ffmpeg->gop_cnt == ffmpeg->ctx_codec->gop_size ){
|
|
ffmpeg->picture->pict_type = AV_PICTURE_TYPE_I;
|
|
ffmpeg->picture->key_frame = 1;
|
|
ffmpeg->gop_cnt = 0;
|
|
} else {
|
|
ffmpeg->picture->pict_type = AV_PICTURE_TYPE_P;
|
|
ffmpeg->picture->key_frame = 0;
|
|
}
|
|
|
|
/* 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 = ffmpeg_put_frame(ffmpeg, tv1);
|
|
while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) {
|
|
retcd = ffmpeg_put_frame(ffmpeg, tv1);
|
|
cnt++;
|
|
if (cnt > 50){
|
|
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Excessive attempts to clear buffered packet");
|
|
retcd = -1;
|
|
}
|
|
}
|
|
//non timelapse buffered is ok
|
|
if (retcd == -2){
|
|
retcd = 0;
|
|
MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: Buffered packet");
|
|
}
|
|
}
|
|
|
|
return retcd;
|
|
|
|
#else
|
|
if (ffmpeg && image && tv1) {
|
|
MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: No ffmpeg support");
|
|
}
|
|
return 0;
|
|
#endif // HAVE_FFMPEG
|
|
}
|
|
|