[vlc-commits] sout: sdi: rework buffering

Francois Cartegnie git at videolan.org
Thu Jul 11 15:15:06 CEST 2019


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Tue Jun  4 18:58:55 2019 +0200| [f2bcfd9758eabd19c82a078af2ef0f6cc857fb35] | committer: Francois Cartegnie

sout: sdi: rework buffering

Full rework of the SDI buffering to handle stream
interleaving, truncation, decoder output pacing,
threaded frame conversion & filtering.
Now also does proper preroll.

Remaining frame drops are due to munmap()

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=f2bcfd9758eabd19c82a078af2ef0f6cc857fb35
---

 modules/stream_out/sdi/DBMSDIOutput.cpp      | 286 ++++++++++++++++++++++-----
 modules/stream_out/sdi/DBMSDIOutput.hpp      |  26 ++-
 modules/stream_out/sdi/SDIAudioMultiplex.cpp |  12 +-
 modules/stream_out/sdi/SDIAudioMultiplex.hpp |   4 +
 modules/stream_out/sdi/SDIOutput.cpp         |  28 ++-
 modules/stream_out/sdi/SDIOutput.hpp         |   1 +
 modules/stream_out/sdi/SDIStream.cpp         | 253 ++++++++++++++++++++----
 modules/stream_out/sdi/SDIStream.hpp         |  41 +++-
 8 files changed, 558 insertions(+), 93 deletions(-)

diff --git a/modules/stream_out/sdi/DBMSDIOutput.cpp b/modules/stream_out/sdi/DBMSDIOutput.cpp
index 431ea4eeb6..19de3288e1 100644
--- a/modules/stream_out/sdi/DBMSDIOutput.cpp
+++ b/modules/stream_out/sdi/DBMSDIOutput.cpp
@@ -47,6 +47,12 @@
 
 #include <arpa/inet.h>
 
+#define DECKLINK_CARD_BUFFER (CLOCK_FREQ)
+#define DECKLINK_PREROLL (CLOCK_FREQ*3/4)
+#define DECKLINK_SCHED_OFFSET (CLOCK_FREQ/20)
+
+static_assert(DECKLINK_CARD_BUFFER > DECKLINK_PREROLL + DECKLINK_SCHED_OFFSET, "not in card buffer limits");
+
 using namespace sdi_sout;
 
 DBMSDIOutput::DBMSDIOutput(sout_stream_t *p_stream) :
@@ -58,10 +64,21 @@ DBMSDIOutput::DBMSDIOutput(sout_stream_t *p_stream) :
     clock.offset = 0;
     lasttimestamp = 0;
     b_running = false;
+    streamStartTime = VLC_TICK_INVALID;
+    vlc_mutex_init(&feeder.lock);
+    vlc_cond_init(&feeder.cond);
 }
 
 DBMSDIOutput::~DBMSDIOutput()
 {
+    if(p_output)
+    {
+        while(!isDrained())
+            vlc_tick_wait(vlc_tick_now() + CLOCK_FREQ/60);
+        vlc_cancel(feeder.thread);
+        vlc_join(feeder.thread, NULL);
+    }
+
     es_format_Clean(&video.configuredfmt);
     if(p_output)
     {
@@ -73,6 +90,9 @@ DBMSDIOutput::~DBMSDIOutput()
     }
     if(p_card)
         p_card->Release();
+
+    vlc_cond_destroy(&feeder.cond);
+    vlc_mutex_destroy(&feeder.lock);
 }
 
 AbstractStream *DBMSDIOutput::Add(const es_format_t *fmt)
@@ -92,16 +112,6 @@ AbstractStream *DBMSDIOutput::Add(const es_format_t *fmt)
     return s;
 }
 
-int DBMSDIOutput::Send(AbstractStream *s, block_t *b)
-{
-    if(!b_running && b->i_dts != VLC_TICK_INVALID)
-    {
-        if( videoStream && (!audioStreams.empty() || audio.i_channels == 0) )
-            Start(b->i_dts);
-    }
-    return SDIOutput::Send(s, b);
-}
-
 #define CHECK(message) do { \
     if (result != S_OK) \
     { \
@@ -114,6 +124,43 @@ int DBMSDIOutput::Send(AbstractStream *s, block_t *b)
 } \
 } while(0)
 
+HRESULT STDMETHODCALLTYPE DBMSDIOutput::QueryInterface( REFIID, LPVOID * )
+{
+    return E_NOINTERFACE;
+}
+
+ULONG STDMETHODCALLTYPE DBMSDIOutput::AddRef()
+{
+    return 1;
+}
+
+ULONG STDMETHODCALLTYPE DBMSDIOutput::Release()
+{
+    return 1;
+}
+
+HRESULT STDMETHODCALLTYPE DBMSDIOutput::ScheduledFrameCompleted
+    (IDeckLinkVideoFrame *, BMDOutputFrameCompletionResult result)
+{
+    if(result == bmdOutputFrameDropped)
+        msg_Warn(p_stream, "dropped frame");
+    else if(result == bmdOutputFrameDisplayedLate)
+        msg_Warn(p_stream, "late frame");
+
+    bool b_active;
+    vlc_mutex_lock(&feeder.lock);
+    if((S_OK == p_output->IsScheduledPlaybackRunning(&b_active)) && b_active)
+        vlc_cond_signal(&feeder.cond);
+    vlc_mutex_unlock(&feeder.lock);
+
+    return S_OK;
+}
+
+HRESULT DBMSDIOutput::ScheduledPlaybackHasStopped (void)
+{
+    return S_OK;
+}
+
 int DBMSDIOutput::Open()
 {
     HRESULT result;
@@ -159,6 +206,9 @@ int DBMSDIOutput::Open()
 
     decklink_iterator->Release();
 
+    if(vlc_clone(&feeder.thread, feederThreadCallback, this, VLC_THREAD_PRIORITY_INPUT))
+        goto error;
+
     return VLC_SUCCESS;
 
 error:
@@ -216,6 +266,13 @@ int DBMSDIOutput::ConfigureAudio(const audio_format_t *)
                     maxchannels,
                     bmdAudioOutputStreamTimestamped);
         CHECK("Could not start audio output");
