Some FFmpeg changes
[blender.git] / source / gameengine / VideoTexture / VideoFFmpeg.cpp
index f21555a..1ba944a 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id$
+/*
 -----------------------------------------------------------------------------
 This source file is part of VideoTexture library
 
@@ -20,10 +20,17 @@ http://www.gnu.org/copyleft/lesser.txt.
 -----------------------------------------------------------------------------
 */
 
+/** \file gameengine/VideoTexture/VideoFFmpeg.cpp
+ *  \ingroup bgevideotex
+ */
+
+
 #ifdef WITH_FFMPEG
 
 // INT64_C fix for some linux machines (C99ism)
+#ifndef __STDC_CONSTANT_MACROS
 #define __STDC_CONSTANT_MACROS
+#endif
 #include <stdint.h>
 
 
@@ -32,8 +39,8 @@ http://www.gnu.org/copyleft/lesser.txt.
 
 #include <string>
 
-#include "Exception.h"
 #include "VideoFFmpeg.h"
+#include "Exception.h"
 
 
 // default framerate
@@ -45,8 +52,6 @@ const long timeScale = 1000;
 #define CATCH_EXCP catch (Exception & exp) \
 { exp.report(); m_status = SourceError; }
 
-extern "C" void do_init_ffmpeg();
-
 // class RenderVideo
 
 // constructor
@@ -54,9 +59,9 @@ VideoFFmpeg::VideoFFmpeg (HRESULT * hRslt) : VideoBase(),
 m_codec(NULL), m_formatCtx(NULL), m_codecCtx(NULL), 
 m_frame(NULL), m_frameDeinterlaced(NULL), m_frameRGB(NULL), m_imgConvertCtx(NULL),
 m_deinterlace(false), m_preseek(0),    m_videoStream(-1), m_baseFrameRate(25.0),
-m_lastFrame(-1),  m_eof(false), m_curPosition(-1), m_startTime(0), 
+m_lastFrame(-1),  m_eof(false), m_externTime(false), m_curPosition(-1), m_startTime(0), 
 m_captWidth(0), m_captHeight(0), m_captRate(0.f), m_isImage(false),
-m_isThreaded(false), m_stopThread(false), m_cacheStarted(false)
+m_isThreaded(false), m_isStreaming(false), m_stopThread(false), m_cacheStarted(false)
 {
        // set video format
        m_format = RGB24;
@@ -155,17 +160,17 @@ void VideoFFmpeg::initParams (short width, short height, float rate, bool image)
 }
 
 
