/* * ZoneMinder MPEG class implementation, $Date$, $Revision$ * Copyright (C) 2003, 2004, 2005 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 #include #include "zm.h" #include "zm_mpeg.h" #if HAVE_LIBAVCODEC bool VideoStream::initialised = false; 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 = 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; snprintf(ofc->filename, sizeof(ofc->filename), "%s", filename); } void VideoStream::SetupCodec( int colours, int width, int height, int bitrate, int frame_rate ) { 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" )); } #if ZM_FFMPEG_CVS AVCodecContext *c = ost->codec; #else AVCodecContext *c = &ost->codec; #endif c->codec_id = of->video_codec; c->codec_type = CODEC_TYPE_VIDEO; /* put sample parameters */ c->bit_rate = bitrate; /* resolution must be a multiple of two */ c->width = width; c->height = height; #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 /* 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 = 30; if ( c->gop_size < 3 ) c->gop_size = 3; if (c->codec_id == CODEC_ID_MPEG1VIDEO || c->codec_id == CODEC_ID_MPEG2VIDEO) { /* just for testing, we also add B frames */ c->max_b_frames = 2; } } } void VideoStream::SetParameters() { /* set the output parameters (must be done even if no parameters). */ if (av_set_parameters(ofc, NULL) < 0) { Fatal(( "Invalid output format parameters" )); } //dump_format(ofc, 0, filename, 1); } 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_CVS AVCodecContext *c = ost->codec; #else AVCodecContext *c = &ost->codec; #endif /* find the video encoder */ AVCodec *codec = avcodec_find_encoder(c->codec_id); if (!codec) { Fatal(( "codec not found" )); } /* open the codec */ if (avcodec_open(c, codec) < 0) { Fatal(( "could not open codec" )); } /* allocate the encoded raw picture */ opicture = avcodec_alloc_frame(); if (!opicture) { 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); if (!opicture_buf) { av_free(opicture); Fatal(( "Could not allocate opicture" )); } avpicture_fill((AVPicture *)opicture, opicture_buf, c->pix_fmt, c->width, c->height); /* 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; if (c->pix_fmt != pf) { tmp_opicture = avcodec_alloc_frame(); if (!tmp_opicture) { 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) { av_free(tmp_opicture); Fatal(( "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 (url_fopen(&ofc->pb, filename, URL_WRONLY) < 0) { 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); } 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 )); } fprintf( stdout, "Content-type: %s\r\n\r\n", mime_type ); /* write the stream header, if any */ av_write_header(ofc); } VideoStream::VideoStream( const char *filename, const char *format, int bitrate, int frame_rate, int colours, int width, int height ) { if ( !initialised ) { Initialise(); } SetupFormat( filename, format ); SetupCodec( colours, width, height, bitrate, frame_rate ); SetParameters(); OpenStream(); } VideoStream::~VideoStream() { /* close each codec */ if (ost) { #if ZM_FFMPEG_CVS 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( 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); } double VideoStream::EncodeFrame( uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { double pts = 0.0; if (ost) { #if FFMPEG_VERSION_INT < 0x000409 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_CVS AVCodecContext *c = ost->codec; #else AVCodecContext *c = &ost->codec; #endif 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) { #if FFMPEG_VERSION_INT < 0x000409 ret = av_write_frame(ofc, ost->index, (uint8_t *)opicture_ptr, sizeof(AVPicture)); #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 } else { if ( add_timestamp ) ost->pts.val = timestamp; int out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, opicture_ptr); if (out_size != 0) { #if FFMPEG_VERSION_INT < 0x000409 ret = av_write_frame(ofc, ost->index, video_outbuf, out_size); #else AVPacket pkt; av_init_packet(&pkt); pkt.pts = c->coded_frame->pts; 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 } } if (ret != 0) { Fatal(( "Error while writing video frame" )); } return( pts ); } #endif // HAVE_LIBAVCODEC