2004-03-04 23:05:54 +08:00
|
|
|
/*
|
|
|
|
* ZoneMinder MPEG class implementation, $Date$, $Revision$
|
2005-02-24 22:40:14 +08:00
|
|
|
* Copyright (C) 2003, 2004, 2005 Philip Coombes
|
2004-03-04 23:05:54 +08:00
|
|
|
*
|
|
|
|
* 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_mpeg.h"
|
|
|
|
|
|
|
|
#if HAVE_LIBAVCODEC
|
|
|
|
|
|
|
|
bool VideoStream::initialised = false;
|
|
|
|
|
2005-12-07 21:42:25 +08:00
|
|
|
VideoStream::MimeData VideoStream::mime_data[] = {
|
|
|
|
{ "asf", "video/x-ms-asf" },
|
|
|
|
{ "swf", "application/x-shockwave-flash" },
|
|
|
|
{ "mp4", "video/mp4" },
|
|
|
|
{ "move", "video/quicktime" }
|
|
|
|
};
|
|
|
|
|
2004-03-04 23:05:54 +08:00
|
|
|
void VideoStream::Initialise()
|
|
|
|
{
|
|
|
|
av_register_all();
|
|
|
|
initialised = true;
|
|
|
|
}
|
|
|
|
|
2004-05-05 17:18:14 +08:00
|
|
|
void VideoStream::SetupFormat( const char *p_filename, const char *p_format )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
filename = p_filename;
|
2004-05-05 17:18:14 +08:00
|
|
|
format = p_format;
|
2004-03-04 23:05:54 +08:00
|
|
|
|
|
|
|
/* auto detect the output format from the name. default is mpeg. */
|
|
|
|
of = guess_format( format, NULL, NULL);
|
|
|
|
if ( !of )
|
|
|
|
{
|
|
|
|
Warning(( "Could not deduce output format from file extension: using MPEG." ));
|
|
|
|
of = 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 )
|
|
|
|
{
|
|
|
|
Fatal(( "Memory error" ));
|
|
|
|
}
|
|
|
|
ofc->oformat = of;
|
2005-12-07 21:42:25 +08:00
|
|
|
snprintf( ofc->filename, sizeof(ofc->filename), "%s", filename );
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
|
2004-03-05 18:40:45 +08:00
|
|
|
void VideoStream::SetupCodec( int colours, int width, int height, int bitrate, int frame_rate )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
pf = (colours==1?PIX_FMT_GRAY8:PIX_FMT_RGB24);
|
|
|
|
|
|
|
|
/* add the video streams using the default format codecs
|
|
|
|
and initialize the codecs */
|
|
|
|
ost = NULL;
|
|
|
|
if (of->video_codec != CODEC_ID_NONE)
|
|
|
|
{
|
|
|
|
ost = av_new_stream(ofc, 0);
|
|
|
|
if (!ost)
|
|
|
|
{
|
|
|
|
Fatal(( "Could not alloc stream" ));
|
|
|
|
}
|
|
|
|
|
2005-10-04 04:38:40 +08:00
|
|
|
#if ZM_FFMPEG_CVS
|
|
|
|
AVCodecContext *c = ost->codec;
|
|
|
|
#else
|
2004-03-04 23:05:54 +08:00
|
|
|
AVCodecContext *c = &ost->codec;
|
2005-10-04 04:38:40 +08:00
|
|
|
#endif
|
|
|
|
|
2004-03-04 23:05:54 +08:00
|
|
|
c->codec_id = of->video_codec;
|
|
|
|
c->codec_type = CODEC_TYPE_VIDEO;
|
|
|
|
|
|
|
|
/* put sample parameters */
|
2004-03-05 18:40:45 +08:00
|
|
|
c->bit_rate = bitrate;
|
2005-10-17 05:32:37 +08:00
|
|
|
c->pix_fmt = PIX_FMT_YUV420P;
|
2004-03-04 23:05:54 +08:00
|
|
|
/* resolution must be a multiple of two */
|
|
|
|
c->width = width;
|
|
|
|
c->height = height;
|
2005-10-04 04:38:40 +08:00
|
|
|
#if ZM_FFMPEG_CVS
|
|
|
|
/* 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 = frame_rate;
|
|
|
|
c->time_base.num = 1;
|
|
|
|
#else
|
2004-03-04 23:05:54 +08:00
|
|
|
/* frames per second */
|
|
|
|
c->frame_rate = frame_rate;
|
|
|
|
c->frame_rate_base = 1;
|
2005-10-04 04:38:40 +08:00
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
c->gop_size = frame_rate/2; /* emit one intra frame every half second or so */
|
2005-10-17 05:32:37 +08:00
|
|
|
c->gop_size = 12;
|
2004-03-04 23:05:54 +08:00
|
|
|
if ( c->gop_size < 3 )
|
|
|
|
c->gop_size = 3;
|
2005-10-17 05:32:37 +08:00
|
|
|
// 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;
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VideoStream::SetParameters()
|
|
|
|
{
|
|
|
|
/* set the output parameters (must be done even if no
|
|
|
|
parameters). */
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( av_set_parameters(ofc, NULL) < 0 )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
Fatal(( "Invalid output format parameters" ));
|
|
|
|
}
|
|
|
|
//dump_format(ofc, 0, filename, 1);
|
|
|
|
}
|
|
|
|
|
2005-12-07 21:42:25 +08:00
|
|
|
const char *VideoStream::MimeType() const
|
|
|
|
{
|
|
|
|
for ( 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 );
|
|
|
|
}
|
|
|
|
|
2004-03-04 23:05:54 +08:00
|
|
|
void VideoStream::OpenStream()
|
|
|
|
{
|
|
|
|
/* now that all the parameters are set, we can open the
|
|
|
|
video codecs and allocate the necessary encode buffers */
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( ost )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
2005-10-04 04:38:40 +08:00
|
|
|
#if ZM_FFMPEG_CVS
|
|
|
|
AVCodecContext *c = ost->codec;
|
|
|
|
#else
|
2004-03-04 23:05:54 +08:00
|
|
|
AVCodecContext *c = &ost->codec;
|
2005-10-04 04:38:40 +08:00
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
|
|
|
|
/* find the video encoder */
|
|
|
|
AVCodec *codec = avcodec_find_encoder(c->codec_id);
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !codec )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
Fatal(( "codec not found" ));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* open the codec */
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( avcodec_open(c, codec) < 0 )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
2005-10-17 05:32:37 +08:00
|
|
|
Fatal(( "Could not open codec" ));
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate the encoded raw picture */
|
|
|
|
opicture = avcodec_alloc_frame();
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !opicture )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
Fatal(( "Could not allocate opicture" ));
|
|
|
|
}
|
|
|
|
int size = avpicture_get_size( c->pix_fmt, c->width, c->height);
|
|
|
|
uint8_t *opicture_buf = (uint8_t *)malloc(size);
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !opicture_buf )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
av_free(opicture);
|
|
|
|
Fatal(( "Could not allocate opicture" ));
|
|
|
|
}
|
2005-12-07 21:42:25 +08:00
|
|
|
avpicture_fill( (AVPicture *)opicture, opicture_buf, c->pix_fmt, c->width, c->height );
|
2004-03-04 23:05:54 +08:00
|
|
|
|
|
|
|
/* if the output format is not RGB24, then a temporary RGB24
|
|
|
|
picture is needed too. It is then converted to the required
|
|
|
|
output format */
|
|
|
|
tmp_opicture = NULL;
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( c->pix_fmt != pf )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
tmp_opicture = avcodec_alloc_frame();
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !tmp_opicture )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
Fatal(( "Could not allocate temporary opicture" ));
|
|
|
|
}
|
|
|
|
int size = avpicture_get_size( pf, c->width, c->height);
|
|
|
|
uint8_t *tmp_opicture_buf = (uint8_t *)malloc(size);
|
|
|
|
if (!tmp_opicture_buf)
|
|
|
|
{
|
2005-12-07 21:42:25 +08:00
|
|
|
av_free( tmp_opicture );
|
2004-03-04 23:05:54 +08:00
|
|
|
Fatal(( "Could not allocate temporary opicture" ));
|
|
|
|
}
|
2005-12-07 21:42:25 +08:00
|
|
|
avpicture_fill( (AVPicture *)tmp_opicture, tmp_opicture_buf, pf, c->width, c->height );
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* open the output file, if needed */
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !(of->flags & AVFMT_NOFILE) )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( url_fopen(&ofc->pb, filename, URL_WRONLY) < 0 )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
Fatal(( "Could not open '%s'", filename ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
video_outbuf = NULL;
|
2005-12-07 21:42:25 +08:00
|
|
|
if ( !(ofc->oformat->flags & AVFMT_RAWPICTURE) )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
/* 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 */
|
|
|
|
av_write_header(ofc);
|
|
|
|
}
|
|
|
|
|
2004-03-05 18:40:45 +08:00
|
|
|
VideoStream::VideoStream( const char *filename, const char *format, int bitrate, int frame_rate, int colours, int width, int height )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
if ( !initialised )
|
|
|
|
{
|
|
|
|
Initialise();
|
|
|
|
}
|
|
|
|
|
|
|
|
SetupFormat( filename, format );
|
2004-03-05 18:40:45 +08:00
|
|
|
SetupCodec( colours, width, height, bitrate, frame_rate );
|
2004-03-04 23:05:54 +08:00
|
|
|
SetParameters();
|
|
|
|
}
|
|
|
|
|
|
|
|
VideoStream::~VideoStream()
|
|
|
|
{
|
|
|
|
/* close each codec */
|
|
|
|
if (ost)
|
|
|
|
{
|
2005-10-04 04:38:40 +08:00
|
|
|
#if ZM_FFMPEG_CVS
|
|
|
|
avcodec_close(ost->codec);
|
|
|
|
#else
|
2004-03-04 23:05:54 +08:00
|
|
|
avcodec_close(&ost->codec);
|
2005-10-04 04:38:40 +08:00
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
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( int i = 0; i < ofc->nb_streams; i++)
|
|
|
|
{
|
|
|
|
av_freep(&ofc->streams[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(of->flags & AVFMT_NOFILE))
|
|
|
|
{
|
|
|
|
/* close the output file */
|
|
|
|
url_fclose(&ofc->pb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* free the stream */
|
|
|
|
av_free(ofc);
|
|
|
|
}
|
|
|
|
|
2004-03-08 18:33:19 +08:00
|
|
|
double VideoStream::EncodeFrame( uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp )
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
|
|
|
double pts = 0.0;
|
|
|
|
|
|
|
|
if (ost)
|
2004-03-08 18:33:19 +08:00
|
|
|
{
|
2005-10-04 04:38:40 +08:00
|
|
|
#if FFMPEG_VERSION_INT < 0x000409
|
2004-03-04 23:05:54 +08:00
|
|
|
pts = (double)ost->pts.val * ofc->pts_num / ofc->pts_den;
|
2004-09-23 22:20:54 +08:00
|
|
|
#else
|
|
|
|
pts = (double)ost->pts.val * ost->time_base.num / ost->time_base.den;
|
|
|
|
#endif
|
2004-03-08 18:33:19 +08:00
|
|
|
}
|
2004-03-04 23:05:54 +08:00
|
|
|
|
2005-10-04 04:38:40 +08:00
|
|
|
#if ZM_FFMPEG_CVS
|
|
|
|
AVCodecContext *c = ost->codec;
|
|
|
|
#else
|
2004-03-04 23:05:54 +08:00
|
|
|
AVCodecContext *c = &ost->codec;
|
2005-10-04 04:38:40 +08:00
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
if (c->pix_fmt != pf)
|
|
|
|
{
|
|
|
|
memcpy( tmp_opicture->data[0], buffer, buffer_size );
|
|
|
|
img_convert((AVPicture *)opicture, c->pix_fmt,
|
|
|
|
(AVPicture *)tmp_opicture, pf,
|
|
|
|
c->width, c->height);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memcpy( opicture->data[0], buffer, buffer_size );
|
|
|
|
}
|
|
|
|
AVFrame *opicture_ptr = opicture;
|
|
|
|
|
|
|
|
int ret = 0;
|
|
|
|
if (ofc->oformat->flags & AVFMT_RAWPICTURE)
|
|
|
|
{
|
2005-10-04 04:38:40 +08:00
|
|
|
#if FFMPEG_VERSION_INT < 0x000409
|
2004-03-04 23:05:54 +08:00
|
|
|
ret = av_write_frame(ofc, ost->index, (uint8_t *)opicture_ptr, sizeof(AVPicture));
|
2004-09-23 22:20:54 +08:00
|
|
|
#else
|
|
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
|
|
|
pkt.flags |= PKT_FLAG_KEY;
|
|
|
|
pkt.stream_index = ost->index;
|
|
|
|
pkt.data = (uint8_t *)opicture_ptr;
|
|
|
|
pkt.size = sizeof(AVPicture);
|
|
|
|
|
|
|
|
ret = av_write_frame(ofc, &pkt);
|
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2004-03-08 18:33:19 +08:00
|
|
|
if ( add_timestamp )
|
|
|
|
ost->pts.val = timestamp;
|
2004-03-04 23:05:54 +08:00
|
|
|
int out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, opicture_ptr);
|
2005-10-17 05:32:37 +08:00
|
|
|
if (out_size > 0)
|
2004-03-04 23:05:54 +08:00
|
|
|
{
|
2005-10-04 04:38:40 +08:00
|
|
|
#if FFMPEG_VERSION_INT < 0x000409
|
2004-03-04 23:05:54 +08:00
|
|
|
ret = av_write_frame(ofc, ost->index, video_outbuf, out_size);
|
2004-09-23 22:20:54 +08:00
|
|
|
#else
|
|
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
2005-10-17 05:32:37 +08:00
|
|
|
pkt.pts= av_rescale_q( c->coded_frame->pts, c->time_base, ost->time_base );
|
2004-09-23 22:20:54 +08:00
|
|
|
if(c->coded_frame->key_frame)
|
|
|
|
pkt.flags |= PKT_FLAG_KEY;
|
|
|
|
pkt.stream_index = ost->index;
|
|
|
|
pkt.data = video_outbuf;
|
|
|
|
pkt.size = out_size;
|
|
|
|
|
|
|
|
ret = av_write_frame(ofc, &pkt);
|
|
|
|
#endif
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ret != 0)
|
|
|
|
{
|
|
|
|
Fatal(( "Error while writing video frame" ));
|
|
|
|
}
|
2004-03-08 18:33:19 +08:00
|
|
|
return( pts );
|
2004-03-04 23:05:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif // HAVE_LIBAVCODEC
|