-int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AVFormatParameters *formatParams)
+int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AVDictionary **formatParams)
 {
-       AVFormatContext *formatCtx;
+       AVFormatContext *formatCtx = NULL;
        int                             i, videoStream;
        AVCodec                 *codec;
        AVCodecContext  *codecCtx;
 
-       if(av_open_input_file(&formatCtx, filename, inputFormat, 0, formatParams)!=0)
+       if (avformat_open_input(&formatCtx, filename, inputFormat, formatParams)!=0)
                return -1;
 
-       if(av_find_stream_info(formatCtx)<0) 
+       if (av_find_stream_info(formatCtx)<0) 
        {
                av_close_input_file(formatCtx);
                return -1;
@@ -173,18 +178,18 @@ int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AV
 
        /* Find the first video stream */
        videoStream=-1;
-       for(i=0; i<formatCtx->nb_streams; i++)
+       for (i=0; i<formatCtx->nb_streams; i++)
        {
-               if(formatCtx->streams[i] &&
+               if (formatCtx->streams[i] &&
                        get_codec_from_stream(formatCtx->streams[i]) && 
-                       (get_codec_from_stream(formatCtx->streams[i])->codec_type==CODEC_TYPE_VIDEO))
+                       (get_codec_from_stream(formatCtx->streams[i])->codec_type==AVMEDIA_TYPE_VIDEO))
                {
                        videoStream=i;
                        break;
                }
        }
 
-       if(videoStream==-1) 
+       if (videoStream==-1) 
        {
                av_close_input_file(formatCtx);
                return -1;
@@ -194,20 +199,20 @@ int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AV
 
        /* Find the decoder for the video stream */
        codec=avcodec_find_decoder(codecCtx->codec_id);
-       if(codec==NULL) 
+       if (codec==NULL) 
        {
                av_close_input_file(formatCtx);
                return -1;
        }
        codecCtx->workaround_bugs = 1;
-       if(avcodec_open(codecCtx, codec)<0) 
+       if (avcodec_open(codecCtx, codec)<0) 
        {
                av_close_input_file(formatCtx);
                return -1;
        }
 
 #ifdef FFMPEG_OLD_FRAME_RATE
-       if(codecCtx->frame_rate>1000 && codecCtx->frame_rate_base==1)
+       if (codecCtx->frame_rate>1000 && codecCtx->frame_rate_base==1)
                codecCtx->frame_rate_base=1000;
        m_baseFrameRate = (double)codecCtx->frame_rate / (double)codecCtx->frame_rate_base;
 #else
@@ -287,7 +292,7 @@ int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AV
 /*
  * This thread is used to load video frame asynchronously.
  * It provides a frame caching service. 
- * The main thread is responsible for positionning the frame pointer in the
+ * The main thread is responsible for positioning the frame pointer in the
  * file correctly before calling startCache() which starts this thread.
  * The cache is organized in two layers: 1) a cache of 20-30 undecoded packets to keep
  * memory and CPU low 2) a cache of 5 decoded frames. 
@@ -304,6 +309,11 @@ void *VideoFFmpeg::cacheThread(void *data)
        CachePacket *cachePacket;
        bool endOfFile = false;
        int frameFinished = 0;
+       double timeBase = av_q2d(video->m_formatCtx->streams[video->m_videoStream]->time_base);
+       int64_t startTs = video->m_formatCtx->streams[video->m_videoStream]->start_time;
+
+       if (startTs == AV_NOPTS_VALUE)
+               startTs = 0;
 
        while (!video->m_stopThread)
        {
@@ -358,10 +368,10 @@ void *VideoFFmpeg::cacheThread(void *data)
                                BLI_remlink(&video->m_packetCacheBase, cachePacket);
                                // use m_frame because when caching, it is not used in main thread
                                // we can't use currentFrame directly because we need to convert to RGB first
-                               avcodec_decode_video(video->m_codecCtx, 
+                               avcodec_decode_video2(video->m_codecCtx, 
                                        video->m_frame, &frameFinished, 
-                                       cachePacket->packet.data, cachePacket->packet.size);
-                               if(frameFinished) 
+                                       &cachePacket->packet);
+                               if (frameFinished) 
                                {
                                        AVFrame * input = video->m_frame;
 
@@ -390,7 +400,8 @@ void *VideoFFmpeg::cacheThread(void *data)
                                                        currentFrame->frame->data,
                                                        currentFrame->frame->linesize);
                                                // move frame to queue, this frame is necessarily the next one
-                                               currentFrame->framePosition = ++video->m_curPosition;
+                                               video->m_curPosition = (long)((cachePacket->packet.dts-startTs) * (video->m_baseFrameRate*timeBase) + 0.5);
+                                               currentFrame->framePosition = video->m_curPosition;
                                                pthread_mutex_lock(&video->m_cacheMutex);
                                                BLI_addtail(&video->m_frameCacheBase, currentFrame);
                                                pthread_mutex_unlock(&video->m_cacheMutex);
@@ -508,8 +519,6 @@ void VideoFFmpeg::releaseFrame(AVFrame* frame)
 // open video file
 void VideoFFmpeg::openFile (char * filename)
 {
-       do_init_ffmpeg();
-
        if (openStream(filename, NULL, NULL) != 0)
                return;
 
@@ -532,15 +541,13 @@ void VideoFFmpeg::openFile (char * filename)
                // but it is really not desirable to seek on http file, so force streaming.
                // It would be good to find this information from the context but there are no simple indication
                !strncmp(filename, "http://", 7) ||
-#ifdef FFMPEG_PB_IS_POINTER
-        (m_formatCtx->pb && m_formatCtx->pb->is_streamed)
-#else
-        m_formatCtx->pb.is_streamed
-#endif
-        )
+               (m_formatCtx->pb && !m_formatCtx->pb->seekable)
+               )
        {
-               // the file is in fact a streaming source, prevent seeking
+               // the file is in fact a streaming source, treat as cam to prevent seeking
                m_isFile = false;
+               // but it's not handled exactly like a camera.
+               m_isStreaming = true;
                // for streaming it is important to do non blocking read
                m_formatCtx->flags |= AVFMT_FLAG_NONBLOCK;
        }
@@ -571,13 +578,10 @@ void VideoFFmpeg::openCam (char * file, short camIdx)
 {
        // open camera source
        AVInputFormat           *inputFormat;
-       AVFormatParameters      formatParams;
-       AVRational                      frameRate;
-       char                            *p, filename[28], rateStr[20];
-
-       do_init_ffmpeg();
+       AVDictionary            *formatParams = NULL;
+       char                            filename[28], rateStr[20];
+       char                *p;
 
-       memset(&formatParams, 0, sizeof(formatParams));
 #ifdef WIN32
        // video capture on windows only through Video For Windows driver
        inputFormat = av_find_input_format("vfwcap");
@@ -594,7 +598,7 @@ void VideoFFmpeg::openCam (char * file, short camIdx)
        // The driver name is constructed automatically from the device type:
        // v4l   : /dev/video<camIdx>
        // dv1394: /dev/dv1394/<camIdx>
-       // If you have different driver name, you can specify the driver name explicitely 
+       // If you have different driver name, you can specify the driver name explicitly
        // instead of device type. Examples of valid filename:
        //    /dev/v4l/video0:pal
        //    /dev/ieee1394/1:ntsc
@@ -607,7 +611,13 @@ void VideoFFmpeg::openCam (char * file, short camIdx)
                sprintf(filename, "/dev/dv1394/%d", camIdx);
        } else 
        {
-               inputFormat = av_find_input_format("video4linux");
+               const char *formats[] = {"video4linux2,v4l2", "video4linux2", "video4linux"};
+               int i, formatsCount = sizeof(formats) / sizeof(char*);
+               for (i = 0; i < formatsCount; i++) {
+                       inputFormat = av_find_input_format(formats[i]);
+                       if (inputFormat)
+                               break;
+               }
                sprintf(filename, "/dev/video%d", camIdx);
        }
        if (!inputFormat)
@@ -621,20 +631,22 @@ void VideoFFmpeg::openCam (char * file, short camIdx)
                if ((p = strchr(filename, ':')) != 0)
                        *p = 0;
        }
-       if (file && (p = strchr(file, ':')) != NULL)
-               formatParams.standard = p+1;
+       if (file && (p = strchr(file, ':')) != NULL) {
+               av_dict_set(&formatParams, "standard", p+1, 0);
+       }
 #endif
        //frame rate
        if (m_captRate <= 0.f)
                m_captRate = defFrameRate;
        sprintf(rateStr, "%f", m_captRate);
-       av_parse_video_frame_rate(&frameRate, rateStr);
-       // populate format parameters
-       // need to specify the time base = inverse of rate
-       formatParams.time_base.num = frameRate.den;
-       formatParams.time_base.den = frameRate.num;
-       formatParams.width = m_captWidth;
-       formatParams.height = m_captHeight;
+
+       av_dict_set(&formatParams, "framerate", rateStr, 0);
+
+       if (m_captWidth > 0 && m_captHeight > 0) {
+               char video_size[64];
+               BLI_snprintf(video_size, sizeof(video_size), "%dx%d", m_captWidth, m_captHeight);
+               av_dict_set(&formatParams, "video_size", video_size, 0);
+       }
 
        if (openStream(filename, inputFormat, &formatParams) != 0)
                return;
@@ -649,6 +661,8 @@ void VideoFFmpeg::openCam (char * file, short camIdx)
                // no need to thread if the system has a single core
                m_isThreaded =  true;
        }
+
+       av_dict_free(&formatParams);
 }
 
 // play video
@@ -723,22 +737,32 @@ void VideoFFmpeg::setFrameRate (float rate)
 
 
 // image calculation
-void VideoFFmpeg::calcImage (unsigned int texId)
-{
-       loadFrame();
-}
-
-
 // load frame from video
-void VideoFFmpeg::loadFrame (void)
+void VideoFFmpeg::calcImage (unsigned int texId, double ts)
 {
        if (m_status == SourcePlaying)
        {
                // get actual time
                double startTime = PIL_check_seconds_timer();
-               if (m_lastFrame == -1 && !m_isFile)
-                       m_startTime = startTime;
-               double actTime = startTime - m_startTime;
+               double actTime;
+               // timestamp passed from audio actuators can sometimes be slightly negative
+               if (m_isFile && ts >= -0.5)
+               {
+                       // allow setting timestamp only when not streaming
+                       actTime = ts;
+                       if (actTime * actFrameRate() < m_lastFrame) 
+                       {
+                               // user is asking to rewind, force a cache clear to make sure we will do a seek
+                               // note that this does not decrement m_repeat if ts didn't reach m_range[1]
+                               stopCache();
+                       }
+               }
+               else
+               {
+                       if (m_lastFrame == -1 && !m_isFile)
+                               m_startTime = startTime;
+                       actTime = startTime - m_startTime;
+               }
                // if video has ended
                if (m_isFile && actTime * m_frameRate >= m_range[1])
                {
@@ -768,7 +792,7 @@ void VideoFFmpeg::loadFrame (void)
                {
                        AVFrame* frame;
                        // get image
-                       if((frame = grabFrame(actFrame)) != NULL)
+                       if ((frame = grabFrame(actFrame)) != NULL)
                        {
                                if (!m_isFile && !m_cacheStarted) 
                                {
@@ -796,12 +820,12 @@ void VideoFFmpeg::loadFrame (void)
                                        // close the file as we don't need it anymore
                                        release();
                                }
-                       } else if (!m_isFile)
+                       } else if (m_isStreaming)
                        {
                                // we didn't get a frame and we are streaming, this may be due to
                                // a delay in the network or because we are getting the frame too fast.
                                // In the later case, shift time by a small amount to compensate for a drift
-                               m_startTime += 0.01;
+                               m_startTime += 0.001;
                        }
                }
        }
@@ -819,7 +843,7 @@ void VideoFFmpeg::setPositions (void)
                m_startTime -= double(m_lastFrame) / actFrameRate();
        else {
                m_startTime -= m_range[0];
-               // start from begining, stop cache just in case
+               // start from beginning, stop cache just in case
                stopCache();
        }
 }
@@ -831,8 +855,9 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
        int frameFinished;
        int posFound = 1;
        bool frameLoaded = false;
-       long long targetTs = 0;
+       int64_t targetTs = 0;
        CacheFrame *frame;
+       int64_t dts = 0;
 
        if (m_cacheStarted)
        {
@@ -862,10 +887,18 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                        }
                        // for streaming, always return the next frame, 
                        // that's what grabFrame does in non cache mode anyway.
-                       if (!m_isFile || frame->framePosition == position)
+                       if (m_isStreaming || frame->framePosition == position)
                        {
                                return frame->frame;
                        }
+                       // for cam, skip old frames to keep image realtime.
+                       // There should be no risk of clock drift since it all happens on the same CPU
+                       if (frame->framePosition > position) 
+                       {
+                               // this can happen after rewind if the seek didn't find the first frame
+                               // the frame in the buffer is ahead of time, just leave it there
+                               return NULL;
+                       }
                        // this frame is not useful, release it
                        pthread_mutex_lock(&m_cacheMutex);
                        BLI_remlink(&m_frameCacheBase, frame);
@@ -873,6 +906,11 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                        pthread_mutex_unlock(&m_cacheMutex);
                } while (true);
        }
+       double timeBase = av_q2d(m_formatCtx->streams[m_videoStream]->time_base);
+       int64_t startTs = m_formatCtx->streams[m_videoStream]->start_time;
+       if (startTs == AV_NOPTS_VALUE)
+               startTs = 0;
+
        // come here when there is no cache or cache has been stopped
        // locate the frame, by seeking if necessary (seeking is only possible for files)
        if (m_isFile)
@@ -887,12 +925,14 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                        {
                                if (packet.stream_index == m_videoStream) 
                                {
-                                       avcodec_decode_video(
+                                       avcodec_decode_video2(
                                                m_codecCtx, 
                                                m_frame, &frameFinished, 
-                                               packet.data, packet.size);
+                                               &packet);
                                        if (frameFinished)
-                                               m_curPosition++;
+                                       {
+                                               m_curPosition = (long)((packet.dts-startTs) * (m_baseFrameRate*timeBase) + 0.5);
+                                       }
                                }
                                av_free_packet(&packet);
                                if (position == m_curPosition+1)
@@ -902,16 +942,12 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                // if the position is not in preseek, do a direct jump
                if (position != m_curPosition + 1) 
                { 
-                       double timeBase = av_q2d(m_formatCtx->streams[m_videoStream]->time_base);
                        int64_t pos = (int64_t)((position - m_preseek) / (m_baseFrameRate*timeBase));
-                       int64_t startTs = m_formatCtx->streams[m_videoStream]->start_time;
-                       int seekres;
 
                        if (pos < 0)
                                pos = 0;
 
-                       if (startTs != AV_NOPTS_VALUE)
-                               pos += startTs;
+                       pos += startTs;
 
                        if (position <= m_curPosition || !m_eof)
                        {
@@ -922,7 +958,7 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                                // of the file.
                                if (position <= m_preseek)
                                {
-                                       // we can safely go the begining of the file
+                                       // we can safely go the beginning of the file
                                        if (av_seek_frame(m_formatCtx, m_videoStream, 0, AVSEEK_FLAG_BYTE) >= 0)
                                        {
                                                // binary seek does not reset the timestamp, must do it now
@@ -943,9 +979,7 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
                                }
                        }
                        // this is the timestamp of the frame we're looking for
-                       targetTs = (int64_t)(position / (m_baseFrameRate * timeBase));
-                       if (startTs != AV_NOPTS_VALUE)
-                               targetTs += startTs;
+                       targetTs = (int64_t)(position / (m_baseFrameRate * timeBase)) + startTs;
 
                        posFound = 0;
                        avcodec_flush_buffers(m_codecCtx);
@@ -964,19 +998,22 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
        // return the next frame. This is not quite correct, may need more work
        while(av_read_frame(m_formatCtx, &packet)>=0) 
        {
-               if(packet.stream_index == m_videoStream) 
+               if (packet.stream_index == m_videoStream) 
                {
-                       avcodec_decode_video(m_codecCtx, 
+                       avcodec_decode_video2(m_codecCtx, 
                                m_frame, &frameFinished, 
-                               packet.data, packet.size);
-
+                               &packet);
+                       // remember dts to compute exact frame number
+                       dts = packet.dts;
                        if (frameFinished && !posFound) 
                        {
-                               if (packet.dts >= targetTs)
+                               if (dts >= targetTs)
+                               {
                                        posFound = 1;
+                               }
                        } 
 
-                       if(frameFinished && posFound == 1) 
+                       if (frameFinished && posFound == 1) 
                        {
                                AVFrame * input = m_frame;
 
@@ -1019,7 +1056,7 @@ AVFrame *VideoFFmpeg::grabFrame(long position)
        m_eof = m_isFile && !frameLoaded;
        if (frameLoaded)
        {
-               m_curPosition = position;
+               m_curPosition = (long)((dts-startTs) * (m_baseFrameRate*timeBase) + 0.5);
                if (m_isThreaded)
                {
                        // normal case for file: first locate, then start cache
@@ -1059,11 +1096,11 @@ static int VideoFFmpeg_init (PyObject * pySelf, PyObject * args, PyObject * kwds
        // capture rate, only if capt is >= 0
        float rate = 25.f;
 
-       static char *kwlist[] = {"file", "capture", "rate", "width", "height", NULL};
+       static const char *kwlist[] = {"file", "capture", "rate", "width", "height", NULL};
 
        // get parameters
-       if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|hfhh", kwlist, &file, &capt,
-               &rate, &width, &height))
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|hfhh",
+               const_cast<char**>(kwlist), &file, &capt, &rate, &width, &height))
                return -1; 
 
        try
@@ -1147,9 +1184,10 @@ static PyGetSetDef videoGetSets[] =
        {(char*)"repeat", (getter)Video_getRepeat, (setter)Video_setRepeat, (char*)"repeat count, -1 for infinite repeat", NULL},
        {(char*)"framerate", (getter)Video_getFrameRate, (setter)Video_setFrameRate, (char*)"frame rate", NULL},
        // attributes from ImageBase class
+       {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
        {(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
        {(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
-       {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
+       {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL},
        {(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL},
        {(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL},
        {(char*)"preseek", (getter)VideoFFmpeg_getPreseek, (setter)VideoFFmpeg_setPreseek, (char*)"nb of frames of preseek", NULL},
@@ -1178,7 +1216,7 @@ PyTypeObject VideoFFmpegType =
        0,                         /*tp_str*/
        0,                         /*tp_getattro*/
        0,                         /*tp_setattro*/
-       0,                         /*tp_as_buffer*/
+       &imageBufferProcs,         /*tp_as_buffer*/
        Py_TPFLAGS_DEFAULT,        /*tp_flags*/
        "FFmpeg video source",       /* tp_doc */
        0,                             /* tp_traverse */
@@ -1267,9 +1305,10 @@ static PyGetSetDef imageGetSets[] =
 { // methods from VideoBase class
        {(char*)"status", (getter)Video_getStatus, NULL, (char*)"video status", NULL},
        // attributes from ImageBase class
+       {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
        {(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
        {(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
-       {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbour)", NULL},
+       {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL},
        {(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL},
        {(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL},
        {NULL}
@@ -1296,7 +1335,7 @@ PyTypeObject ImageFFmpegType =
        0,                         /*tp_str*/
        0,                         /*tp_getattro*/
        0,                         /*tp_setattro*/
-       0,                         /*tp_as_buffer*/
+       &imageBufferProcs,         /*tp_as_buffer*/
        Py_TPFLAGS_DEFAULT,        /*tp_flags*/
        "FFmpeg image source",       /* tp_doc */
        0,                             /* tp_traverse */