Files
motion/ffmpeg.c
MrDave f196a2c5af Whitespace and Tab cleanup
Remove trailing whitespace and convert tabs to four spaces.
No code changes.
2017-02-12 18:25:43 -07:00

974 lines
31 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
#define AVSTREAM_CODEC_PTR(avs_ptr) (avs_ptr->codec)
/****************************************************************************
* 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
}
/*********************************************/
/****************************************************************************
****************************************************************************
****************************************************************************/
/**
* timelapse_exists
* Determines whether the timelapse file exists
*
* Returns
* 0: File doesn't exist
* 1: File exists
*/
static int timelapse_exists(const char *fname){
FILE *file;
file = fopen(fname, "r");
if (file)
{
fclose(file);
return 1;
}
return 0;
}
static int 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;
}
/** locking callback for use with ffmpeg's av_lockmgr_register */
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;
}
/**
* get_oformat
* Obtains the output format used for the specified codec. For mpeg4 codecs,
* the format is avi; for mpeg1 codec, the format is mpeg. The filename has
* to be passed, because it gets the appropriate extension appended onto it.
*
* Returns
* AVOutputFormat pointer or NULL if any error happens.
*/
static AVOutputFormat *get_oformat(const char *codec, char *filename){
const char *ext;
AVOutputFormat *of = NULL;
/*
* Here, we use guess_format to automatically setup the codec information.
* If we are using msmpeg4, manually set that codec here.
* We also dynamically add the file extension to the filename here.
*/
if (strcmp(codec, "tlapse") == 0) {
ext = ".mpg";
of = av_guess_format ("mpeg2video", NULL, NULL);
if (of) of->video_codec = MY_CODEC_ID_MPEG2VIDEO;
} else if (strcmp(codec, "mpeg4") == 0) {
ext = ".avi";
of = av_guess_format("avi", NULL, NULL);
} else if (strcmp(codec, "msmpeg4") == 0) {
ext = ".avi";
of = av_guess_format("avi", NULL, NULL);
/* Manually override the codec id. */
if (of) of->video_codec = MY_CODEC_ID_MSMPEG4V2;
} else if (strcmp(codec, "swf") == 0) {
ext = ".swf";
of = av_guess_format("swf", NULL, NULL);
} else if (strcmp(codec, "flv") == 0) {
ext = ".flv";
of = av_guess_format("flv", NULL, NULL);
of->video_codec = MY_CODEC_ID_FLV1;
} else if (strcmp(codec, "ffv1") == 0) {
ext = ".avi";
of = av_guess_format("avi", NULL, NULL);
if (of) of->video_codec = MY_CODEC_ID_FFV1;
} else if (strcmp(codec, "mov") == 0) {
ext = ".mov";
of = av_guess_format("mov", NULL, NULL);
} else if (strcmp (codec, "mp4") == 0){
ext = ".mp4";
of = av_guess_format ("mp4", NULL, NULL);
of->video_codec = MY_CODEC_ID_H264;
} else if (strcmp (codec, "mkv") == 0){
ext = ".mkv";
of = av_guess_format ("matroska", NULL, NULL);
of->video_codec = MY_CODEC_ID_H264;
} else if (strcmp (codec, "hevc") == 0){
ext = ".mp4";
of = av_guess_format ("mp4", NULL, NULL);
of->video_codec = MY_CODEC_ID_HEVC;
} else {
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: ffmpeg_video_codec option value"
" %s is not supported", codec);
return NULL;
}
if (!of) {
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not guess format for %s", codec);
return NULL;
}
/* The 4 allows for ".avi" or ".mpg" to be appended. */
strncat(filename, ext, 4);
return of;
}
/**
* ffmpeg_cleanups
* Clean up ffmpeg struct if something was wrong.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_cleanups(struct ffmpeg *ffmpeg){
/* Close each codec */
if (ffmpeg->video_st) {
avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st));
}
free(ffmpeg->video_outbuf);
av_freep(&ffmpeg->picture);
avformat_free_context(ffmpeg->oc);
free(ffmpeg);
}
/**
* ffmpeg_put_frame
* Encodes and writes a video frame using the av_write_frame API. This is
* a helper function for ffmpeg_put_image and ffmpeg_put_other_image.
*
* Returns
* Number of bytes written or -1 if any error happens.
*/
int ffmpeg_put_frame(struct ffmpeg *ffmpeg, AVFrame *pic, const struct timeval *tv1){
/**
* Since the logic,return values and conditions changed so
* dramatically between versions, the encoding of the frame
* is 100% blocked based upon Libav/FFMpeg version
*/
#if (LIBAVFORMAT_VERSION_MAJOR >= 55) || ((LIBAVFORMAT_VERSION_MAJOR == 54) && (LIBAVFORMAT_VERSION_MINOR > 6))
int retcd;
int got_packet_ptr;
AVPacket pkt;
char errstr[128];
int64_t pts_interval;
av_init_packet(&pkt); /* Init static structure. */
if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) {
pkt.stream_index = ffmpeg->video_st->index;
pkt.flags |= AV_PKT_FLAG_KEY;
pkt.data = (uint8_t *)pic;
pkt.size = sizeof(AVPicture);
} else {
pkt.data = NULL;
pkt.size = 0;
retcd = avcodec_encode_video2(AVSTREAM_CODEC_PTR(ffmpeg->video_st),
&pkt, pic, &got_packet_ptr);
if (retcd < 0 ){
av_strerror(retcd, errstr, sizeof(errstr));
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_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(pkt);
return -2;
}
}
if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
retcd = timelapse_append(ffmpeg, pkt);
} else if (ffmpeg->tlapse == TIMELAPSE_NEW) {
retcd = av_write_frame(ffmpeg->oc, &pkt);
} else {
pts_interval = ((1000000L * (tv1->tv_sec - ffmpeg->start_time.tv_sec)) + tv1->tv_usec - ffmpeg->start_time.tv_usec) + 10000;
// MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: interval:%d img_sec:%d img_usec:%d strt_sec:%d strt_usec:%d "
// ,pts_interval,tv1->tv_sec,tv1->tv_usec,ffmpeg->start_time.tv_sec,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;
}
pkt.pts = av_rescale_q(pts_interval,(AVRational){1, 1000000L},ffmpeg->video_st->time_base);
if (pkt.pts <= ffmpeg->last_pts) pkt.pts = ffmpeg->last_pts + 1;
pkt.dts = pkt.pts;
retcd = av_write_frame(ffmpeg->oc, &pkt);
ffmpeg->last_pts = pkt.pts;
}
// MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: pts:%d dts:%d stream:%d interval %d",pkt.pts,pkt.dts,ffmpeg->video_st->time_base.den,pts_interval);
my_packet_unref(pkt);
if (retcd != 0) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame");
ffmpeg_cleanups(ffmpeg);
return -1;
}
return retcd;
#else // Old versions of Libav/FFmpeg
int retcd;
AVPacket pkt;
av_init_packet(&pkt); /* Init static structure. */
pkt.stream_index = ffmpeg->video_st->index;
if (ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE) {
// Raw video case.
pkt.size = sizeof(AVPicture);
pkt.data = (uint8_t *)pic;
pkt.flags |= AV_PKT_FLAG_KEY;
} else {
retcd = avcodec_encode_video(AVSTREAM_CODEC_PTR(ffmpeg->video_st),
ffmpeg->video_outbuf,
ffmpeg->video_outbuf_size, pic);
if (retcd < 0 ){
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error encoding video");
my_packet_unref(pkt);
return -1;
}
if (retcd == 0 ){
// No bytes encoded => buffered=>special handling
my_packet_unref(pkt);
return -2;
}
pkt.size = retcd;
pkt.data = ffmpeg->video_outbuf;
pkt.pts = AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->pts;
if (AVSTREAM_CODEC_PTR(ffmpeg->video_st)->coded_frame->key_frame)
pkt.flags |= AV_PKT_FLAG_KEY;
}
if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
retcd = timelapse_append(ffmpeg, pkt);
} else {
retcd = av_write_frame(ffmpeg->oc, &pkt);
}
my_packet_unref(pkt);
if (retcd != 0) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error while writing video frame");
ffmpeg_cleanups(ffmpeg);
return -1;
}
return retcd;
#endif
}
/**
* ffmpeg_prepare_frame
* Allocates and prepares a picture frame by setting up the U, Y and V pointers in
* the frame according to the passed pointers.
*
* Returns
* NULL If the allocation fails.
*
* The returned AVFrame pointer must be freed after use.
*/
AVFrame *ffmpeg_prepare_frame(struct ffmpeg *ffmpeg, unsigned char *y,
unsigned char *u, unsigned char *v)
{
AVFrame *picture;
picture = my_frame_alloc();
if (!picture) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc frame");
return NULL;
}
/* Take care of variable bitrate setting. */
if (ffmpeg->vbr)
picture->quality = ffmpeg->vbr;
/* Setup pointers and line widths. */
picture->data[0] = y;
picture->data[1] = u;
picture->data[2] = v;
picture->linesize[0] = ffmpeg->c->width;
picture->linesize[1] = ffmpeg->c->width / 2;
picture->linesize[2] = ffmpeg->c->width / 2;
picture->format = ffmpeg->c->pix_fmt;
picture->width = ffmpeg->c->width;
picture->height = ffmpeg->c->height;
return picture;
}
/**
* ffmpeg_avcodec_log
* Handle any logging output from the ffmpeg library avcodec.
*
* Parameters
* *ignoreme A pointer we will ignore
* errno_flag The error number value
* fmt Text message to be used for log entry in printf() format.
* ap List of variables to be used in formatted message text.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_avcodec_log(void *ignoreme ATTRIBUTE_UNUSED, int errno_flag, 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;
}
/* If the debug_level is correct then send the message to the motion logging routine.
* While it is not really desired to look for specific text in the message, there does
* not seem another option. The specific messages indicated are lost camera which we
* have our own message and UE golomb is not something that is possible for us to fix.
* It is caused by the stream sent from the source camera
*/
if(strstr(buf, "No route to host") == NULL){
if (strstr(buf, "Invalid UE golomb") != NULL) {
MOTION_LOG(DBG, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf);
} else if (errno_flag <= AV_LOG_ERROR) {
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf);
} else if (errno_flag <= AV_LOG_WARNING) {
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf);
} else if (errno_flag < AV_LOG_DEBUG){
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s: %s", buf);
}
}
}
#endif /* HAVE_FFMPEG */
/****************************************************************************
****************************************************************************
****************************************************************************/
/**
* ffmpeg_init
* Initializes for libavformat.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_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();
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_finalise(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 */
}
/**
* ffmpeg_open
* Opens an mpeg file using the new libavformat method. Both mpeg1
* and mpeg4 are supported. However, if the current ffmpeg version doesn't allow
* mpeg1 with non-standard framerate, the open will fail. Timelapse is a special
* case and is tested separately.
*
* Returns
* A new allocated ffmpeg struct or NULL if any error happens.
*/
struct ffmpeg *ffmpeg_open(const char *ffmpeg_video_codec, char *filename,
unsigned char *y, unsigned char *u, unsigned char *v,
int width, int height, int rate, int bps, int vbr, int tlapse,
const struct timeval *tv1)
{
#ifdef HAVE_FFMPEG
AVCodecContext *c;
AVCodec *codec;
struct ffmpeg *ffmpeg;
int retcd;
char errstr[128];
AVDictionary *opts = 0;
/*
* Allocate space for our ffmpeg structure. This structure contains all the
* codec and image information we need to generate movies.
*/
ffmpeg = mymalloc(sizeof(struct ffmpeg));
ffmpeg->tlapse = tlapse;
/* Store codec name in ffmpeg->codec, with buffer overflow check. */
snprintf(ffmpeg->codec, sizeof(ffmpeg->codec), "%s", ffmpeg_video_codec);
/* Allocation the output media context. */
ffmpeg->oc = avformat_alloc_context();
if (!ffmpeg->oc) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not allocate output context");
ffmpeg_cleanups(ffmpeg);
return NULL;
}
/* Setup output format */
if (ffmpeg->tlapse == TIMELAPSE_APPEND){
ffmpeg->oc->oformat = get_oformat("tlapse", filename);
} else {
ffmpeg->oc->oformat = get_oformat(ffmpeg_video_codec, filename);
}
if (!ffmpeg->oc->oformat) {
ffmpeg_cleanups(ffmpeg);
return NULL;
}
snprintf(ffmpeg->oc->filename, sizeof(ffmpeg->oc->filename), "%s", filename);
ffmpeg->video_st = NULL;
if (ffmpeg->oc->oformat->video_codec != MY_CODEC_ID_NONE) {
codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec);
if (!codec) {
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Codec %s not found", ffmpeg_video_codec);
ffmpeg_cleanups(ffmpeg);
return NULL;
}
ffmpeg->video_st = avformat_new_stream(ffmpeg->oc, codec);
if (!ffmpeg->video_st) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Could not alloc stream");
ffmpeg_cleanups(ffmpeg);
return NULL;
}
} else {
/* We did not get a proper video codec. */
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: Could not get the codec");
ffmpeg_cleanups(ffmpeg);
return NULL;
}
/* Only the newer codec and containers can handle the really fast FPS */
if (((strcmp(ffmpeg_video_codec, "msmpeg4") == 0) ||
(strcmp(ffmpeg_video_codec, "mpeg4") == 0) ||
(strcmp(ffmpeg_video_codec, "swf") == 0) ) && (rate >100)){
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_cleanups(ffmpeg);
return NULL;
}
ffmpeg->c = c = AVSTREAM_CODEC_PTR(ffmpeg->video_st);
c->codec_id = ffmpeg->oc->oformat->video_codec;
c->codec_type = AVMEDIA_TYPE_VIDEO;
c->bit_rate = bps;
c->width = width;
c->height = height;
c->time_base.num = 1;
c->time_base.den = rate;
c->gop_size = 12;
c->pix_fmt = MY_PIX_FMT_YUV420P;
c->max_b_frames = 0;
if (vbr > 100) vbr = 100;
if (c->codec_id == MY_CODEC_ID_H264 ||
c->codec_id == MY_CODEC_ID_HEVC){
if (vbr > 0) {
ffmpeg->vbr = (int)(( (100-vbr) * 51)/100);
} else {
ffmpeg->vbr = 28;
}
av_dict_set(&opts, "preset", "ultrafast", 0);
char crf[4];
snprintf(crf, 4, "%d",ffmpeg->vbr);
av_dict_set(&opts, "crf", crf, 0);
av_dict_set(&opts, "tune", "zerolatency", 0);
} else {
/* The selection of 8000 in the else is a subjective number based upon viewing output files */
if (vbr > 0){
ffmpeg->vbr =(int)(((100-vbr)*(100-vbr)*(100-vbr) * 8000) / 1000000) + 1;
c->flags |= CODEC_FLAG_QSCALE;
c->global_quality=ffmpeg->vbr;
}
}
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s vbr/crf for codec: %d", ffmpeg->vbr);
if (strcmp(ffmpeg_video_codec, "ffv1") == 0) c->strict_std_compliance = -2;
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
retcd = avcodec_open2(c, codec, &opts);
if (retcd < 0) {
if (codec->supported_framerates) {
const AVRational *fps = codec->supported_framerates;
while (fps->num) {
MOTION_LOG(INF, TYPE_ENCODER, NO_ERRNO, "%s Reported FPS Supported %d/%d", fps->num, fps->den);
fps++;
}
}
int chkrate = 1;
while ((chkrate < 36) && (retcd != 0)) {
c->time_base.den = chkrate;
retcd = avcodec_open2(c, codec, &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(&opts);
ffmpeg_cleanups(ffmpeg);
return NULL;
}
}
av_dict_free(&opts);
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s Selected Output FPS %d", c->time_base.den);
ffmpeg->last_pts = 0;
ffmpeg->video_st->time_base.num = 1;
ffmpeg->video_st->time_base.den = 1000;
if ((strcmp(ffmpeg_video_codec, "swf") == 0) ||
(ffmpeg->tlapse != TIMELAPSE_NONE) ) {
ffmpeg->video_st->time_base.num = 1;
ffmpeg->video_st->time_base.den = rate;
if ((rate > 50) && (strcmp(ffmpeg_video_codec, "swf") == 0)){
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "%s The FPS could be too high for the SWF container. Consider other choices.");
}
}
ffmpeg->video_outbuf = NULL;
if (!(ffmpeg->oc->oformat->flags & AVFMT_RAWPICTURE)) {
ffmpeg->video_outbuf_size = ffmpeg->c->width * 512;
ffmpeg->video_outbuf = mymalloc(ffmpeg->video_outbuf_size);
}
ffmpeg->picture = my_frame_alloc();
if (!ffmpeg->picture) {
MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "%s: could not alloc frame");
ffmpeg_cleanups(ffmpeg);
return NULL;
}
/* Set the frame data. */
ffmpeg->picture->data[0] = y;
ffmpeg->picture->data[1] = u;
ffmpeg->picture->data[2] = v;
ffmpeg->picture->linesize[0] = ffmpeg->c->width;
ffmpeg->picture->linesize[1] = ffmpeg->c->width / 2;
ffmpeg->picture->linesize[2] = ffmpeg->c->width / 2;
/* Open the output file, if needed. */
if ((timelapse_exists(filename) == 0) || (ffmpeg->tlapse != TIMELAPSE_APPEND)) {
if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&ffmpeg->oc->pb, filename, MY_FLAG_WRITE) < 0) {
if (errno == ENOENT) {
if (create_path(filename) == -1) {
ffmpeg_cleanups(ffmpeg);
return NULL;
}
if (avio_open(&ffmpeg->oc->pb, filename, MY_FLAG_WRITE) < 0) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: error opening file %s", filename);
ffmpeg_cleanups(ffmpeg);
return NULL;
}
/* Permission denied */
} else if (errno == EACCES) {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO,"%s: Permission denied. %s",filename);
ffmpeg_cleanups(ffmpeg);
return NULL;
} else {
MOTION_LOG(ERR, TYPE_ENCODER, SHOW_ERRNO, "%s: Error opening file %s", filename);
ffmpeg_cleanups(ffmpeg);
return NULL;
}
}
}
ffmpeg->start_time.tv_sec = tv1->tv_sec;
ffmpeg->start_time.tv_usec= tv1->tv_usec;
/* 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_cleanups(ffmpeg);
return NULL;
}
if (ffmpeg->tlapse == TIMELAPSE_APPEND) {
av_write_trailer(ffmpeg->oc);
avio_close(ffmpeg->oc->pb);
}
}
return ffmpeg;
#else /* No FFMPEG */
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
struct ffmpeg *ffmpeg;
ffmpeg = mymalloc(sizeof(struct ffmpeg));
ffmpeg_video_codec = ffmpeg_video_codec;
filename = filename;
y = y;
u = u;
v = v;
width = width;
height = height;
rate = rate;
bps = bps;
vbr = vbr;
tlapse = tlapse;
ffmpeg->dummy = 0;
tv1 = tv1;
return ffmpeg;
#endif /* HAVE_FFMPEG */
}
/**
* ffmpeg_close
* Closes a video file.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_close(struct ffmpeg *ffmpeg){
#ifdef HAVE_FFMPEG
if (ffmpeg->tlapse != TIMELAPSE_APPEND) {
av_write_trailer(ffmpeg->oc);
}
/* Close each codec */
if (ffmpeg->video_st) {
avcodec_close(AVSTREAM_CODEC_PTR(ffmpeg->video_st));
}
av_freep(&ffmpeg->picture);
free(ffmpeg->video_outbuf);
if (!(ffmpeg->oc->oformat->flags & AVFMT_NOFILE)) {
if (ffmpeg->tlapse != TIMELAPSE_APPEND) {
avio_close(ffmpeg->oc->pb);
}
}
avformat_free_context(ffmpeg->oc);
#endif // HAVE_FFMPEG
free(ffmpeg);
}
/**
* ffmpeg_put_other_image
* Puts an arbitrary picture defined by y, u and v.
*
* Returns
* Number of bytes written by ffmpeg_put_frame
* -1 if any error happens in ffmpeg_put_frame
* 0 if error allocating picture.
*/
int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y,
unsigned char *u, unsigned char *v, const struct timeval *tv1){
#ifdef HAVE_FFMPEG
AVFrame *picture;
int retcd = 0;
int cnt = 0;
/* Allocate the encoded raw picture. */
picture = ffmpeg_prepare_frame(ffmpeg, y, u, v);
if (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 = ffmpeg_put_frame(ffmpeg, picture, tv1);
while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) {
retcd = ffmpeg_put_frame(ffmpeg, picture, 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");
}
av_free(picture);
}
return retcd;
#else
ffmpeg = ffmpeg;
y = y;
u = u;
v = v;
tv1 = tv1;
return 0;
#endif // HAVE_FFMPEG
}
/**
* ffmpeg_put_image
* Puts the image pointed to by ffmpeg->picture.
*
* Returns
* value returned by ffmpeg_put_frame call.
*/
int ffmpeg_put_image(struct ffmpeg *ffmpeg, const struct timeval *tv1){
#ifdef HAVE_FFMPEG
/* 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
*/
int retcd;
int cnt = 0;
retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, tv1);
while ((retcd == -2) && (ffmpeg->tlapse != TIMELAPSE_NONE)) {
retcd = ffmpeg_put_frame(ffmpeg, ffmpeg->picture, 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
ffmpeg = ffmpeg;
tv1 = tv1;
return 0;
#endif // HAVE_FFMPEG
}