+
+        if(S_OK != p_output->BeginAudioPreroll())
+        {
+            p_output->DisableAudioOutput();
+            goto error;
+        }
+
         audio.b_configured = true;
 
         p_attributes->Release();
@@ -378,6 +435,8 @@ int DBMSDIOutput::ConfigureVideo(const video_format_t *vfmt)
         result = p_output->EnableVideoOutput(mode_id, flags);
         CHECK("Could not enable video output");
 
+        p_output->SetScheduledFrameCompletionCallback(this);
+
         video_format_Copy(fmt, vfmt);
         fmt->i_width = fmt->i_visible_width = p_display_mode->GetWidth();
         fmt->i_height = fmt->i_visible_height = p_display_mode->GetHeight();
@@ -416,7 +475,7 @@ error:
     return VLC_EGENERIC;
 }
 
-int DBMSDIOutput::Start(vlc_tick_t startTime)
+int DBMSDIOutput::StartPlayback()
 {
     HRESULT result;
     if(FAKE_DRIVER && !b_running)
@@ -427,7 +486,10 @@ int DBMSDIOutput::Start(vlc_tick_t startTime)
     if(b_running)
         return VLC_EGENERIC;
 
-    result = p_output->StartScheduledPlayback(startTime, CLOCK_FREQ, 1.0);
+    if(audio.b_configured)
+        p_output->EndAudioPreroll();
+
+    result = p_output->StartScheduledPlayback(streamStartTime, CLOCK_FREQ, 1.0);
     CHECK("Could not start playback");
     b_running = true;
     return VLC_SUCCESS;
@@ -436,45 +498,163 @@ error:
     return VLC_EGENERIC;
 }
 
