zoneminder/src/zm_mpeg.cpp

489 lines
12 KiB
C++

/*
* ZoneMinder MPEG class implementation, $Date$, $Revision$
* Copyright (C) 2001-2008 Philip Coombes
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <string.h>
#include "zm.h"
#include "zm_rgb.h"
#include "zm_mpeg.h"
#if HAVE_LIBAVCODEC
extern "C" {
#include <libavutil/mathematics.h>
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 1, 0)
static int encode_frame(AVCodecContext *c, AVFrame *frame)
{
AVPacket pkt = { 0 };
int ret, got_output;
av_init_packet(&pkt);
av_init_packet(&pkt);
ret = avcodec_encode_video2(c, &pkt, frame, &got_output);
if (ret < 0)
return ret;
ret = pkt.size;
av_free_packet(&pkt);
return ret;
}
#endif
bool VideoStream::initialised = false;
VideoStream::MimeData VideoStream::mime_data[] = {
{ "asf", "video/x-ms-asf" },
{ "swf", "application/x-shockwave-flash" },
{ "flv", "video/x-flv" },
{ "mp4", "video/mp4" },
{ "move", "video/quicktime" }
};
void VideoStream::Initialise()
{
av_register_all();
initialised = true;
}
void VideoStream::SetupFormat( const char *p_filename, const char *p_format )
{
filename = p_filename;
format = p_format;
/* auto detect the output format from the name. default is mpeg. */
of = av_guess_format( format, NULL, NULL);
if ( !of )
{
Warning( "Could not deduce output format from file extension: using mpeg" );
of = av_guess_format("mpeg", NULL, NULL);
}
if ( !of )
{
Fatal( "Could not find suitable output format" );
}
/* allocate the output media context */
ofc = (AVFormatContext *)av_mallocz(sizeof(AVFormatContext));
if ( !ofc )
{
Panic( "Memory error" );
}
ofc->oformat = of;
snprintf( ofc->filename, sizeof(ofc->filename), "%s", filename );
}
void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate )
{
/* ffmpeg format matching */
switch(colours) {
case ZM_COLOUR_RGB24:
{
if(subpixelorder == ZM_SUBPIX_ORDER_BGR) {
/* BGR subpixel order */
pf = PIX_FMT_BGR24;
} else {
/* Assume RGB subpixel order */
pf = PIX_FMT_RGB24;
}
break;
}
case ZM_COLOUR_RGB32:
{
if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
/* ARGB subpixel order */
pf = PIX_FMT_ARGB;
} else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
/* ABGR subpixel order */
pf = PIX_FMT_ABGR;
} else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
/* BGRA subpixel order */
pf = PIX_FMT_BGRA;
} else {
/* Assume RGBA subpixel order */
pf = PIX_FMT_RGBA;
}
break;
}
case ZM_COLOUR_GRAY8:
pf = PIX_FMT_GRAY8;
break;
default:
Panic("Unexpected colours: %d",colours);
break;
}
/* add the video streams using the default format codecs
and initialize the codecs */
ost = NULL;
if (of->video_codec != CODEC_ID_NONE)
{
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 4, 0)
ost = av_new_stream(ofc, 0);
#else
ost = avformat_new_stream(ofc, 0);
#endif
if (!ost)
{
Panic( "Could not alloc stream" );
}
#if ZM_FFMPEG_SVN
AVCodecContext *c = ost->codec;
#else
AVCodecContext *c = &ost->codec;
#endif
c->codec_id = of->video_codec;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
c->codec_type = AVMEDIA_TYPE_VIDEO;
#else
c->codec_type = CODEC_TYPE_VIDEO;
#endif
/* put sample parameters */
c->bit_rate = bitrate;
c->pix_fmt = PIX_FMT_YUV420P;
/* resolution must be a multiple of two */
c->width = width;
c->height = height;
#if ZM_FFMPEG_SVN
/* time base: this is the fundamental unit of time (in seconds) in terms
of which frame timestamps are represented. for fixed-fps content,
timebase should be 1/framerate and timestamp increments should be
identically 1. */
//c->time_base.den = (int)(frame_rate*100);
//c->time_base.num = 100;
c->time_base.den = frame_rate;
c->time_base.num = 1;
#else
/* frames per second */
c->frame_rate = frame_rate;
c->frame_rate_base = 1;
#endif
//c->gop_size = frame_rate/2; /* emit one intra frame every half second or so */
c->gop_size = 12;
if ( c->gop_size < 3 )
c->gop_size = 3;
// some formats want stream headers to be seperate
if(!strcmp(ofc->oformat->name, "mp4") || !strcmp(ofc->oformat->name, "mov") || !strcmp(ofc->oformat->name, "3gp"))
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
}
void VideoStream::SetParameters()
{
/* set the output parameters (must be done even if no
parameters). */
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 4, 0)
if ( av_set_parameters(ofc, NULL) < 0 )
#else
if ( avformat_write_header(ofc, NULL) < 0 )
#endif
{
Panic( "Invalid output format parameters" );
}
//dump_format(ofc, 0, filename, 1);
}
const char *VideoStream::MimeType() const
{
for ( unsigned int i = 0; i < sizeof(mime_data)/sizeof(*mime_data); i++ )
{
if ( strcmp( format, mime_data[i].format ) == 0 )
{
return( mime_data[i].mime_type );
}
}
const char *mime_type = of->mime_type;
if ( !mime_type )
{
mime_type = "video/mpeg";
Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type );
}
return( mime_type );
}
void VideoStream::OpenStream()
{
/* now that all the parameters are set, we can open the
video codecs and allocate the necessary encode buffers */
if ( ost )
{
#if ZM_FFMPEG_SVN
AVCodecContext *c = ost->codec;
#else
AVCodecContext *c = &ost->codec;
#endif
/* find the video encoder */
AVCodec *codec = avcodec_find_encoder(c->codec_id);
if ( !codec )
{
Panic( "codec not found" );
}
/* open the codec */
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 7, 0)
if ( avcodec_open(c, codec) < 0 )
#else
if ( avcodec_open2(c, codec, 0) < 0 )
#endif
{
Panic( "Could not open codec" );
}
/* allocate the encoded raw picture */
opicture = avcodec_alloc_frame();
if ( !opicture )
{
Panic( "Could not allocate opicture" );
}
int size = avpicture_get_size( c->pix_fmt, c->width, c->height);
uint8_t *opicture_buf = (uint8_t *)av_malloc(size);
if ( !opicture_buf )
{
av_free(opicture);
Panic( "Could not allocate opicture" );
}
avpicture_fill( (AVPicture *)opicture, opicture_buf, c->pix_fmt, c->width, c->height );
/* if the output format is not identical to the input format, then a temporary
picture is needed too. It is then converted to the required
output format */
tmp_opicture = NULL;
if ( c->pix_fmt != pf )
{
tmp_opicture = avcodec_alloc_frame();
if ( !tmp_opicture )
{
Panic( "Could not allocate temporary opicture" );
}
int size = avpicture_get_size( pf, c->width, c->height);
uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc(size);
if (!tmp_opicture_buf)
{
av_free( tmp_opicture );
Panic( "Could not allocate temporary opicture" );
}
avpicture_fill( (AVPicture *)tmp_opicture, tmp_opicture_buf, pf, c->width, c->height );
}
}
/* open the output file, if needed */
if ( !(of->flags & AVFMT_NOFILE) )
{
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
if ( avio_open(&ofc->pb, filename, AVIO_FLAG_WRITE) < 0 )
#else
if ( url_fopen(&ofc->pb, filename, AVIO_FLAG_WRITE) < 0 )
#endif
{
Fatal( "Could not open '%s'", filename );
}
}
video_outbuf = NULL;
if ( !(ofc->oformat->flags & AVFMT_RAWPICTURE) )
{
/* allocate output buffer */
/* XXX: API change will be done */
video_outbuf_size = 200000;
video_outbuf = (uint8_t *)malloc(video_outbuf_size);
}
/* write the stream header, if any */
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 4, 0)
av_write_header(ofc);
#else
avformat_write_header(ofc, NULL);
#endif
}
VideoStream::VideoStream( const char *filename, const char *format, int bitrate, double frame_rate, int colours, int subpixelorder, int width, int height )
{
if ( !initialised )
{
Initialise();
}
SetupFormat( filename, format );
SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate );
SetParameters();
}
VideoStream::~VideoStream()
{
/* close each codec */
if (ost)
{
#if ZM_FFMPEG_SVN
avcodec_close(ost->codec);
#else
avcodec_close(&ost->codec);
#endif
av_free(opicture->data[0]);
av_free(opicture);
if (tmp_opicture)
{
av_free(tmp_opicture->data[0]);
av_free(tmp_opicture);
}
av_free(video_outbuf);
}
/* write the trailer, if any */
av_write_trailer(ofc);
/* free the streams */
for( unsigned int i = 0; i < ofc->nb_streams; i++)
{
av_freep(&ofc->streams[i]);
}
if (!(of->flags & AVFMT_NOFILE))
{
/* close the output file */
#if ZM_FFMPEG_SVN
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
avio_close(ofc->pb);
#else
url_fclose(ofc->pb);
#endif
#else
url_fclose(&ofc->pb);
#endif
}
/* free the stream */
av_free(ofc);
}
double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp )
{
#ifdef HAVE_LIBSWSCALE
static struct SwsContext *img_convert_ctx = 0;
#endif // HAVE_LIBSWSCALE
double pts = 0.0;
if (ost)
{
#if ZM_FFMPEG_048
pts = (double)ost->pts.val * ofc->pts_num / ofc->pts_den;
#else
pts = (double)ost->pts.val * ost->time_base.num / ost->time_base.den;
#endif
}
#if ZM_FFMPEG_SVN
AVCodecContext *c = ost->codec;
#else
AVCodecContext *c = &ost->codec;
#endif
if ( c->pix_fmt != pf )
{
memcpy( tmp_opicture->data[0], buffer, buffer_size );
#ifdef HAVE_LIBSWSCALE
if ( !img_convert_ctx )
{
img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL );
if ( !img_convert_ctx )
Panic( "Unable to initialise image scaling context" );
}
sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize );
#else // HAVE_LIBSWSCALE
Fatal("swscale is required for MPEG mode");
#endif // HAVE_LIBSWSCALE
}
else
{
memcpy( opicture->data[0], buffer, buffer_size );
}
AVFrame *opicture_ptr = opicture;
int ret = 0;
if ( ofc->oformat->flags & AVFMT_RAWPICTURE )
{
#if ZM_FFMPEG_048
ret = av_write_frame( ofc, ost->index, (uint8_t *)opicture_ptr, sizeof(AVPicture) );
#else
AVPacket pkt;
av_init_packet( &pkt );
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
pkt.flags |= AV_PKT_FLAG_KEY;
#else
pkt.flags |= PKT_FLAG_KEY;
#endif
pkt.stream_index = ost->index;
pkt.data = (uint8_t *)opicture_ptr;
pkt.size = sizeof(AVPicture);
ret = av_write_frame(ofc, &pkt);
#endif
}
else
{
if ( add_timestamp )
ost->pts.val = timestamp;
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(54, 1, 0) // NEXTIME
int out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, opicture_ptr);
#else
int out_size = encode_frame(c, opicture_ptr);
#endif
if ( out_size > 0 )
{
#if ZM_FFMPEG_048
ret = av_write_frame(ofc, ost->index, video_outbuf, out_size);
#else
AVPacket pkt;
av_init_packet(&pkt);
#if ZM_FFMPEG_049
pkt.pts = c->coded_frame->pts;
#else
pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, ost->time_base );
#endif
if(c->coded_frame->key_frame)
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
pkt.flags |= AV_PKT_FLAG_KEY;
#else
pkt.flags |= PKT_FLAG_KEY;
#endif
pkt.stream_index = ost->index;
pkt.data = video_outbuf;
pkt.size = out_size;
ret = av_write_frame( ofc, &pkt );
#endif
}
}
if ( ret != 0 )
{
Fatal( "Error %d while writing video frame: %s", ret, strerror( errno ) );
}
return( pts );
}
#endif // HAVE_LIBAVCODEC