2013-03-17 07:45:21 +08:00
//
// ZoneMinder Ffmpeg Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $
// 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 "zm.h"
# if HAVE_LIBAVFORMAT
# include "zm_ffmpeg_camera.h"
2014-03-23 08:08:39 +08:00
# ifndef AV_ERROR_MAX_STRING_SIZE
# define AV_ERROR_MAX_STRING_SIZE 64
# endif
2015-05-18 08:18:54 +08:00
# ifdef SOLARIS
# include <sys/errno.h> // for ESRCH
# include <signal.h>
# include <pthread.h>
# endif
2014-05-18 02:41:22 +08:00
FfmpegCamera : : FfmpegCamera ( int p_id , const std : : string & p_path , const std : : string & p_method , const std : : string & p_options , int p_width , int p_height , int p_colours , int p_brightness , int p_contrast , int p_hue , int p_colour , bool p_capture ) :
2013-03-17 07:45:21 +08:00
Camera ( p_id , FFMPEG_SRC , p_width , p_height , p_colours , ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR ( p_colours ) , p_brightness , p_contrast , p_hue , p_colour , p_capture ) ,
2014-05-05 19:29:12 +08:00
mPath ( p_path ) ,
2014-05-18 02:41:22 +08:00
mMethod ( p_method ) ,
2014-05-05 19:29:12 +08:00
mOptions ( p_options )
2013-03-17 07:45:21 +08:00
{
if ( capture )
{
Initialise ( ) ;
}
mFormatContext = NULL ;
mVideoStreamId = - 1 ;
mCodecContext = NULL ;
mCodec = NULL ;
mRawFrame = NULL ;
mFrame = NULL ;
frameCount = 0 ;
2014-03-22 00:50:49 +08:00
mIsOpening = false ;
mCanCapture = false ;
mOpenStart = 0 ;
2014-10-11 22:58:47 +08:00
mReopenThread = 0 ;
2013-03-17 07:45:21 +08:00
# if HAVE_LIBSWSCALE
mConvertContext = NULL ;
# endif
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
if ( colours = = ZM_COLOUR_RGB32 ) {
subpixelorder = ZM_SUBPIX_ORDER_RGBA ;
2015-11-03 08:58:23 +08:00
imagePixFormat = AV_PIX_FMT_RGBA ;
2013-03-17 07:45:21 +08:00
} else if ( colours = = ZM_COLOUR_RGB24 ) {
subpixelorder = ZM_SUBPIX_ORDER_RGB ;
2015-11-03 08:58:23 +08:00
imagePixFormat = AV_PIX_FMT_RGB24 ;
2013-03-17 07:45:21 +08:00
} else if ( colours = = ZM_COLOUR_GRAY8 ) {
subpixelorder = ZM_SUBPIX_ORDER_NONE ;
2015-11-03 08:58:23 +08:00
imagePixFormat = AV_PIX_FMT_GRAY8 ;
2013-03-17 07:45:21 +08:00
} else {
Panic ( " Unexpected colours: %d " , colours ) ;
}
}
FfmpegCamera : : ~ FfmpegCamera ( )
{
2014-03-22 00:50:49 +08:00
CloseFfmpeg ( ) ;
2013-03-17 07:45:21 +08:00
if ( capture )
{
Terminate ( ) ;
}
}
void FfmpegCamera : : Initialise ( )
{
if ( logDebugging ( ) )
av_log_set_level ( AV_LOG_DEBUG ) ;
else
av_log_set_level ( AV_LOG_QUIET ) ;
av_register_all ( ) ;
}
void FfmpegCamera : : Terminate ( )
{
}
int FfmpegCamera : : PrimeCapture ( )
{
Info ( " Priming capture from %s " , mPath . c_str ( ) ) ;
2014-03-22 00:50:49 +08:00
if ( OpenFfmpeg ( ) ! = 0 ) {
ReopenFfmpeg ( ) ;
}
return 0 ;
}
int FfmpegCamera : : PreCapture ( )
{
// Nothing to do here
return ( 0 ) ;
}
int FfmpegCamera : : Capture ( Image & image )
{
if ( ! mCanCapture ) {
return - 1 ;
}
2014-10-11 22:58:47 +08:00
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
if ( mReopenThread ! = 0 ) {
void * retval = 0 ;
int ret ;
2015-04-09 04:15:54 +08:00
ret = pthread_join ( mReopenThread , & retval ) ;
2014-10-11 22:58:47 +08:00
if ( ret ! = 0 ) {
Error ( " Could not join reopen thread. " ) ;
}
Info ( " Successfully reopened stream. " ) ;
mReopenThread = 0 ;
}
2014-03-22 00:50:49 +08:00
AVPacket packet ;
uint8_t * directbuffer ;
/* Request a writeable buffer of the target image */
directbuffer = image . WriteBuffer ( width , height , colours , subpixelorder ) ;
if ( directbuffer = = NULL ) {
Error ( " Failed requesting writeable buffer for the captured image. " ) ;
return ( - 1 ) ;
}
2014-05-05 19:29:12 +08:00
2014-03-22 00:50:49 +08:00
int frameComplete = false ;
while ( ! frameComplete )
{
int avResult = av_read_frame ( mFormatContext , & packet ) ;
if ( avResult < 0 )
{
2014-03-22 02:42:03 +08:00
char errbuf [ AV_ERROR_MAX_STRING_SIZE ] ;
av_strerror ( avResult , errbuf , AV_ERROR_MAX_STRING_SIZE ) ;
if (
// Check if EOF.
( avResult = = AVERROR_EOF | | ( mFormatContext - > pb & & mFormatContext - > pb - > eof_reached ) ) | |
// Check for Connection failure.
( avResult = = - 110 )
)
2014-03-22 00:50:49 +08:00
{
2014-03-22 02:42:03 +08:00
Info ( " av_read_frame returned \" %s \" . Reopening stream. " , errbuf ) ;
2014-03-22 00:50:49 +08:00
ReopenFfmpeg ( ) ;
}
2014-03-22 02:42:03 +08:00
Error ( " Unable to read packet from stream %d: error %d \" %s \" . " , packet . stream_index , avResult , errbuf ) ;
2014-03-22 00:50:49 +08:00
return ( - 1 ) ;
}
Debug ( 5 , " Got packet from stream %d " , packet . stream_index ) ;
if ( packet . stream_index = = mVideoStreamId )
{
2015-05-29 23:38:02 +08:00
# if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
2014-03-22 00:50:49 +08:00
if ( avcodec_decode_video2 ( mCodecContext , mRawFrame , & frameComplete , & packet ) < 0 )
# else
if ( avcodec_decode_video ( mCodecContext , mRawFrame , & frameComplete , packet . data , packet . size ) < 0 )
# endif
Fatal ( " Unable to decode frame at frame %d " , frameCount ) ;
Debug ( 4 , " Decoded video packet at frame %d " , frameCount ) ;
if ( frameComplete )
{
Debug ( 3 , " Got frame %d " , frameCount ) ;
2016-03-02 22:03:55 +08:00
# if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays ( mFrame - > data , mFrame - > linesize ,
directbuffer , imagePixFormat , width , height , 1 ) ;
# else
avpicture_fill ( ( AVPicture * ) mFrame , directbuffer ,
imagePixFormat , width , height ) ;
# endif
2014-03-22 00:50:49 +08:00
# if HAVE_LIBSWSCALE
if ( mConvertContext = = NULL ) {
2015-11-04 13:24:39 +08:00
mConvertContext = sws_getContext ( mCodecContext - > width , mCodecContext - > height , mCodecContext - > pix_fmt , width , height , imagePixFormat , SWS_BICUBIC , NULL , NULL , NULL ) ;
2014-03-22 00:50:49 +08:00
if ( mConvertContext = = NULL )
Fatal ( " Unable to create conversion context for %s " , mPath . c_str ( ) ) ;
}
if ( sws_scale ( mConvertContext , mRawFrame - > data , mRawFrame - > linesize , 0 , mCodecContext - > height , mFrame - > data , mFrame - > linesize ) < 0 )
Fatal ( " Unable to convert raw format %u to target format %u at frame %d " , mCodecContext - > pix_fmt , imagePixFormat , frameCount ) ;
# else // HAVE_LIBSWSCALE
Fatal ( " You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras " ) ;
# endif // HAVE_LIBSWSCALE
frameCount + + ;
}
}
2015-11-04 12:30:14 +08:00
# if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
av_packet_unref ( & packet ) ;
# else
2014-03-22 00:50:49 +08:00
av_free_packet ( & packet ) ;
2015-11-04 12:30:14 +08:00
# endif
2014-03-22 00:50:49 +08:00
}
return ( 0 ) ;
}
2014-05-05 19:29:12 +08:00
2014-03-22 00:50:49 +08:00
int FfmpegCamera : : PostCapture ( )
{
// Nothing to do here
return ( 0 ) ;
}
int FfmpegCamera : : OpenFfmpeg ( ) {
Debug ( 2 , " OpenFfmpeg called. " ) ;
mOpenStart = time ( NULL ) ;
mIsOpening = true ;
2013-03-17 07:45:21 +08:00
// Open the input, not necessarily a file
2015-05-29 23:38:02 +08:00
# if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0)
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling av_open_input_file " ) ;
2013-03-17 07:45:21 +08:00
if ( av_open_input_file ( & mFormatContext , mPath . c_str ( ) , NULL , 0 , NULL ) ! = 0 )
# else
2014-10-12 13:47:27 +08:00
// Handle options
2014-05-05 19:29:12 +08:00
AVDictionary * opts = 0 ;
StringVector opVect = split ( Options ( ) , " , " ) ;
2014-05-18 02:41:22 +08:00
// Set transport method as specified by method field, rtpUni is default
if ( Method ( ) = = " rtpMulti " )
2014-05-18 03:48:08 +08:00
opVect . push_back ( " rtsp_transport=udp_multicast " ) ;
2014-05-18 02:41:22 +08:00
else if ( Method ( ) = = " rtpRtsp " )
2014-05-18 03:48:08 +08:00
opVect . push_back ( " rtsp_transport=tcp " ) ;
2014-05-18 02:41:22 +08:00
else if ( Method ( ) = = " rtpRtspHttp " )
2014-05-18 03:48:08 +08:00
opVect . push_back ( " rtsp_transport=http " ) ;
2014-05-18 02:41:22 +08:00
2014-05-15 21:06:01 +08:00
Debug ( 2 , " Number of Options: %d " , opVect . size ( ) ) ;
2014-05-05 21:54:13 +08:00
for ( size_t i = 0 ; i < opVect . size ( ) ; i + + )
2014-05-05 19:29:12 +08:00
{
StringVector parts = split ( opVect [ i ] , " = " ) ;
2014-05-15 21:06:01 +08:00
if ( parts . size ( ) > 1 ) {
2014-05-17 10:33:33 +08:00
parts [ 0 ] = trimSpaces ( parts [ 0 ] ) ;
parts [ 1 ] = trimSpaces ( parts [ 1 ] ) ;
if ( av_dict_set ( & opts , parts [ 0 ] . c_str ( ) , parts [ 1 ] . c_str ( ) , 0 ) = = 0 ) {
Debug ( 2 , " set option %d '%s' to '%s' " , i , parts [ 0 ] . c_str ( ) , parts [ 1 ] . c_str ( ) ) ;
2014-05-15 21:06:01 +08:00
}
else
{
2014-05-17 10:33:33 +08:00
Warning ( " Error trying to set option %d '%s' to '%s' " , i , parts [ 0 ] . c_str ( ) , parts [ 1 ] . c_str ( ) ) ;
2014-05-15 21:06:01 +08:00
}
}
2014-03-22 00:50:49 +08:00
}
Debug ( 1 , " Calling avformat_open_input " ) ;
mFormatContext = avformat_alloc_context ( ) ;
mFormatContext - > interrupt_callback . callback = FfmpegInterruptCallback ;
mFormatContext - > interrupt_callback . opaque = this ;
2014-05-18 02:41:22 +08:00
2014-10-12 13:47:27 +08:00
if ( avformat_open_input ( & mFormatContext , mPath . c_str ( ) , NULL , & opts ) ! = 0 )
2013-03-17 07:45:21 +08:00
# endif
2014-03-22 00:50:49 +08:00
{
mIsOpening = false ;
Error ( " Unable to open input %s due to: %s " , mPath . c_str ( ) , strerror ( errno ) ) ;
return - 1 ;
}
mIsOpening = false ;
Debug ( 1 , " Opened input " ) ;
2013-03-17 07:45:21 +08:00
2014-03-22 00:50:49 +08:00
// Locate stream info from avformat_open_input
2015-05-29 23:38:02 +08:00
# if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling av_find_stream_info " ) ;
2013-03-17 07:45:21 +08:00
if ( av_find_stream_info ( mFormatContext ) < 0 )
# else
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling avformat_find_stream_info " ) ;
2013-03-17 07:45:21 +08:00
if ( avformat_find_stream_info ( mFormatContext , 0 ) < 0 )
# endif
Fatal ( " Unable to find stream info from %s due to: %s " , mPath . c_str ( ) , strerror ( errno ) ) ;
2015-05-29 23:38:02 +08:00
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Got stream info " ) ;
2013-03-17 07:45:21 +08:00
// Find first video stream present
mVideoStreamId = - 1 ;
for ( unsigned int i = 0 ; i < mFormatContext - > nb_streams ; i + + )
{
2015-05-29 23:38:02 +08:00
# if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
2013-03-17 07:45:21 +08:00
if ( mFormatContext - > streams [ i ] - > codec - > codec_type = = AVMEDIA_TYPE_VIDEO )
# else
if ( mFormatContext - > streams [ i ] - > codec - > codec_type = = CODEC_TYPE_VIDEO )
# endif
{
mVideoStreamId = i ;
break ;
}
}
if ( mVideoStreamId = = - 1 )
Fatal ( " Unable to locate video stream in %s " , mPath . c_str ( ) ) ;
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Found video stream " ) ;
2013-03-17 07:45:21 +08:00
mCodecContext = mFormatContext - > streams [ mVideoStreamId ] - > codec ;
// Try and get the codec from the codec context
if ( ( mCodec = avcodec_find_decoder ( mCodecContext - > codec_id ) ) = = NULL )
Fatal ( " Can't find codec for video stream from %s " , mPath . c_str ( ) ) ;
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Found decoder " ) ;
2013-03-17 07:45:21 +08:00
// Open the codec
2015-05-29 23:38:02 +08:00
# if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling avcodec_open " ) ;
2013-03-17 07:45:21 +08:00
if ( avcodec_open ( mCodecContext , mCodec ) < 0 )
# else
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling avcodec_open2 " ) ;
2013-03-17 07:45:21 +08:00
if ( avcodec_open2 ( mCodecContext , mCodec , 0 ) < 0 )
# endif
Fatal ( " Unable to open codec for video stream from %s " , mPath . c_str ( ) ) ;
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Opened codec " ) ;
2013-03-17 07:45:21 +08:00
// Allocate space for the native video frame
2015-05-29 23:38:02 +08:00
# if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
mRawFrame = av_frame_alloc ( ) ;
# else
2013-03-17 07:45:21 +08:00
mRawFrame = avcodec_alloc_frame ( ) ;
2015-05-29 23:38:02 +08:00
# endif
2013-03-17 07:45:21 +08:00
// Allocate space for the converted video frame
2015-05-29 23:38:02 +08:00
# if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
mFrame = av_frame_alloc ( ) ;
# else
2013-03-17 07:45:21 +08:00
mFrame = avcodec_alloc_frame ( ) ;
2015-05-29 23:38:02 +08:00
# endif
2014-03-22 00:50:49 +08:00
if ( mRawFrame = = NULL | | mFrame = = NULL )
Fatal ( " Unable to allocate frame for %s " , mPath . c_str ( ) ) ;
Debug ( 1 , " Allocated frames " ) ;
2016-03-02 22:03:55 +08:00
# if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
int pSize = av_image_get_buffer_size ( imagePixFormat , width , height , 1 ) ;
# else
2014-03-22 00:50:49 +08:00
int pSize = avpicture_get_size ( imagePixFormat , width , height ) ;
2016-03-02 22:03:55 +08:00
# endif
2014-03-22 00:50:49 +08:00
if ( ( unsigned int ) pSize ! = imagesize ) {
Fatal ( " Image size mismatch. Required: %d Available: %d " , pSize , imagesize ) ;
}
Debug ( 1 , " Validated imagesize " ) ;
2013-03-17 07:45:21 +08:00
# if HAVE_LIBSWSCALE
2014-03-22 00:50:49 +08:00
Debug ( 1 , " Calling sws_isSupportedInput " ) ;
if ( ! sws_isSupportedInput ( mCodecContext - > pix_fmt ) ) {
Fatal ( " swscale does not support the codec format: %c%c%c%c " , ( mCodecContext - > pix_fmt ) & 0xff , ( ( mCodecContext - > pix_fmt > > 8 ) & 0xff ) , ( ( mCodecContext - > pix_fmt > > 16 ) & 0xff ) , ( ( mCodecContext - > pix_fmt > > 24 ) & 0xff ) ) ;
}
if ( ! sws_isSupportedOutput ( imagePixFormat ) ) {
Fatal ( " swscale does not support the target format: %c%c%c%c " , ( imagePixFormat ) & 0xff , ( ( imagePixFormat > > 8 ) & 0xff ) , ( ( imagePixFormat > > 16 ) & 0xff ) , ( ( imagePixFormat > > 24 ) & 0xff ) ) ;
}
2013-03-17 07:45:21 +08:00
# else // HAVE_LIBSWSCALE
Fatal ( " You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras " ) ;
# endif // HAVE_LIBSWSCALE
2014-03-22 00:50:49 +08:00
mCanCapture = true ;
return 0 ;
2013-03-17 07:45:21 +08:00
}
2014-03-22 00:50:49 +08:00
int FfmpegCamera : : ReopenFfmpeg ( ) {
Debug ( 2 , " ReopenFfmpeg called. " ) ;
mCanCapture = false ;
2014-10-11 22:58:47 +08:00
if ( pthread_create ( & mReopenThread , NULL , ReopenFfmpegThreadCallback , ( void * ) this ) ! = 0 ) {
2014-03-22 05:53:19 +08:00
// Log a fatal error and exit the process.
Fatal ( " ReopenFfmpeg failed to create worker thread. " ) ;
2014-03-22 00:50:49 +08:00
}
return 0 ;
2013-03-17 07:45:21 +08:00
}
2014-03-22 00:50:49 +08:00
int FfmpegCamera : : CloseFfmpeg ( ) {
Debug ( 2 , " CloseFfmpeg called. " ) ;
mCanCapture = false ;
2015-11-04 12:30:14 +08:00
# if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
2015-11-03 08:58:23 +08:00
av_frame_free ( & mFrame ) ;
av_frame_free ( & mRawFrame ) ;
2015-11-04 12:30:14 +08:00
# else
av_freep ( & mFrame ) ;
av_freep ( & mRawFrame ) ;
# endif
2013-03-17 07:45:21 +08:00
2014-03-22 00:50:49 +08:00
# if HAVE_LIBSWSCALE
if ( mConvertContext )
2013-03-17 07:45:21 +08:00
{
2014-03-22 00:50:49 +08:00
sws_freeContext ( mConvertContext ) ;
mConvertContext = NULL ;
}
2014-04-26 04:57:29 +08:00
# endif
2013-03-17 07:45:21 +08:00
2014-03-22 00:50:49 +08:00
if ( mCodecContext )
{
avcodec_close ( mCodecContext ) ;
mCodecContext = NULL ; // Freed by av_close_input_file
}
if ( mFormatContext )
{
2015-05-29 23:38:02 +08:00
# if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
2014-03-22 00:50:49 +08:00
av_close_input_file ( mFormatContext ) ;
# else
avformat_close_input ( & mFormatContext ) ;
# endif
mFormatContext = NULL ;
}
2013-03-17 07:45:21 +08:00
2014-03-22 00:50:49 +08:00
return 0 ;
}
2013-03-17 07:45:21 +08:00
2014-03-22 00:50:49 +08:00
int FfmpegCamera : : FfmpegInterruptCallback ( void * ctx )
{
FfmpegCamera * camera = reinterpret_cast < FfmpegCamera * > ( ctx ) ;
if ( camera - > mIsOpening ) {
int now = time ( NULL ) ;
2014-03-22 02:15:36 +08:00
if ( ( now - camera - > mOpenStart ) > config . ffmpeg_open_timeout ) {
Error ( " Open video took more than %d seconds. " , config . ffmpeg_open_timeout ) ;
2014-03-22 00:50:49 +08:00
return 1 ;
2013-03-17 07:45:21 +08:00
}
}
2014-03-22 00:50:49 +08:00
return 0 ;
2013-03-17 07:45:21 +08:00
}
2014-03-22 00:50:49 +08:00
void * FfmpegCamera : : ReopenFfmpegThreadCallback ( void * ctx ) {
if ( ctx = = NULL ) return NULL ;
FfmpegCamera * camera = reinterpret_cast < FfmpegCamera * > ( ctx ) ;
2014-10-11 22:58:47 +08:00
while ( 1 ) {
// Close current stream.
camera - > CloseFfmpeg ( ) ;
2014-03-22 00:50:49 +08:00
2015-04-19 18:38:23 +08:00
// Sleep if necessary to not reconnect too fast.
2014-10-11 22:58:47 +08:00
int wait = config . ffmpeg_open_timeout - ( time ( NULL ) - camera - > mOpenStart ) ;
wait = wait < 0 ? 0 : wait ;
if ( wait > 0 ) {
Debug ( 1 , " Sleeping %d seconds before reopening stream. " , wait ) ;
sleep ( wait ) ;
}
2014-03-22 00:50:49 +08:00
2014-10-11 22:58:47 +08:00
if ( camera - > OpenFfmpeg ( ) = = 0 ) {
return NULL ;
}
}
2013-03-17 07:45:21 +08:00
}
# endif // HAVE_LIBAVFORMAT