X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fhighgui%2Fcvcap_ffmpeg.cpp;fp=src%2Fhighgui%2Fcvcap_ffmpeg.cpp;h=63d43915b87906644bdf5c5351337d920528b106;hb=e4c14cdbdf2fe805e79cd96ded236f57e7b89060;hp=0000000000000000000000000000000000000000;hpb=454138ff8a20f6edb9b65a910101403d8b520643;p=opencv diff --git a/src/highgui/cvcap_ffmpeg.cpp b/src/highgui/cvcap_ffmpeg.cpp new file mode 100644 index 0000000..63d4391 --- /dev/null +++ b/src/highgui/cvcap_ffmpeg.cpp @@ -0,0 +1,1358 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "_highgui.h" + +#if defined _MSC_VER && _MSC_VER >= 1200 +#pragma warning( disable: 4244 4510 4512 4610 ) +#endif + +extern "C" { +#if !defined(WIN32) || defined(__MINGW32__) +// some versions of FFMPEG assume a C99 compiler, and don't define INT64_C +#ifndef INT64_C +#define INT64_C +#define __STDC_CONSTANT_MACROS +// force re-inclusion of stdint.h to get INT64_C macro +#undef _STDINT_H +#include +#endif +#include +#endif + +#ifdef WIN32 + #include + #include + #include +#else + +// if the header path is not specified explicitly, let's deduce it +#if !defined HAVE_FFMPEG_AVCODEC_H && !defined HAVE_LIBAVCODEC_AVCODEC_H + +#if defined(HAVE_GENTOO_FFMPEG) + #define HAVE_LIBAVCODEC_AVCODEC_H 1 + #define HAVE_LIBAVFORMAT_AVFORMAT_H 1 + #if defined(HAVE_FFMPEG_SWSCALE) + #define HAVE_LIBSWSCALE_SWSCALE_H 1 + #endif +#elif defined HAVE_FFMPEG + #define HAVE_FFMPEG_AVCODEC_H 1 + #define HAVE_FFMPEG_AVFORMAT_H 1 + #if defined(HAVE_FFMPEG_SWSCALE) + #define HAVE_FFMPEG_SWSCALE_H 1 + #endif +#endif + +#endif + +#if defined(HAVE_FFMPEG_AVCODEC_H) + #include +#endif +#if defined(HAVE_FFMPEG_AVFORMAT_H) + #include +#endif +#if defined(HAVE_FFMPEG_SWSCALE_H) + #include +#endif + +#if defined(HAVE_LIBAVFORMAT_AVFORMAT_H) + #include +#endif +#if defined(HAVE_LIBAVCODEC_AVCODEC_H) + #include +#endif +#if defined(HAVE_LIBSWSCALE_SWSCALE_H) + #include +#endif + +#endif + +} + +#if defined _MSC_VER && _MSC_VER >= 1200 +#pragma warning( default: 4244 4510 4512 4610 ) +#endif + +#ifdef NDEBUG +#define CV_WARN(message) +#else +#define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__) +#endif + + +#ifndef MKTAG +#define MKTAG(a,b,c,d) (a | (b << 8) | (c << 16) | (d << 24)) +#endif + +/* PIX_FMT_RGBA32 macro changed in newer ffmpeg versions */ +#ifndef PIX_FMT_RGBA32 +#define PIX_FMT_RGBA32 PIX_FMT_RGB32 +#endif + + + +char * FOURCC2str( int fourcc ) +{ + char * mystr=(char*)malloc(5); + mystr[0]=(char)((fourcc )&255); + mystr[1]=(char)((fourcc>> 8)&255); + mystr[2]=(char)((fourcc>>16)&255); + mystr[3]=(char)((fourcc>>24)&255); + mystr[4]=0; + return mystr; +} + + +// required to look up the correct codec ID depending on the FOURCC code, +// this is just a snipped from the file riff.c from ffmpeg/libavformat +typedef struct AVCodecTag { + int id; + unsigned int tag; +} AVCodecTag; + +const AVCodecTag codec_bmp_tags[] = { + { CODEC_ID_H264, MKTAG('H', '2', '6', '4') }, + { CODEC_ID_H264, MKTAG('h', '2', '6', '4') }, + { CODEC_ID_H264, MKTAG('X', '2', '6', '4') }, + { CODEC_ID_H264, MKTAG('x', '2', '6', '4') }, + { CODEC_ID_H264, MKTAG('a', 'v', 'c', '1') }, + { CODEC_ID_H264, MKTAG('V', 'S', 'S', 'H') }, + + { CODEC_ID_H263, MKTAG('H', '2', '6', '3') }, + { CODEC_ID_H263P, MKTAG('H', '2', '6', '3') }, + { CODEC_ID_H263I, MKTAG('I', '2', '6', '3') }, /* intel h263 */ + { CODEC_ID_H261, MKTAG('H', '2', '6', '1') }, + + /* added based on MPlayer */ + { CODEC_ID_H263P, MKTAG('U', '2', '6', '3') }, + { CODEC_ID_H263P, MKTAG('v', 'i', 'v', '1') }, + + { CODEC_ID_MPEG4, MKTAG('F', 'M', 'P', '4') }, + { CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', 'X') }, + { CODEC_ID_MPEG4, MKTAG('D', 'X', '5', '0') }, + { CODEC_ID_MPEG4, MKTAG('X', 'V', 'I', 'D') }, + { CODEC_ID_MPEG4, MKTAG('M', 'P', '4', 'S') }, + { CODEC_ID_MPEG4, MKTAG('M', '4', 'S', '2') }, + { CODEC_ID_MPEG4, MKTAG(0x04, 0, 0, 0) }, /* some broken avi use this */ + + /* added based on MPlayer */ + { CODEC_ID_MPEG4, MKTAG('D', 'I', 'V', '1') }, + { CODEC_ID_MPEG4, MKTAG('B', 'L', 'Z', '0') }, + { CODEC_ID_MPEG4, MKTAG('m', 'p', '4', 'v') }, + { CODEC_ID_MPEG4, MKTAG('U', 'M', 'P', '4') }, + { CODEC_ID_MPEG4, MKTAG('W', 'V', '1', 'F') }, + { CODEC_ID_MPEG4, MKTAG('S', 'E', 'D', 'G') }, + + { CODEC_ID_MPEG4, MKTAG('R', 'M', 'P', '4') }, + + { CODEC_ID_MSMPEG4V3, MKTAG('D', 'I', 'V', '3') }, /* default signature when using MSMPEG4 */ + { CODEC_ID_MSMPEG4V3, MKTAG('M', 'P', '4', '3') }, + + /* added based on MPlayer */ + { CODEC_ID_MSMPEG4V3, MKTAG('M', 'P', 'G', '3') }, + { CODEC_ID_MSMPEG4V3, MKTAG('D', 'I', 'V', '5') }, + { CODEC_ID_MSMPEG4V3, MKTAG('D', 'I', 'V', '6') }, + { CODEC_ID_MSMPEG4V3, MKTAG('D', 'I', 'V', '4') }, + { CODEC_ID_MSMPEG4V3, MKTAG('A', 'P', '4', '1') }, + { CODEC_ID_MSMPEG4V3, MKTAG('C', 'O', 'L', '1') }, + { CODEC_ID_MSMPEG4V3, MKTAG('C', 'O', 'L', '0') }, + + { CODEC_ID_MSMPEG4V2, MKTAG('M', 'P', '4', '2') }, + + /* added based on MPlayer */ + { CODEC_ID_MSMPEG4V2, MKTAG('D', 'I', 'V', '2') }, + + { CODEC_ID_MSMPEG4V1, MKTAG('M', 'P', 'G', '4') }, + + { CODEC_ID_WMV1, MKTAG('W', 'M', 'V', '1') }, + + /* added based on MPlayer */ + { CODEC_ID_WMV2, MKTAG('W', 'M', 'V', '2') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 's', 'd') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 'h', 'd') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', 's', 'l') }, + { CODEC_ID_DVVIDEO, MKTAG('d', 'v', '2', '5') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '1') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', 'g', '2') }, + { CODEC_ID_MPEG2VIDEO, MKTAG('m', 'p', 'g', '2') }, + { CODEC_ID_MPEG2VIDEO, MKTAG('M', 'P', 'E', 'G') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('P', 'I', 'M', '1') }, + { CODEC_ID_MPEG1VIDEO, MKTAG('V', 'C', 'R', '2') }, + { CODEC_ID_MPEG1VIDEO, 0x10000001 }, + { CODEC_ID_MPEG2VIDEO, 0x10000002 }, + { CODEC_ID_MPEG2VIDEO, MKTAG('D', 'V', 'R', ' ') }, + { CODEC_ID_MPEG2VIDEO, MKTAG('M', 'M', 'E', 'S') }, + { CODEC_ID_MJPEG, MKTAG('M', 'J', 'P', 'G') }, + { CODEC_ID_MJPEG, MKTAG('L', 'J', 'P', 'G') }, + { CODEC_ID_LJPEG, MKTAG('L', 'J', 'P', 'G') }, + { CODEC_ID_MJPEG, MKTAG('J', 'P', 'G', 'L') }, /* Pegasus lossless JPEG */ + { CODEC_ID_MJPEG, MKTAG('M', 'J', 'L', 'S') }, /* JPEG-LS custom FOURCC for avi - decoder */ + { CODEC_ID_MJPEG, MKTAG('j', 'p', 'e', 'g') }, + { CODEC_ID_MJPEG, MKTAG('I', 'J', 'P', 'G') }, + { CODEC_ID_MJPEG, MKTAG('A', 'V', 'R', 'n') }, + { CODEC_ID_HUFFYUV, MKTAG('H', 'F', 'Y', 'U') }, + { CODEC_ID_FFVHUFF, MKTAG('F', 'F', 'V', 'H') }, + { CODEC_ID_CYUV, MKTAG('C', 'Y', 'U', 'V') }, + { CODEC_ID_RAWVIDEO, 0 }, + { CODEC_ID_RAWVIDEO, MKTAG('I', '4', '2', '0') }, + { CODEC_ID_RAWVIDEO, MKTAG('Y', 'U', 'Y', '2') }, + { CODEC_ID_RAWVIDEO, MKTAG('Y', '4', '2', '2') }, + { CODEC_ID_RAWVIDEO, MKTAG('Y', 'V', '1', '2') }, + { CODEC_ID_RAWVIDEO, MKTAG('U', 'Y', 'V', 'Y') }, + { CODEC_ID_RAWVIDEO, MKTAG('I', 'Y', 'U', 'V') }, + { CODEC_ID_RAWVIDEO, MKTAG('Y', '8', '0', '0') }, + { CODEC_ID_RAWVIDEO, MKTAG('H', 'D', 'Y', 'C') }, + { CODEC_ID_INDEO3, MKTAG('I', 'V', '3', '1') }, + { CODEC_ID_INDEO3, MKTAG('I', 'V', '3', '2') }, + { CODEC_ID_VP3, MKTAG('V', 'P', '3', '1') }, + { CODEC_ID_VP3, MKTAG('V', 'P', '3', '0') }, + { CODEC_ID_ASV1, MKTAG('A', 'S', 'V', '1') }, + { CODEC_ID_ASV2, MKTAG('A', 'S', 'V', '2') }, + { CODEC_ID_VCR1, MKTAG('V', 'C', 'R', '1') }, + { CODEC_ID_FFV1, MKTAG('F', 'F', 'V', '1') }, + { CODEC_ID_XAN_WC4, MKTAG('X', 'x', 'a', 'n') }, + { CODEC_ID_MSRLE, MKTAG('m', 'r', 'l', 'e') }, + { CODEC_ID_MSRLE, MKTAG(0x1, 0x0, 0x0, 0x0) }, + { CODEC_ID_MSVIDEO1, MKTAG('M', 'S', 'V', 'C') }, + { CODEC_ID_MSVIDEO1, MKTAG('m', 's', 'v', 'c') }, + { CODEC_ID_MSVIDEO1, MKTAG('C', 'R', 'A', 'M') }, + { CODEC_ID_MSVIDEO1, MKTAG('c', 'r', 'a', 'm') }, + { CODEC_ID_MSVIDEO1, MKTAG('W', 'H', 'A', 'M') }, + { CODEC_ID_MSVIDEO1, MKTAG('w', 'h', 'a', 'm') }, + { CODEC_ID_CINEPAK, MKTAG('c', 'v', 'i', 'd') }, + { CODEC_ID_TRUEMOTION1, MKTAG('D', 'U', 'C', 'K') }, + { CODEC_ID_MSZH, MKTAG('M', 'S', 'Z', 'H') }, + { CODEC_ID_ZLIB, MKTAG('Z', 'L', 'I', 'B') }, + { CODEC_ID_SNOW, MKTAG('S', 'N', 'O', 'W') }, + { CODEC_ID_4XM, MKTAG('4', 'X', 'M', 'V') }, + { CODEC_ID_FLV1, MKTAG('F', 'L', 'V', '1') }, + { CODEC_ID_SVQ1, MKTAG('s', 'v', 'q', '1') }, + { CODEC_ID_TSCC, MKTAG('t', 's', 'c', 'c') }, + { CODEC_ID_ULTI, MKTAG('U', 'L', 'T', 'I') }, + { CODEC_ID_VIXL, MKTAG('V', 'I', 'X', 'L') }, + { CODEC_ID_QPEG, MKTAG('Q', 'P', 'E', 'G') }, + { CODEC_ID_QPEG, MKTAG('Q', '1', '.', '0') }, + { CODEC_ID_QPEG, MKTAG('Q', '1', '.', '1') }, + { CODEC_ID_WMV3, MKTAG('W', 'M', 'V', '3') }, + { CODEC_ID_LOCO, MKTAG('L', 'O', 'C', 'O') }, + { CODEC_ID_THEORA, MKTAG('t', 'h', 'e', 'o') }, +#if LIBAVCODEC_VERSION_INT>0x000409 + { CODEC_ID_WNV1, MKTAG('W', 'N', 'V', '1') }, + { CODEC_ID_AASC, MKTAG('A', 'A', 'S', 'C') }, + { CODEC_ID_INDEO2, MKTAG('R', 'T', '2', '1') }, + { CODEC_ID_FRAPS, MKTAG('F', 'P', 'S', '1') }, + { CODEC_ID_TRUEMOTION2, MKTAG('T', 'M', '2', '0') }, +#endif +#if LIBAVCODEC_VERSION_INT>((50<<16)+(1<<8)+0) + { CODEC_ID_FLASHSV, MKTAG('F', 'S', 'V', '1') }, + { CODEC_ID_JPEGLS,MKTAG('M', 'J', 'L', 'S') }, /* JPEG-LS custom FOURCC for avi - encoder */ + { CODEC_ID_VC1, MKTAG('W', 'V', 'C', '1') }, + { CODEC_ID_VC1, MKTAG('W', 'M', 'V', 'A') }, + { CODEC_ID_CSCD, MKTAG('C', 'S', 'C', 'D') }, + { CODEC_ID_ZMBV, MKTAG('Z', 'M', 'B', 'V') }, + { CODEC_ID_KMVC, MKTAG('K', 'M', 'V', 'C') }, +#endif +#if LIBAVCODEC_VERSION_INT>((51<<16)+(11<<8)+0) + { CODEC_ID_VP5, MKTAG('V', 'P', '5', '0') }, + { CODEC_ID_VP6, MKTAG('V', 'P', '6', '0') }, + { CODEC_ID_VP6, MKTAG('V', 'P', '6', '1') }, + { CODEC_ID_VP6, MKTAG('V', 'P', '6', '2') }, + { CODEC_ID_VP6F, MKTAG('V', 'P', '6', 'F') }, + { CODEC_ID_JPEG2000, MKTAG('M', 'J', '2', 'C') }, + { CODEC_ID_VMNC, MKTAG('V', 'M', 'n', 'c') }, +#endif +#if LIBAVCODEC_VERSION_INT>=((51<<16)+(49<<8)+0) +// this tag seems not to exist in older versions of FFMPEG + { CODEC_ID_TARGA, MKTAG('t', 'g', 'a', ' ') }, +#endif + { CODEC_ID_NONE, 0 }, +}; + + +class CvCapture_FFMPEG : public CvCapture +{ +public: + CvCapture_FFMPEG() { init(); } + virtual ~CvCapture_FFMPEG() { close(); } + + virtual bool open( const char* filename ); + virtual void close(); + + virtual double getProperty(int); + virtual bool setProperty(int, double); + virtual bool grabFrame(); + virtual IplImage* retrieveFrame(int); + +protected: + void init(); + bool reopen(); + bool slowSeek( int framenumber ); + + AVFormatContext * ic; + int video_stream; + AVStream * video_st; + AVFrame * picture; + int64_t picture_pts; + AVFrame rgb_picture; + AVPacket packet; + IplImage frame; +#if defined(HAVE_FFMPEG_SWSCALE) + struct SwsContext *img_convert_ctx; +#endif +/* + 'filename' contains the filename of the videosource, + 'filename==NULL' indicates that ffmpeg's seek support works + for the particular file. + 'filename!=NULL' indicates that the slow fallback function is used for seeking, + and so the filename is needed to reopen the file on backward seeking. +*/ + char * filename; +}; + + +void CvCapture_FFMPEG::init() +{ + ic = 0; + video_stream = -1; + video_st = 0; + picture = 0; + picture_pts = 0; + memset( &rgb_picture, 0, sizeof(rgb_picture) ); + memset( &frame, 0, sizeof(frame) ); + filename = 0; + packet.data = NULL; +#if defined(HAVE_FFMPEG_SWSCALE) + img_convert_ctx = 0; +#endif +} + + +void CvCapture_FFMPEG::close() +{ + if( picture ) + av_free(picture); + + if( video_st ) + { +#if LIBAVFORMAT_BUILD > 4628 + avcodec_close( video_st->codec ); +#else + avcodec_close( &video_st->codec ); +#endif + video_st = NULL; + } + + if( ic ) + { + av_close_input_file(ic); + ic = NULL; + } + + if( rgb_picture.data[0] ) + cvFree( &rgb_picture.data[0] ); + + // free last packet if exist + if (packet.data) { + av_free_packet (&packet); + } + + + init(); +} + + +/* + Used to reopen a video if the slower fallback function for seeking is used. +*/ +bool CvCapture_FFMPEG::reopen() +{ + if ( filename==NULL ) return false; + +#if LIBAVFORMAT_BUILD > 4628 + avcodec_close( video_st->codec ); +#else + avcodec_close( &video_st->codec ); +#endif + av_close_input_file(ic); + + // reopen video + av_open_input_file(&ic, filename, NULL, 0, NULL); + av_find_stream_info(ic); +#if LIBAVFORMAT_BUILD > 4628 + AVCodecContext *enc = ic->streams[video_stream]->codec; +#else + AVCodecContext *enc = &ic->streams[video_stream]->codec; +#endif + AVCodec *codec = avcodec_find_decoder(enc->codec_id); + avcodec_open(enc, codec); + video_st = ic->streams[video_stream]; + + // reset framenumber to zero + picture_pts=0; + + return true; +} + + + +bool CvCapture_FFMPEG::open( const char* _filename ) +{ + unsigned i; + bool valid = false; + + close(); + + /* register all codecs, demux and protocols */ + av_register_all(); + +#ifndef _DEBUG + // av_log_level = AV_LOG_QUIET; +#endif + + int err = av_open_input_file(&ic, _filename, NULL, 0, NULL); + if (err < 0) { + CV_WARN("Error opening file"); + goto exit_func; + } + err = av_find_stream_info(ic); + if (err < 0) { + CV_WARN("Could not find codec parameters"); + goto exit_func; + } + for(i = 0; i < ic->nb_streams; i++) { +#if LIBAVFORMAT_BUILD > 4628 + AVCodecContext *enc = ic->streams[i]->codec; +#else + AVCodecContext *enc = &ic->streams[i]->codec; +#endif + + if( CODEC_TYPE_VIDEO == enc->codec_type && video_stream < 0) { + AVCodec *codec = avcodec_find_decoder(enc->codec_id); + if (!codec || + avcodec_open(enc, codec) < 0) + goto exit_func; + video_stream = i; + video_st = ic->streams[i]; + picture = avcodec_alloc_frame(); + + rgb_picture.data[0] = (uint8_t*)cvAlloc( + avpicture_get_size( PIX_FMT_BGR24, + enc->width, enc->height )); + avpicture_fill( (AVPicture*)&rgb_picture, rgb_picture.data[0], + PIX_FMT_BGR24, enc->width, enc->height ); + + cvInitImageHeader( &frame, cvSize( enc->width, + enc->height ), 8, 3, 0, 4 ); + cvSetData( &frame, rgb_picture.data[0], + rgb_picture.linesize[0] ); + break; + } + } + + if(video_stream >= 0) valid = true; + + // perform check if source is seekable via ffmpeg's seek function av_seek_frame(...) + err = av_seek_frame(ic, video_stream, 10, 0); + if (err < 0) + { + filename=(char*)malloc(strlen(_filename)+1); + strcpy(filename, _filename); + // reopen videofile to 'seek' back to first frame + reopen(); + } + else + { + // seek seems to work, so we don't need the filename, + // but we still need to seek back to filestart + filename=NULL; + av_seek_frame(ic, video_stream, 0, 0); + } +exit_func: + + if( !valid ) + close(); + + return valid; +} + + +bool CvCapture_FFMPEG::grabFrame() +{ + bool valid = false; + static bool bFirstTime = true; + int got_picture; + + // First time we're called, set packet.data to NULL to indicate it + // doesn't have to be freed + if (bFirstTime) { + bFirstTime = false; + packet.data = NULL; + } + + if( !ic || !video_st ) + return false; + + // free last packet if exist + if (packet.data != NULL) { + av_free_packet (&packet); + } + + // get the next frame + while (!valid && (av_read_frame(ic, &packet) >= 0)) { + if( packet.stream_index != video_stream ) { + av_free_packet (&packet); + continue; + } + +#if LIBAVFORMAT_BUILD > 4628 + avcodec_decode_video(video_st->codec, + picture, &got_picture, + packet.data, packet.size); +#else + avcodec_decode_video(&video_st->codec, + picture, &got_picture, + packet.data, packet.size); +#endif + + if (got_picture) { + // we have a new picture, so memorize it + picture_pts = packet.pts; + valid = 1; + } + } + + // return if we have a new picture or not + return valid; +} + + +IplImage* CvCapture_FFMPEG::retrieveFrame(int) +{ + if( !video_st || !picture->data[0] ) + return 0; + +#if !defined(HAVE_FFMPEG_SWSCALE) +#if LIBAVFORMAT_BUILD > 4628 + img_convert( (AVPicture*)&rgb_picture, PIX_FMT_BGR24, + (AVPicture*)picture, + video_st->codec->pix_fmt, + video_st->codec->width, + video_st->codec->height ); +#else + img_convert( (AVPicture*)&rgb_picture, PIX_FMT_BGR24, + (AVPicture*)picture, + video_st->codec.pix_fmt, + video_st->codec.width, + video_st->codec.height ); +#endif +#else + img_convert_ctx = sws_getContext(video_st->codec->width, + video_st->codec->height, + video_st->codec->pix_fmt, + video_st->codec->width, + video_st->codec->height, + PIX_FMT_BGR24, + SWS_BICUBIC, + NULL, NULL, NULL); + + sws_scale(img_convert_ctx, picture->data, + picture->linesize, 0, + video_st->codec->height, + rgb_picture.data, rgb_picture.linesize); + sws_freeContext(img_convert_ctx); +#endif + return &frame; +} + +#if defined(__APPLE__) +#define AV_NOPTS_VALUE_ ((int64_t)0x8000000000000000LL) +#else +#define AV_NOPTS_VALUE_ ((int64_t)AV_NOPTS_VALUE) +#endif + +double CvCapture_FFMPEG::getProperty( int property_id ) +{ + // if( !capture || !video_st || !picture->data[0] ) return 0; + if( !video_st ) return 0; + + + int64_t timestamp; + timestamp = picture_pts; + + switch( property_id ) + { + case CV_CAP_PROP_POS_MSEC: + if(video_st->cur_dts != AV_NOPTS_VALUE_) + return (((double)video_st->cur_dts-1) *1000) / av_q2d (video_st->r_frame_rate); + break; + case CV_CAP_PROP_POS_FRAMES: + if(video_st->cur_dts != AV_NOPTS_VALUE_) + return (double)video_st->cur_dts-1; + break; + case CV_CAP_PROP_POS_AVI_RATIO: + if(video_st->cur_dts != AV_NOPTS_VALUE_ && video_st->duration != AV_NOPTS_VALUE_) + return (double)(video_st->cur_dts-1)/(double)video_st->duration; + break; + case CV_CAP_PROP_FRAME_COUNT: + if(video_st->duration != AV_NOPTS_VALUE_) + return (double)video_st->duration; + break; + case CV_CAP_PROP_FRAME_WIDTH: + return (double)frame.width; + case CV_CAP_PROP_FRAME_HEIGHT: + return (double)frame.height; + case CV_CAP_PROP_FPS: +#if LIBAVCODEC_BUILD > 4753 + return av_q2d (video_st->r_frame_rate); +#else + return (double)video_st->codec.frame_rate + / (double)video_st->codec.frame_rate_base; +#endif + case CV_CAP_PROP_FOURCC: +#if LIBAVFORMAT_BUILD > 4628 + return (double)video_st->codec->codec_tag; +#else + return (double)video_st->codec.codec_tag; +#endif + } + return 0; +} + + + +// this is a VERY slow fallback function, ONLY used if ffmpeg's av_seek_frame delivers no correct result! +bool CvCapture_FFMPEG::slowSeek( int framenumber ) +{ + if ( framenumber>picture_pts ) + { + while ( picture_ptsstart_time != AV_NOPTS_VALUE_) + timestamp += ic->start_time; + break; + + case CV_CAP_PROP_POS_MSEC: + time_base=ic->streams[video_stream]->time_base; + timestamp=(int64_t)(value*(float(time_base.den)/float(time_base.num))/1000); + if(ic->start_time != AV_NOPTS_VALUE_) + timestamp += ic->start_time; + break; + + case CV_CAP_PROP_POS_AVI_RATIO: + timestamp=(int64_t)(value*ic->duration); + if(ic->start_time != AV_NOPTS_VALUE_ && ic->duration != AV_NOPTS_VALUE_) + timestamp += ic->start_time; + break; + } + + if ( filename ) + { + // ffmpeg's seek doesn't work... + if (!slowSeek((int)timestamp)) + { + fprintf(stderr, "HIGHGUI ERROR: AVI: could not (slow) seek to position %0.3f\n", + (double)timestamp / AV_TIME_BASE); + return false; + } + } + else + { + int ret = av_seek_frame(ic, video_stream, timestamp, 0); + if (ret < 0) + { + fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f\n", + (double)timestamp / AV_TIME_BASE); + return false; + } + } + picture_pts=(int64_t)value; + } + break; + + default: + return false; + } + + return true; +} + + + +CvCapture* cvCreateFileCapture_FFMPEG( const char* filename ) +{ + CvCapture_FFMPEG* capture = new CvCapture_FFMPEG; + if( capture->open( filename )) + return capture; + delete capture; + return 0; +} + + +///////////////// FFMPEG CvVideoWriter implementation ////////////////////////// +class CvVideoWriter_FFMPEG : public CvVideoWriter +{ +public: + CvVideoWriter_FFMPEG() { init(); } + virtual ~CvVideoWriter_FFMPEG() { close(); } + + virtual bool open( const char* filename, int fourcc, + double fps, CvSize frameSize, bool isColor ); + virtual void close(); + virtual bool writeFrame( const IplImage* image ); + +protected: + void init(); + + AVOutputFormat *fmt; + AVFormatContext *oc; + uint8_t * outbuf; + uint32_t outbuf_size; + FILE * outfile; + AVFrame * picture; + AVFrame * input_picture; + uint8_t * picbuf; + AVStream * video_st; + int input_pix_fmt; + IplImage * temp_image; +#if defined(HAVE_FFMPEG_SWSCALE) + struct SwsContext *img_convert_ctx; +#endif +}; + +static const char * icvFFMPEGErrStr(int err) +{ + switch(err) { + case AVERROR_NUMEXPECTED: + return "Incorrect filename syntax"; + case AVERROR_INVALIDDATA: + return "Invalid data in header"; + case AVERROR_NOFMT: + return "Unknown format"; + case AVERROR_IO: + return "I/O error occurred"; + case AVERROR_NOMEM: + return "Memory allocation error"; + default: + break; + } + return "Unspecified error"; +} + +/* function internal to FFMPEG (libavformat/riff.c) to lookup codec id by fourcc tag*/ +extern "C" { + enum CodecID codec_get_bmp_id(unsigned int tag); +} + +void CvVideoWriter_FFMPEG::init() +{ + fmt = 0; + oc = 0; + outbuf = 0; + outbuf_size = 0; + outfile = 0; + picture = 0; + input_picture = 0; + picbuf = 0; + video_st = 0; + input_pix_fmt = 0; + temp_image = 0; +#if defined(HAVE_FFMPEG_SWSCALE) + img_convert_ctx = 0; +#endif +} + +/** + * the following function is a modified version of code + * found in ffmpeg-0.4.9-pre1/output_example.c + */ +static AVFrame * icv_alloc_picture_FFMPEG(int pix_fmt, int width, int height, bool alloc) +{ + AVFrame * picture; + uint8_t * picture_buf; + int size; + + picture = avcodec_alloc_frame(); + if (!picture) + return NULL; + size = avpicture_get_size( (PixelFormat) pix_fmt, width, height); + if(alloc){ + picture_buf = (uint8_t *) cvAlloc(size); + if (!picture_buf) + { + av_free(picture); + return NULL; + } + avpicture_fill((AVPicture *)picture, picture_buf, + (PixelFormat) pix_fmt, width, height); + } + else { + } + return picture; +} + +/* add a video output stream to the container */ +static AVStream *icv_add_video_stream_FFMPEG(AVFormatContext *oc, + CodecID codec_id, + int w, int h, int bitrate, + double fps, int pixel_format) +{ + AVCodecContext *c; + AVStream *st; + int frame_rate, frame_rate_base; + AVCodec *codec; + + + st = av_new_stream(oc, 0); + if (!st) { + CV_WARN("Could not allocate stream"); + return NULL; + } + +#if LIBAVFORMAT_BUILD > 4628 + c = st->codec; +#else + c = &(st->codec); +#endif + +#if LIBAVFORMAT_BUILD > 4621 + c->codec_id = av_guess_codec(oc->oformat, NULL, oc->filename, NULL, CODEC_TYPE_VIDEO); +#else + c->codec_id = oc->oformat->video_codec; +#endif + + if(codec_id != CODEC_ID_NONE){ + c->codec_id = codec_id; + } + + //if(codec_tag) c->codec_tag=codec_tag; + codec = avcodec_find_encoder(c->codec_id); + + c->codec_type = CODEC_TYPE_VIDEO; + + /* put sample parameters */ + c->bit_rate = bitrate; + + /* resolution must be a multiple of two */ + c->width = w; + c->height = h; + + /* 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. */ + frame_rate=cvRound(fps); + frame_rate_base=1; + while (fabs((double)frame_rate/frame_rate_base) - fps > 0.001){ + frame_rate_base*=10; + frame_rate=cvRound(fps*frame_rate_base); + } +#if LIBAVFORMAT_BUILD > 4752 + c->time_base.den = frame_rate; + c->time_base.num = frame_rate_base; + /* adjust time base for supported framerates */ + if(codec && codec->supported_framerates){ + const AVRational *p= codec->supported_framerates; + AVRational req = {frame_rate, frame_rate_base}; + const AVRational *best=NULL; + AVRational best_error= {INT_MAX, 1}; + for(; p->den!=0; p++){ + AVRational error= av_sub_q(req, *p); + if(error.num <0) error.num *= -1; + if(av_cmp_q(error, best_error) < 0){ + best_error= error; + best= p; + } + } + c->time_base.den= best->num; + c->time_base.num= best->den; + } +#else + c->frame_rate = frame_rate; + c->frame_rate_base = frame_rate_base; +#endif + + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = (PixelFormat) pixel_format; + + if (c->codec_id == CODEC_ID_MPEG2VIDEO) { + c->max_b_frames = 2; + } + if (c->codec_id == CODEC_ID_MPEG1VIDEO || c->codec_id == CODEC_ID_MSMPEG4V3){ + /* needed to avoid using macroblocks in which some coeffs overflow + this doesnt happen with normal video, it just happens here as the + motion of the chroma plane doesnt match the luma plane */ + /* avoid FFMPEG warning 'clipping 1 dct coefficients...' */ + c->mb_decision=2; + } +#if LIBAVCODEC_VERSION_INT>0x000409 + // some formats want stream headers to be seperate + if(oc->oformat->flags & AVFMT_GLOBALHEADER) + { + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } +#endif + + return st; +} + +int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, uint8_t * outbuf, uint32_t outbuf_size, AVFrame * picture ){ + CV_FUNCNAME("icv_av_write_frame_FFMPEG"); + +#if LIBAVFORMAT_BUILD > 4628 + AVCodecContext * c = video_st->codec; +#else + AVCodecContext * c = &(video_st->codec); +#endif + int out_size; + int ret; + + __BEGIN__; + + if (oc->oformat->flags & AVFMT_RAWPICTURE) { + /* raw video case. The API will change slightly in the near + futur for that */ + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= (uint8_t *)picture; + pkt.size= sizeof(AVPicture); + + ret = av_write_frame(oc, &pkt); + } else { + /* encode the image */ + out_size = avcodec_encode_video(c, outbuf, outbuf_size, picture); + /* if zero size, it means the image was buffered */ + if (out_size > 0) { + AVPacket pkt; + av_init_packet(&pkt); + +#if LIBAVFORMAT_BUILD > 4752 + pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base); +#else + pkt.pts = c->coded_frame->pts; +#endif + if(c->coded_frame->key_frame) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= outbuf; + pkt.size= out_size; + + /* write the compressed frame in the media file */ + ret = av_write_frame(oc, &pkt); + } else { + ret = 0; + } + } + if (ret != 0) { + CV_ERROR(CV_StsError, "Error while writing video frame"); + } + + __END__; + return CV_StsOk; +} + +/// write a frame with FFMPEG +bool CvVideoWriter_FFMPEG::writeFrame( const IplImage * image ) +{ + bool ret = false; + + CV_FUNCNAME("CvVideoWriter_FFMPEG::writerFrame"); + + __BEGIN__; + + // typecast from opaque data type to implemented struct +#if LIBAVFORMAT_BUILD > 4628 + AVCodecContext *c = video_st->codec; +#else + AVCodecContext *c = &(video_st->codec); +#endif + +#if LIBAVFORMAT_BUILD < 5231 + // It is not needed in the latest versions of the ffmpeg + if( c->codec_id == CODEC_ID_RAWVIDEO && image->origin != IPL_ORIGIN_BL ) + { + if( !temp_image ) + temp_image = cvCreateImage( cvGetSize(image), + image->depth, image->nChannels ); + cvFlip( image, temp_image, 0 ); + image = temp_image; + } +#endif + + // check parameters + if (input_pix_fmt == PIX_FMT_BGR24) { + if (image->nChannels != 3 || image->depth != IPL_DEPTH_8U) { + CV_ERROR(CV_StsUnsupportedFormat, "cvWriteFrame() needs images with depth = IPL_DEPTH_8U and nChannels = 3."); + } + } + else if (input_pix_fmt == PIX_FMT_GRAY8) { + if (image->nChannels != 1 || image->depth != IPL_DEPTH_8U) { + CV_ERROR(CV_StsUnsupportedFormat, "cvWriteFrame() needs images with depth = IPL_DEPTH_8U and nChannels = 1."); + } + } + else { + assert(false); + } + + // check if buffer sizes match, i.e. image has expected format (size, channels, bitdepth, alignment) + assert (image->imageSize == avpicture_get_size( input_pix_fmt, image->width, image->height )); + + if ( c->pix_fmt != input_pix_fmt ) { + assert( input_picture ); + // let input_picture point to the raw data buffer of 'image' + avpicture_fill((AVPicture *)input_picture, (uint8_t *) image->imageData, + (PixelFormat)input_pix_fmt, image->width, image->height); + +#if !defined(HAVE_FFMPEG_SWSCALE) + // convert to the color format needed by the codec + if( img_convert((AVPicture *)picture, c->pix_fmt, + (AVPicture *)input_picture, (PixelFormat)input_pix_fmt, + image->width, image->height) < 0){ + CV_ERROR(CV_StsUnsupportedFormat, "FFMPEG::img_convert pixel format conversion from BGR24 not handled"); + } +#else + img_convert_ctx = sws_getContext(image->width, + image->height, + PIX_FMT_BGR24, + c->width, + c->height, + c->pix_fmt, + SWS_BICUBIC, + NULL, NULL, NULL); + + if ( sws_scale(img_convert_ctx, input_picture->data, + input_picture->linesize, 0, + image->height, + picture->data, picture->linesize) < 0 ) + { + CV_ERROR(CV_StsUnsupportedFormat, "FFMPEG::img_convert pixel format conversion from BGR24 not handled"); + } + sws_freeContext(img_convert_ctx); +#endif + } + else{ + avpicture_fill((AVPicture *)picture, (uint8_t *) image->imageData, + (PixelFormat)input_pix_fmt, image->width, image->height); + } + + ret = icv_av_write_frame_FFMPEG( oc, video_st, outbuf, outbuf_size, picture) >= 0; + + __END__; + return ret; +} + +/// close video output stream and free associated memory +void CvVideoWriter_FFMPEG::close() +{ + unsigned i; + + // nothing to do if already released + if ( !picture ) + return; + + /* no more frame to compress. The codec has a latency of a few + frames if using B frames, so we get the last frames by + passing the same picture again */ + // TODO -- do we need to account for latency here? + + /* write the trailer, if any */ + av_write_trailer(oc); + + // free pictures +#if LIBAVFORMAT_BUILD > 4628 + if( video_st->codec->pix_fmt != input_pix_fmt){ +#else + if( video_st->codec.pix_fmt != input_pix_fmt){ +#endif + cvFree(&(picture->data[0])); + } + av_free(picture); + + if (input_picture) { + av_free(input_picture); + } + + /* close codec */ +#if LIBAVFORMAT_BUILD > 4628 + avcodec_close(video_st->codec); +#else + avcodec_close(&(video_st->codec)); +#endif + + av_free(outbuf); + + /* free the streams */ + for(i = 0; i < oc->nb_streams; i++) { + av_freep(&oc->streams[i]->codec); + av_freep(&oc->streams[i]); + } + + if (!(fmt->flags & AVFMT_NOFILE)) { + /* close the output file */ + + +#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(49<<8)+0) + url_fclose(oc->pb); +#else + url_fclose(&oc->pb); +#endif + + } + + /* free the stream */ + av_free(oc); + + cvReleaseImage( &temp_image ); + + init(); +} + +/// Create a video writer object that uses FFMPEG +bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, + double fps, CvSize frameSize, bool is_color ) +{ + CV_FUNCNAME("CvVideoWriter_FFMPEG::open"); + + CodecID codec_id = CODEC_ID_NONE; + int err, codec_pix_fmt, bitrate_scale=64; + + __BEGIN__; + + close(); + + // check arguments + assert (filename); + assert (fps > 0); + assert (frameSize.width > 0 && frameSize.height > 0); + + // tell FFMPEG to register codecs + av_register_all (); + + /* auto detect the output format from the name and fourcc code. */ + fmt = guess_format(NULL, filename, NULL); + if (!fmt) { + CV_ERROR( CV_StsUnsupportedFormat, "FFMPEG does not recognize the given file extension"); + } + + /* determine optimal pixel format */ + if (is_color) { + input_pix_fmt = PIX_FMT_BGR24; + } + else { + input_pix_fmt = PIX_FMT_GRAY8; + } + + // alloc memory for context + oc = av_alloc_format_context(); + assert (oc); + + /* set file name */ + oc->oformat = fmt; + snprintf(oc->filename, sizeof(oc->filename), "%s", filename); + + /* set some options */ + oc->max_delay = (int)(0.7*AV_TIME_BASE); /* This reduces buffer underrun warnings with MPEG */ + + /* Lookup codec_id for given fourcc */ +#if LIBAVCODEC_VERSION_INT<((51<<16)+(49<<8)+0) + if( (codec_id = codec_get_bmp_id( fourcc )) == CODEC_ID_NONE ){ + CV_ERROR( CV_StsUnsupportedFormat, + "FFMPEG could not find a codec matching the given FOURCC code. Use fourcc=CV_FOURCC_DEFAULT for auto selection." ); + } +#else + { + const struct AVCodecTag * tags[] = { codec_bmp_tags, NULL}; + if( (codec_id = av_codec_get_id(tags, fourcc)) == CODEC_ID_NONE ){ + CV_ERROR( CV_StsUnsupportedFormat, + "FFMPEG could not find a codec matching the given FOURCC code. Use fourcc=CV_FOURCC_DEFAULT for auto selection." ); + } + } +#endif + + // set a few optimal pixel formats for lossless codecs of interest.. + switch (codec_id) { +#if LIBAVCODEC_VERSION_INT>((50<<16)+(1<<8)+0) + case CODEC_ID_JPEGLS: + // BGR24 or GRAY8 depending on is_color... + codec_pix_fmt = input_pix_fmt; + break; +#endif + case CODEC_ID_HUFFYUV: + codec_pix_fmt = PIX_FMT_YUV422P; + break; + case CODEC_ID_MJPEG: + case CODEC_ID_LJPEG: + codec_pix_fmt = PIX_FMT_YUVJ420P; + bitrate_scale = 128; + break; + case CODEC_ID_RAWVIDEO: + default: + // good for lossy formats, MPEG, etc. + codec_pix_fmt = PIX_FMT_YUV420P; + break; + } + + // TODO -- safe to ignore output audio stream? + video_st = icv_add_video_stream_FFMPEG(oc, codec_id, + frameSize.width, frameSize.height, frameSize.width*frameSize.height*bitrate_scale, + fps, codec_pix_fmt); + + + /* set the output parameters (must be done even if no + parameters). */ + if (av_set_parameters(oc, NULL) < 0) { + CV_ERROR(CV_StsBadArg, "Invalid output format parameters"); + } + + dump_format(oc, 0, filename, 1); + + /* now that all the parameters are set, we can open the audio and + video codecs and allocate the necessary encode buffers */ + if (!video_st){ + CV_ERROR(CV_StsBadArg, "Couldn't open video stream"); + } + + AVCodec *codec; + AVCodecContext *c; + +#if LIBAVFORMAT_BUILD > 4628 + c = (video_st->codec); +#else + c = &(video_st->codec); +#endif + + c->codec_tag = fourcc; + /* find the video encoder */ + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { + CV_ERROR(CV_StsBadArg, "codec not found"); + } + + /* open the codec */ + if ( (err=avcodec_open(c, codec)) < 0) { + char errtext[256]; + sprintf(errtext, "Could not open codec '%s': %s", codec->name, icvFFMPEGErrStr(err)); + CV_ERROR(CV_StsBadArg, errtext); + } + + outbuf = NULL; + + if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { + /* allocate output buffer */ + /* assume we will never get codec output with more than 4 bytes per pixel... */ + outbuf_size = frameSize.width*frameSize.height*4; + outbuf = (uint8_t *) av_malloc(outbuf_size); + } + + bool need_color_convert; + need_color_convert = (c->pix_fmt != input_pix_fmt); + + /* allocate the encoded raw picture */ + picture = icv_alloc_picture_FFMPEG(c->pix_fmt, c->width, c->height, need_color_convert); + if (!picture) { + CV_ERROR(CV_StsNoMem, "Could not allocate picture"); + } + + /* if the output format is not our input format, then a temporary + picture of the input format is needed too. It is then converted + to the required output format */ + input_picture = NULL; + if ( need_color_convert ) { + input_picture = icv_alloc_picture_FFMPEG(input_pix_fmt, c->width, c->height, false); + if (!input_picture) { + CV_ERROR(CV_StsNoMem, "Could not allocate picture"); + } + } + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) { + CV_ERROR(CV_StsBadArg, "Couldn't open output file for writing"); + } + } + + /* write the stream header, if any */ + av_write_header( oc ); + + + __END__; + + return true; +} + +CvVideoWriter* cvCreateVideoWriter_FFMPEG( const char* filename, int fourcc, double fps, + CvSize frameSize, int isColor ) +{ + CvVideoWriter_FFMPEG* writer = new CvVideoWriter_FFMPEG; + if( writer->open( filename, fourcc, fps, frameSize, isColor != 0 )) + return writer; + delete writer; + return 0; +}