-int DBMSDIOutput::Process()
+int DBMSDIOutput::FeedOneFrame()
 {
-    if((!p_output && !FAKE_DRIVER) || !b_running)
-        return VLC_EGENERIC;
+    picture_t *p = reinterpret_cast<picture_t *>(videoBuffer.Dequeue());
+    if(p)
+        return ProcessVideo(p, reinterpret_cast<block_t *>(captionsBuffer.Dequeue()));
 
-    picture_t *p;
-    while((p = reinterpret_cast<picture_t *>(videoBuffer.Dequeue())))
-    {
-        vlc_tick_t bufferStart = audioMultiplex->bufferStart();
-        unsigned i_samples_per_frame =
-                audioMultiplex->alignedInterleaveInSamples(bufferStart, SAMPLES_PER_FRAME);
+    return VLC_SUCCESS;
+}
 
+int DBMSDIOutput::FeedAudio(vlc_tick_t start, vlc_tick_t preroll, bool b_truncate)
+{
 #ifdef SDI_MULTIPLEX_DEBUG
         audioMultiplex->Debug();
 #endif
+    vlc_tick_t bufferStart = audioMultiplex->bufferStart();
+    unsigned i_samples_per_frame =
+            audioMultiplex->alignedInterleaveInSamples(bufferStart, SAMPLES_PER_FRAME);
 
-        while(bufferStart <= p->date &&
-              audioMultiplex->availableVirtualSamples(bufferStart) >= i_samples_per_frame)
+    unsigned buffered = 0;
+    while(bufferStart <= (start+preroll) &&
+          audioMultiplex->availableVirtualSamples(bufferStart) >= i_samples_per_frame)
+    {
+        block_t *out = audioMultiplex->Extract(i_samples_per_frame);
+        if(out)
         {
-            block_t *out = audioMultiplex->Extract(i_samples_per_frame);
-            if(out)
-            {
 #ifdef SDI_MULTIPLEX_DEBUG
-                  msg_Dbg(p_stream, "extracted %u samples pts %ld i_samples_per_frame %u",
-                          out->i_nb_samples, out->i_dts, i_samples_per_frame);
+              msg_Dbg(p_stream, "extracted %u samples pts %ld i_samples_per_frame %u",
+                      out->i_nb_samples, out->i_dts, i_samples_per_frame);
 #endif
+              if(b_truncate && out->i_pts < start)
+              {
+#ifdef SDI_MULTIPLEX_DEBUG
+                  msg_Err(p_stream,"dropping %u samples at %ld (-%ld)",
+                          i_samples_per_frame, out->i_pts, (start - out->i_pts));
+#endif
+                  block_Release(out);
+              }
+              else
+              {
                   ProcessAudio(out);
-            }
-            else break;
-            bufferStart = audioMultiplex->bufferStart();
-            i_samples_per_frame = audioMultiplex->alignedInterleaveInSamples(bufferStart, SAMPLES_PER_FRAME);
+              }
         }
+        else break;
+        bufferStart = audioMultiplex->bufferStart();
+        i_samples_per_frame = audioMultiplex->alignedInterleaveInSamples(bufferStart, SAMPLES_PER_FRAME);
+        buffered += i_samples_per_frame;
+    }
 
-        ProcessVideo(p, reinterpret_cast<block_t *>(captionsBuffer.Dequeue()));
+    return buffered > 0 ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+void * DBMSDIOutput::feederThreadCallback(void *me)
+{
+    reinterpret_cast<DBMSDIOutput *>(me)->feederThread();
+    return NULL;
+}
+
+void DBMSDIOutput::feederThread()
+{
+    vlc_tick_t maxdelay = CLOCK_FREQ/60;
+    for(;;)
+    {
+        vlc_mutex_lock(&feeder.lock);
+        mutex_cleanup_push(&feeder.lock);
+        vlc_cond_timedwait(&feeder.cond, &feeder.lock, vlc_tick_now() + maxdelay);
+        vlc_cleanup_pop();
+        int cancel = vlc_savecancel();
+        doSchedule();
+        if(timescale)
+            maxdelay = CLOCK_FREQ * frameduration / timescale;
+        vlc_restorecancel(cancel);
+        vlc_mutex_unlock(&feeder.lock);
     }
+}
+
+int DBMSDIOutput::Process()
+{
+    if((!p_output && !FAKE_DRIVER))
+        return VLC_SUCCESS;
+
+    vlc_mutex_lock(&feeder.lock);
+    vlc_cond_signal(&feeder.cond);
+    vlc_mutex_unlock(&feeder.lock);
 
     return VLC_SUCCESS;
 }
 
+int DBMSDIOutput::doSchedule()
+{
+    const vlc_tick_t preroll = DECKLINK_PREROLL;
+    vlc_tick_t next = videoBuffer.NextPictureTime();
+    if(next == VLC_TICK_INVALID ||
+       (!b_running && !ReachedPlaybackTime(next + preroll + SAMPLES_PER_FRAME*CLOCK_FREQ/48000)))
+        return VLC_SUCCESS;
+
+    if(FAKE_DRIVER)
+    {
+        FeedOneFrame();
+        FeedAudio(next, preroll, false);
+        b_running = true;
+        return VLC_SUCCESS;
+    }
+
+    uint32_t bufferedFramesCount;
+    uint32_t bufferedAudioCount = 0;
+    if(S_OK != p_output->GetBufferedVideoFrameCount(&bufferedFramesCount))
+        return VLC_EGENERIC;
+
+    uint32_t bufferedFramesTarget = (uint64_t)timescale*preroll/frameduration/CLOCK_FREQ;
+    if( bufferedFramesTarget > bufferedFramesCount )
+    {
+        for(size_t i=0; i<bufferedFramesTarget - bufferedFramesCount; i++)
+        {
+            FeedOneFrame();
+            if(b_running)
+                break;
+        }
+        p_output->GetBufferedVideoFrameCount(&bufferedFramesCount);
+    }
+
+    /* no frames got in ?? */
+    if(streamStartTime == VLC_TICK_INVALID)
+    {
+        assert(bufferedFramesTarget);
+        assert(!b_running);
+        return VLC_EGENERIC;
+    }
+
+    if(audio.b_configured)
+    {
+        if(S_OK != p_output->GetBufferedAudioSampleFrameCount(&bufferedAudioCount))
+            return VLC_EGENERIC;
+
+        uint32_t bufferedAudioTarget = 48000*preroll/CLOCK_FREQ;
+        if(bufferedAudioCount < bufferedAudioTarget)
+        {
+            vlc_tick_t audioSamplesDuration = (bufferedAudioTarget - bufferedAudioCount)
+                                            * CLOCK_FREQ / 48000;
+            if(b_running)
+                FeedAudio(next, audioSamplesDuration, false);
+            else
+                FeedAudio(streamStartTime, audioSamplesDuration, true);
+
+            p_output->GetBufferedAudioSampleFrameCount(&bufferedAudioCount);
+        }
+    }
+
+    if(!b_running && bufferedFramesCount >= bufferedFramesTarget)
+    {
+        msg_Dbg(p_stream, "Preroll end with %d frames/%d samples",
+                          bufferedFramesCount, bufferedAudioCount);
+        StartPlayback();
+    }
+
+    return (bufferedFramesCount < bufferedFramesTarget) ? VLC_ENOITEM : VLC_SUCCESS;
+}
+
 int DBMSDIOutput::ProcessAudio(block_t *p_block)
 {
     if(FAKE_DRIVER)
@@ -489,14 +669,13 @@ int DBMSDIOutput::ProcessAudio(block_t *p_block)
         return VLC_EGENERIC;
     }
 
-    p_block->i_pts -= clock.offset;
-
     uint32_t sampleFrameCount = p_block->i_nb_samples;
     uint32_t written;
+    p_block->i_pts -= clock.offset;
+    const BMDTimeValue scheduleTime = p_block->i_pts + DECKLINK_SCHED_OFFSET;
     HRESULT result = p_output->ScheduleAudioSamples(
                 p_block->p_buffer, p_block->i_nb_samples,
-                p_block->i_pts + CLOCK_FREQ,
-                CLOCK_FREQ, &written);
+                scheduleTime, CLOCK_FREQ, &written);
 
     if (result != S_OK)
         msg_Err(p_stream, "Failed to schedule audio sample: 0x%X", result);
@@ -525,8 +704,8 @@ int DBMSDIOutput::ProcessVideo(picture_t *picture, block_t *p_cc)
         double playbackSpeed;
         if(S_OK == p_output->GetScheduledStreamTime(CLOCK_FREQ, &streamTime, &playbackSpeed))
         {
-            if(picture->date + CLOCK_FREQ - streamTime >
-                    VLC_TICK_FROM_SEC(video.nosignal_delay))
+            if(picture->date - streamTime >
+               VLC_TICK_FROM_SEC(video.nosignal_delay))
             {
                 msg_Info(p_stream, "no signal");
                 picture_Hold(video.pic_nosignal);
@@ -543,6 +722,7 @@ int DBMSDIOutput::doProcessVideo(picture_t *picture, block_t *p_cc)
 {
     HRESULT result;
     int w, h, stride, length, ret = VLC_EGENERIC;
+    BMDTimeValue scheduleTime;
     IDeckLinkMutableVideoFrame *pDLVideoFrame = NULL;
     w = video.configuredfmt.video.i_visible_width;
     h = video.configuredfmt.video.i_visible_height;
@@ -613,15 +793,21 @@ int DBMSDIOutput::doProcessVideo(picture_t *picture, block_t *p_cc)
     // compute frame duration in CLOCK_FREQ units
     length = (frameduration * CLOCK_FREQ) / timescale;
     picture->date -= clock.offset;
-    result = p_output->ScheduleVideoFrame(pDLVideoFrame,
-                                          picture->date + CLOCK_FREQ,
-                                          length, CLOCK_FREQ);
+    scheduleTime = picture->date + DECKLINK_SCHED_OFFSET;
+    result = p_output->ScheduleVideoFrame(pDLVideoFrame, scheduleTime, length, CLOCK_FREQ);
     if (result != S_OK) {
         msg_Err(p_stream, "Dropped Video frame %" PRId64 ": 0x%x",
                 picture->date, result);
         goto error;
     }
-    lasttimestamp = __MAX(picture->date, lasttimestamp);
+    lasttimestamp = __MAX(scheduleTime, lasttimestamp);
+
+    if(!b_running) /* preroll */
+    {
+        if(streamStartTime == VLC_TICK_INVALID ||
+           picture->date < streamStartTime)
+            streamStartTime = picture->date;
+    }
 
 end:
     ret = VLC_SUCCESS;
@@ -629,6 +815,7 @@ end:
 error:
     if(p_cc)
         block_Release(p_cc);
+
     picture_Release(picture);
     if (pDLVideoFrame)
         pDLVideoFrame->Release();
@@ -639,7 +826,7 @@ error:
 void DBMSDIOutput::checkClockDrift()
 {
     BMDTimeValue hardwareTime, timeInFrame, ticksPerFrame;
-    if(FAKE_DRIVER)
+    if(!b_running || FAKE_DRIVER)
         return;
     if(S_OK == p_output->GetHardwareReferenceClock(CLOCK_FREQ,
                                                    &hardwareTime,
@@ -664,3 +851,14 @@ void DBMSDIOutput::checkClockDrift()
         }
     }
 }
+
+bool DBMSDIOutput::isDrained()
+{
+    if(b_running)
+    {
+        if(!videoStream->isEOS() || !videoBuffer.isEOS())
+            return false;
+    }
+
+    return true;
+}
diff --git a/modules/stream_out/sdi/DBMSDIOutput.hpp b/modules/stream_out/sdi/DBMSDIOutput.hpp
index 09a614186b..32b77759d5 100644
--- a/modules/stream_out/sdi/DBMSDIOutput.hpp
+++ b/modules/stream_out/sdi/DBMSDIOutput.hpp
@@ -28,7 +28,8 @@
 
 namespace sdi_sout
 {
-    class DBMSDIOutput : public SDIOutput
+    class DBMSDIOutput : public SDIOutput,
+                         public IDeckLinkVideoOutputCallback
     {
         public:
             DBMSDIOutput(sout_stream_t *);
@@ -36,7 +37,12 @@ namespace sdi_sout
             virtual AbstractStream *Add(const es_format_t *); /* reimpl */
             virtual int Open(); /* impl */
             virtual int Process(); /* impl */
-            virtual int Send(AbstractStream *, block_t *); /* reimpl */
+
+            virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID *);
+            virtual ULONG STDMETHODCALLTYPE AddRef ();
+            virtual ULONG STDMETHODCALLTYPE Release ();
+            virtual HRESULT ScheduledFrameCompleted (IDeckLinkVideoFrame *, BMDOutputFrameCompletionResult);
+            virtual HRESULT ScheduledPlaybackHasStopped (void);
 
         protected:
             int ProcessVideo(picture_t *, block_t *);
@@ -59,9 +65,23 @@ namespace sdi_sout
                 BMDTimeValue hardware_reference;
             } clock;
             bool b_running;
-            int Start(vlc_tick_t);
+            bool b_prerolled;
+            vlc_tick_t streamStartTime;
+            int StartPlayback();
+            struct
+            {
+                vlc_mutex_t lock; /* Driver calls callback... until buffer is empty :/ */
+                vlc_cond_t cond;
+                vlc_thread_t thread;
+            } feeder;
+            static void *feederThreadCallback(void *);
+            void feederThread();
+            int doSchedule();
             int doProcessVideo(picture_t *, block_t *);
+            int FeedOneFrame();
+            int FeedAudio(vlc_tick_t, vlc_tick_t, bool);
             void checkClockDrift();
+            bool isDrained();
     };
 }
 
diff --git a/modules/stream_out/sdi/SDIAudioMultiplex.cpp b/modules/stream_out/sdi/SDIAudioMultiplex.cpp
index a156cf735f..a4ce26944a 100644
--- a/modules/stream_out/sdi/SDIAudioMultiplex.cpp
+++ b/modules/stream_out/sdi/SDIAudioMultiplex.cpp
@@ -32,7 +32,7 @@ using namespace sdi_sout;
 SDIAudioMultiplexBuffer::SDIAudioMultiplexBuffer(vlc_object_t *obj)
     : AES3AudioBuffer(obj, 2), AbstractStreamOutputBuffer()
 {
-
+    b_draining = false;
 }
 
 SDIAudioMultiplexBuffer::~SDIAudioMultiplexBuffer()
@@ -55,6 +55,16 @@ void * SDIAudioMultiplexBuffer::Dequeue()
     return NULL;
 }
 
+void SDIAudioMultiplexBuffer::Drain()
+{
+    b_draining = true;
+}
+
+bool SDIAudioMultiplexBuffer::isEOS()
+{
+    return b_draining && AES3AudioBuffer::bufferStart() == VLC_TICK_INVALID;
+}
+
 static void ConfigureChannels(unsigned i, es_format_t *fmt)
 {
     if( i>=8 )
diff --git a/modules/stream_out/sdi/SDIAudioMultiplex.hpp b/modules/stream_out/sdi/SDIAudioMultiplex.hpp
index ea9f2d26d8..10b7d8c76a 100644
--- a/modules/stream_out/sdi/SDIAudioMultiplex.hpp
+++ b/modules/stream_out/sdi/SDIAudioMultiplex.hpp
@@ -39,6 +39,10 @@ namespace sdi_sout
             virtual void FlushQueued(); /* impl */
             virtual void Enqueue(void *); /* impl */
             virtual void * Dequeue(); /* impl */
+            virtual void Drain(); /* impl */
+            virtual bool isEOS(); /* impl */
+        private:
+            bool b_draining;
     };
 
     class SDIAudioMultiplexConfig
diff --git a/modules/stream_out/sdi/SDIOutput.cpp b/modules/stream_out/sdi/SDIOutput.cpp
index 0d584ebffa..84fbe8cc89 100644
--- a/modules/stream_out/sdi/SDIOutput.cpp
+++ b/modules/stream_out/sdi/SDIOutput.cpp
@@ -74,6 +74,8 @@ SDIOutput::~SDIOutput()
         audioStreams.pop_front();
     }
     delete audioMultiplex;
