vlc | branch: master | Francois Cartegnie <[email protected]> | 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(); }; _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
