Files
motion/ffmpeg.c
MrDave a838d0527e Movie pts fix when using pre-capture
The previous method for calculating the PTS was to extract the time when
the particular function was being executed.  e.g. The time when the open
ffmpeg function was executed, the time that the image was encoded into the
movie.  This commit revises that behaviour to use instead the timevalues
that are attached to each image.  This is done to handle the precapture
situation in which all the precapture images were sent in at once at the
start of the event.  For the open function, the timevalue is obtained
from the oldest image in the ring buffer.
2016-12-10 17:26:31 -07:00

947 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;
}
/**
* ffmpeg_init
* Initializes for libavformat.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_init(void){
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);
}
}
void ffmpeg_finalise(void) {
avformat_network_deinit();
}
/**
* 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_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)
{
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;
}
/**
* 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_close
* Closes a video file.
*
* Returns
* Function returns nothing.
*/
void ffmpeg_close(struct ffmpeg *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);
free(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){
/* 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;
}
/**
* 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){
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;
}
/**
* 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);
}
}
}
#else /* No FFMPEG */
/****************************************************************************
****************************************************************************
****************************************************************************/
void ffmpeg_init(void){
MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO,"%s: No ffmpeg functionality included");
}
void ffmpeg_finalise(void) {}
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)
{
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;
return ffmpeg;
}
void ffmpeg_close(struct ffmpeg *ffmpeg){
free(ffmpeg);
}
int ffmpeg_put_other_image(struct ffmpeg *ffmpeg, unsigned char *y,
unsigned char *u, unsigned char *v){
ffmpeg = ffmpeg;
y = y;
u = u;
v = v;
return 0;
}
int ffmpeg_put_image(struct ffmpeg *ffmpeg){
ffmpeg = ffmpeg;
return 0;
}
#endif /* HAVE_FFMPEG */