+    delete captionsStream;
+    delete videoStream;
     if(video.pic_nosignal)
         picture_Release(video.pic_nosignal);
     es_format_Clean(&video.configuredfmt);
@@ -153,15 +155,6 @@ void SDIOutput::Del(AbstractStream *s)
 {
     s->Drain();
     Process();
-    if(videoStream == s)
-        videoStream = NULL;
-    else if(dynamic_cast<AudioDecodedStream *>(s))
-    {
-        audioStreams.remove(static_cast<AudioDecodedStream *>(s));
-    }
-    else if(captionsStream == s)
-        captionsStream = NULL;
-    delete s;
 }
 
 int SDIOutput::Control(int i_query, va_list args)
@@ -176,6 +169,23 @@ int SDIOutput::Control(int i_query, va_list args)
     };
 }
 
+bool SDIOutput::ReachedPlaybackTime(vlc_tick_t t)
+{
+    if (captionsStream &&
+        !captionsStream->ReachedPlaybackTime(t))
+        return false;
+
+    for(auto it = audioStreams.begin(); it != audioStreams.end(); ++it)
+        if(!(*it)->ReachedPlaybackTime(t))
+            return false;
+
+    if(!videoStream || /* must have video */
+       !videoStream->ReachedPlaybackTime(t))
+        return false;
+
+    return true;
+}
+
 AbstractStream *SDIOutput::createStream(const StreamID &id,
                                         const es_format_t *fmt,
                                         AbstractStreamOutputBuffer *buffer,
diff --git a/modules/stream_out/sdi/SDIOutput.hpp b/modules/stream_out/sdi/SDIOutput.hpp
index c21f5b09e9..5b4be98129 100644
--- a/modules/stream_out/sdi/SDIOutput.hpp
+++ b/modules/stream_out/sdi/SDIOutput.hpp
@@ -47,6 +47,7 @@ namespace sdi_sout
                                                   bool = true);
             virtual int ConfigureVideo(const video_format_t *) = 0;
             virtual int ConfigureAudio(const audio_format_t *) = 0;
+            virtual bool ReachedPlaybackTime(vlc_tick_t);
             sout_stream_t *p_stream;
             VideoDecodedStream *videoStream;
             std::list<AbstractStream *> audioStreams;
diff --git a/modules/stream_out/sdi/SDIStream.cpp b/modules/stream_out/sdi/SDIStream.cpp
index 993ca3c5ce..4845466a1a 100644
--- a/modules/stream_out/sdi/SDIStream.cpp
+++ b/modules/stream_out/sdi/SDIStream.cpp
@@ -40,26 +40,51 @@ AbstractStreamOutputBuffer::~AbstractStreamOutputBuffer()
 {
 }
 
+AbstractQueueStreamOutputBuffer::AbstractQueueStreamOutputBuffer()
+{
+    b_draining = false;
+}
+
+AbstractQueueStreamOutputBuffer::~AbstractQueueStreamOutputBuffer()
+{
+
+}
+
 void AbstractQueueStreamOutputBuffer::Enqueue(void *p)
 {
-    queue_mutex.lock();
+    buffer_mutex.lock();
     queued.push(p);
-    queue_mutex.unlock();
+    buffer_mutex.unlock();
 }
 
 void *AbstractQueueStreamOutputBuffer::Dequeue()
 {
     void *p = NULL;
-    queue_mutex.lock();
+    buffer_mutex.lock();
     if(!queued.empty())
     {
         p = queued.front();
         queued.pop();
     }
-    queue_mutex.unlock();
+    buffer_mutex.unlock();
     return p;
 }
 
+void AbstractQueueStreamOutputBuffer::Drain()
+{
+    buffer_mutex.lock();
+    b_draining = true;
+    buffer_mutex.unlock();
+}
+
+bool AbstractQueueStreamOutputBuffer::isEOS()
+{
+    buffer_mutex.lock();
+    bool b = b_draining && queued.empty();
+    buffer_mutex.unlock();
+    return b;
+}
+
 BlockStreamOutputBuffer::BlockStreamOutputBuffer()
     : AbstractQueueStreamOutputBuffer()
 {
@@ -82,12 +107,27 @@ void BlockStreamOutputBuffer::FlushQueued()
 PictureStreamOutputBuffer::PictureStreamOutputBuffer()
     : AbstractQueueStreamOutputBuffer()
 {
-
+    vlc_sem_init(&pool_semaphore, 16);
 }
 
 PictureStreamOutputBuffer::~PictureStreamOutputBuffer()
 {
+    vlc_sem_destroy(&pool_semaphore);
+}
 
+void * PictureStreamOutputBuffer::Dequeue()
+{
+    void *p = AbstractQueueStreamOutputBuffer::Dequeue();
+    if(p)
+        vlc_sem_post(&pool_semaphore);
+    return p;
+}
+
+void PictureStreamOutputBuffer::Enqueue(void *p)
+{
+    if(p)
+        vlc_sem_wait(&pool_semaphore);
+    AbstractQueueStreamOutputBuffer::Enqueue(p);
 }
 
 void PictureStreamOutputBuffer::FlushQueued()
@@ -97,6 +137,18 @@ void PictureStreamOutputBuffer::FlushQueued()
         picture_Release(p);
 }
 
+vlc_tick_t PictureStreamOutputBuffer::NextPictureTime()
+{
+    vlc_tick_t t;
+    buffer_mutex.lock();
+    if(!queued.empty())
+        t = reinterpret_cast<picture_t *>(queued.front())->date;
+    else
+        t = VLC_TICK_INVALID;
+    buffer_mutex.unlock();
+    return t;
+}
+
 unsigned StreamID::i_next_sequence_id = 0;
 
 StreamID::StreamID(int i_stream_id)
@@ -172,20 +224,39 @@ AbstractDecodedStream::AbstractDecodedStream(vlc_object_t *p_obj,
 {
     p_decoder = NULL;
     es_format_Init(&requestedoutput, 0, 0);
+    vlc_mutex_init(&inputLock);
+    vlc_cond_init(&inputWait);
+    threadEnd = false;
+    status = DECODING;
+    pcr = VLC_TICK_INVALID;
 }
 
 AbstractDecodedStream::~AbstractDecodedStream()
 {
+    Flush();
+    deinit();
     es_format_Clean(&requestedoutput);
+    vlc_cond_destroy(&inputWait);
+    vlc_mutex_destroy(&inputLock);
+}
 
-    if(!p_decoder)
-        return;
-
-    struct decoder_owner *p_owner;
-    p_owner = container_of(p_decoder, struct decoder_owner, dec);
-    es_format_Clean(&p_owner->decoder_out);
-    es_format_Clean(&p_owner->last_fmt_update);
-    decoder_Destroy( p_decoder );
+void AbstractDecodedStream::deinit()
+{
+    if(p_decoder)
+    {
+        Flush();
+        vlc_mutex_lock(&inputLock);
+        vlc_cond_signal(&inputWait);
+        threadEnd = true;
+        vlc_mutex_unlock(&inputLock);
+        vlc_join(thread, NULL);
+        struct decoder_owner *p_owner;
+        p_owner = container_of(p_decoder, struct decoder_owner, dec);
+        es_format_Clean(&p_owner->decoder_out);
+        es_format_Clean(&p_owner->last_fmt_update);
+        decoder_Destroy(p_decoder);
+        p_decoder = NULL;
+    }
 }
 
 bool AbstractDecodedStream::init(const es_format_t *p_fmt)
@@ -226,46 +297,134 @@ bool AbstractDecodedStream::init(const es_format_t *p_fmt)
         return false;
     }
 
+    if(vlc_clone(&thread, decoderThreadCallback, this, VLC_THREAD_PRIORITY_VIDEO))
+    {
+        es_format_Clean(&p_owner->decoder_out);
+        es_format_Clean(&p_owner->last_fmt_update);
+        decoder_Destroy( p_decoder );
+        p_decoder = NULL;
+        return false;
+    }
+
     return true;
 }
 
-int AbstractDecodedStream::Send(block_t *p_block)
+void * AbstractDecodedStream::decoderThreadCallback(void *me)
 {
-    assert(p_decoder);
+    reinterpret_cast<AbstractDecodedStream *>(me)->decoderThread();
+    return NULL;
+}
 
+void AbstractDecodedStream::decoderThread()
+{
     struct decoder_owner *p_owner =
             container_of(p_decoder, struct decoder_owner, dec);
 
-     if(!p_owner->b_error)
+    vlc_savecancel();
+    vlc_mutex_lock(&inputLock);
+    for(;;)
     {
-        int ret = p_decoder->pf_decode(p_decoder, p_block);
-        switch(ret)
+        while(inputQueue.empty() && !threadEnd)
+            vlc_cond_wait(&inputWait, &inputLock);
+        if(threadEnd)
         {
-            case VLCDEC_SUCCESS:
-                break;
-            case VLCDEC_ECRITICAL:
-                p_owner->b_error = true;
-                break;
-            case VLCDEC_RELOAD:
-                p_owner->b_error = true;
-                if(p_block)
-                    block_Release(p_block);
-                break;
-            default:
-                vlc_assert_unreachable();
+            vlc_mutex_unlock(&inputLock);
+            break;
+        }
+
+        block_t *p_block = inputQueue.front();
+        inputQueue.pop();
+
+        bool b_draincall = (status == DRAINING) && (p_block == NULL);
+        vlc_mutex_unlock(&inputLock);
+
+        if(!p_owner->b_error)
+        {
+            int ret = p_decoder->pf_decode(p_decoder, p_block);
+            switch(ret)
+            {
+                case VLCDEC_SUCCESS:
+                    break;
+                case VLCDEC_ECRITICAL:
+                    p_owner->b_error = true;
+                    break;
+                case VLCDEC_RELOAD:
+                    p_owner->b_error = true;
+                    if(p_block)
+                        block_Release(p_block);
+                    break;
+                default:
+                    vlc_assert_unreachable();
+            }
+        }
+
+        vlc_mutex_lock(&inputLock);
+        if(p_owner->b_error)
+        {
+            status = FAILED;
+            outputbuffer->Drain();
+        }
+        else if(b_draincall)
+        {
+            status = DRAINED;
+            outputbuffer->Drain();
         }
     }
+}
 
-    return p_owner->b_error ? VLC_EGENERIC : VLC_SUCCESS;
+int AbstractDecodedStream::Send(block_t *p_block)
+{
+    assert(p_decoder);
+    vlc_mutex_lock(&inputLock);
+    inputQueue.push(p_block);
+    if(p_block)
+    {
+        vlc_tick_t t = std::min(p_block->i_dts, p_block->i_pts);
+        if(t == VLC_TICK_INVALID)
+            t = std::max(p_block->i_dts, p_block->i_pts);
+        pcr = std::max(pcr, t);
+    }
+    vlc_cond_signal(&inputWait);
+    vlc_mutex_unlock(&inputLock);
+    return VLC_SUCCESS;
 }
 
 void AbstractDecodedStream::Flush()
 {
+    vlc_mutex_lock(&inputLock);
+    while(!inputQueue.empty())
+    {
+        if(inputQueue.front())
+            block_Release(inputQueue.front());
+        inputQueue.pop();
+    }
+    vlc_mutex_unlock(&inputLock);
 }
 
 void AbstractDecodedStream::Drain()
 {
     Send(NULL);
+    vlc_mutex_lock(&inputLock);
+    if(status != FAILED && status != DRAINED)
+        status = DRAINING;
+    vlc_mutex_unlock(&inputLock);
+}
+
+bool AbstractDecodedStream::isEOS()
+{
+    vlc_mutex_lock(&inputLock);
+    bool b = (status == FAILED || status == DRAINED);
+    vlc_mutex_unlock(&inputLock);
+    return b;
+}
+
+bool AbstractDecodedStream::ReachedPlaybackTime(vlc_tick_t t)
+{
+    vlc_mutex_lock(&inputLock);
+    bool b = (pcr != VLC_TICK_INVALID) && t < pcr;
+    b |= (status == DRAINED) || (status == FAILED);
+    vlc_mutex_unlock(&inputLock);
+    return b;
 }
 
 void AbstractDecodedStream::setOutputFormat(const es_format_t *p_fmt)
@@ -285,6 +444,7 @@ VideoDecodedStream::VideoDecodedStream(vlc_object_t *p_obj,
 
 VideoDecodedStream::~VideoDecodedStream()
 {
+    deinit();
     if(p_filters_chain)
         filter_chain_Delete(p_filters_chain);
 }
@@ -334,7 +494,6 @@ int VideoDecodedStream::VideoDecCallback_update_format(decoder_t *p_dec)
     return VLC_SUCCESS;
 }
 
-
 static picture_t *transcode_video_filter_buffer_new(filter_t *p_filter)
 {
     p_filter->fmt_out.video.i_chroma = p_filter->fmt_out.i_codec;
@@ -403,7 +562,6 @@ void VideoDecodedStream::Output(picture_t *p_pic)
 
     if(p_filters_chain)
         p_pic = filter_chain_VideoFilter(p_filters_chain, p_pic);
-
     if(p_pic)
         outputbuffer->Enqueue(p_pic);
 }
@@ -423,6 +581,7 @@ AudioDecodedStream::AudioDecodedStream(vlc_object_t *p_obj,
 
 AudioDecodedStream::~AudioDecodedStream()
 {
+    deinit();
     if(p_filters)
         aout_FiltersDelete(p_stream, p_filters);
 }
@@ -513,7 +672,8 @@ AbstractRawStream::AbstractRawStream(vlc_object_t *p_obj, const StreamID &id,
                                AbstractStreamOutputBuffer *buffer)
     : AbstractStream(p_obj, id, buffer)
 {
-
+    pcr = VLC_TICK_INVALID;
+    b_draining = false;
 }
 
 AbstractRawStream::~AbstractRawStream()
@@ -523,10 +683,16 @@ AbstractRawStream::~AbstractRawStream()
 
 int AbstractRawStream::Send(block_t *p_block)
 {
+    vlc_tick_t t = std::min(p_block->i_dts, p_block->i_pts);
+    if(t == VLC_TICK_INVALID)
+        t = std::max(p_block->i_dts, p_block->i_pts);
     if(p_block->i_buffer)
         outputbuffer->Enqueue(p_block);
     else
         block_Release(p_block);
+    buffer_mutex.lock();
+    pcr = std::max(pcr, t);
+    buffer_mutex.unlock();
     return VLC_SUCCESS;
 }
 
@@ -537,7 +703,26 @@ void AbstractRawStream::Flush()
 
 void AbstractRawStream::Drain()
 {
+    buffer_mutex.lock();
+    b_draining = true;
+    buffer_mutex.unlock();
+}
 
+bool AbstractRawStream::ReachedPlaybackTime(vlc_tick_t t)
+{
+    buffer_mutex.lock();
+    bool b = (pcr != VLC_TICK_INVALID) && t < pcr;
+    b |= b_draining;
+    buffer_mutex.unlock();
+    return b;
+}
+
+bool AbstractRawStream::isEOS()
+{
+    buffer_mutex.lock();
+    bool b = b_draining;
+    buffer_mutex.unlock();
+    return b;
 }
 
 void AbstractRawStream::FlushQueued()
diff --git a/modules/stream_out/sdi/SDIStream.hpp b/modules/stream_out/sdi/SDIStream.hpp
index 669de6dab1..2e714646c6 100644
--- a/modules/stream_out/sdi/SDIStream.hpp
+++ b/modules/stream_out/sdi/SDIStream.hpp
@@ -39,16 +39,22 @@ namespace sdi_sout
             virtual void FlushQueued() = 0;
             virtual void Enqueue(void *) = 0;
             virtual void * Dequeue() = 0;
+            virtual void Drain() = 0;
     };
 
     class AbstractQueueStreamOutputBuffer : public AbstractStreamOutputBuffer
     {
         public:
+            AbstractQueueStreamOutputBuffer();
+            ~AbstractQueueStreamOutputBuffer();
             virtual void Enqueue(void *);
             virtual void * Dequeue();
+            virtual void Drain();
+            virtual bool isEOS();
 
-        private:
-            std::mutex queue_mutex;
+        protected:
+            bool b_draining;
+            std::mutex buffer_mutex;
             std::queue<void *> queued;
     };
 
@@ -66,6 +72,12 @@ namespace sdi_sout
             PictureStreamOutputBuffer();
             virtual ~PictureStreamOutputBuffer();
             virtual void FlushQueued();
+            vlc_tick_t NextPictureTime();
+            virtual void Enqueue(void *); /* reimpl */
+            virtual void * Dequeue(); /* reimpl */
+
+        private:
+            vlc_sem_t pool_semaphore;
     };
 
     class StreamID
@@ -93,6 +105,8 @@ namespace sdi_sout
             virtual int Send(block_t*) = 0;
             virtual void Drain() = 0;
             virtual void Flush() = 0;
+            virtual bool ReachedPlaybackTime(vlc_tick_t) = 0;
+            virtual bool isEOS() = 0;
             const StreamID & getID() const;
 
         protected:
@@ -113,12 +127,30 @@ namespace sdi_sout
             virtual int Send(block_t*);
             virtual void Flush();
             virtual void Drain();
+            virtual bool ReachedPlaybackTime(vlc_tick_t); /* impl */
+            virtual bool isEOS(); /* impl */
             void setOutputFormat(const es_format_t *);
 
         protected:
             decoder_t *p_decoder;
             virtual void setCallbacks() = 0;
+            static void *decoderThreadCallback(void *);
+            void decoderThread();
+            void deinit();
             es_format_t requestedoutput;
+            std::queue<block_t *> inputQueue;
+            vlc_mutex_t inputLock;
+            vlc_cond_t inputWait;
+            vlc_thread_t thread;
+            bool threadEnd;
+            enum
+            {
+                DECODING,
+                DRAINING,
+                DRAINED,
+                FAILED,
+            } status;
+            vlc_tick_t pcr;
     };
 
     class VideoDecodedStream : public AbstractDecodedStream
@@ -167,8 +199,13 @@ namespace sdi_sout
             virtual int Send(block_t*); /* impl */
             virtual void Flush(); /* impl */
             virtual void Drain(); /* impl */
+            virtual bool ReachedPlaybackTime(vlc_tick_t); /* impl */
+            virtual bool isEOS(); /* impl */
 
         protected:
+            std::mutex buffer_mutex;
+            vlc_tick_t pcr;
+            bool b_draining;
             void FlushQueued();
     };
 



More information about the vlc-commits mailing list