mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-14 05:12:09 +00:00
decklink2output: Duplicate more frames on underrun
... and log actual render time with scheduled time
This commit is contained in:
parent
4c727b6860
commit
0c8257140a
1 changed files with 447 additions and 307 deletions
|
@ -39,6 +39,321 @@ GST_DEBUG_CATEGORY_STATIC (gst_decklink2_output_debug);
|
|||
|
||||
class IGstDeckLinkVideoOutputCallback;
|
||||
|
||||
class IGstDeckLinkTimecode:public IDeckLinkTimecode
|
||||
{
|
||||
public:
|
||||
IGstDeckLinkTimecode (GstVideoTimeCode * timecode):ref_count_ (1)
|
||||
{
|
||||
g_assert (timecode);
|
||||
|
||||
timecode_ = gst_video_time_code_copy (timecode);
|
||||
}
|
||||
|
||||
/* IUnknown */
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, void **object)
|
||||
{
|
||||
if (riid == IID_IDeckLinkTimecode) {
|
||||
*object = static_cast < IDeckLinkTimecode * >(this);
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_add (1);
|
||||
|
||||
return cnt + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_sub (1);
|
||||
if (cnt == 1)
|
||||
delete this;
|
||||
|
||||
return cnt - 1;
|
||||
}
|
||||
|
||||
/* IDeckLinkTimecode */
|
||||
BMDTimecodeBCD STDMETHODCALLTYPE GetBCD (void)
|
||||
{
|
||||
BMDTimecodeBCD bcd = 0;
|
||||
|
||||
bcd |= (timecode_->frames % 10) << 0;
|
||||
bcd |= ((timecode_->frames / 10) & 0x0f) << 4;
|
||||
bcd |= (timecode_->seconds % 10) << 8;
|
||||
bcd |= ((timecode_->seconds / 10) & 0x0f) << 12;
|
||||
bcd |= (timecode_->minutes % 10) << 16;
|
||||
bcd |= ((timecode_->minutes / 10) & 0x0f) << 20;
|
||||
bcd |= (timecode_->hours % 10) << 24;
|
||||
bcd |= ((timecode_->hours / 10) & 0x0f) << 28;
|
||||
|
||||
if (timecode_->config.fps_n == 24 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x0 << 30;
|
||||
else if (timecode_->config.fps_n == 25 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x1 << 30;
|
||||
else if (timecode_->config.fps_n == 30 && timecode_->config.fps_d == 1001)
|
||||
bcd |= 0x2 << 30;
|
||||
else if (timecode_->config.fps_n == 30 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x3 << 30;
|
||||
|
||||
return bcd;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetComponents (unsigned char *hours, unsigned char *minutes,
|
||||
unsigned char *seconds, unsigned char *frames)
|
||||
{
|
||||
*hours = timecode_->hours;
|
||||
*minutes = timecode_->minutes;
|
||||
*seconds = timecode_->seconds;
|
||||
*frames = timecode_->frames;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetString (dlstring_t * timecode)
|
||||
{
|
||||
gchar *s = gst_video_time_code_to_string (timecode_);
|
||||
*timecode = StdToDlString (s);
|
||||
g_free (s);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
BMDTimecodeFlags STDMETHODCALLTYPE GetFlags (void)
|
||||
{
|
||||
BMDTimecodeFlags flags = (BMDTimecodeFlags) 0;
|
||||
|
||||
if ((timecode_->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) != 0)
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeIsDropFrame);
|
||||
else
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeFlagDefault);
|
||||
|
||||
if (timecode_->field_count == 2)
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeFieldMark);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetTimecodeUserBits (BMDTimecodeUserBits * userBits)
|
||||
{
|
||||
*userBits = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ IGstDeckLinkTimecode () {
|
||||
gst_video_time_code_free (timecode_);
|
||||
}
|
||||
|
||||
private:
|
||||
GstVideoTimeCode * timecode_;
|
||||
std::atomic < ULONG > ref_count_;
|
||||
};
|
||||
|
||||
class IGstDeckLinkVideoFrame:public IDeckLinkVideoFrame
|
||||
{
|
||||
public:
|
||||
IGstDeckLinkVideoFrame (GstVideoFrame * frame):ref_count_ (1)
|
||||
{
|
||||
frame_ = *frame;
|
||||
}
|
||||
|
||||
/* IUnknown */
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, void **object)
|
||||
{
|
||||
if (riid == IID_IDeckLinkVideoOutputCallback) {
|
||||
*object = static_cast < IDeckLinkVideoFrame * >(this);
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_add (1);
|
||||
|
||||
return cnt + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_sub (1);
|
||||
if (cnt == 1)
|
||||
delete this;
|
||||
|
||||
return cnt - 1;
|
||||
}
|
||||
|
||||
/* IDeckLinkVideoFrame */
|
||||
long STDMETHODCALLTYPE GetWidth (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_WIDTH (&frame_);
|
||||
}
|
||||
|
||||
long STDMETHODCALLTYPE GetHeight (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_HEIGHT (&frame_);
|
||||
}
|
||||
|
||||
long STDMETHODCALLTYPE GetRowBytes (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_PLANE_STRIDE (&frame_, 0);
|
||||
}
|
||||
|
||||
BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat (void)
|
||||
{
|
||||
BMDPixelFormat format = bmdFormatUnspecified;
|
||||
switch (GST_VIDEO_FRAME_FORMAT (&frame_)) {
|
||||
case GST_VIDEO_FORMAT_UYVY:
|
||||
format = bmdFormat8BitYUV;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_v210:
|
||||
format = bmdFormat10BitYUV;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_ARGB:
|
||||
format = bmdFormat8BitARGB;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_BGRA:
|
||||
format = bmdFormat8BitBGRA;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
BMDFrameFlags STDMETHODCALLTYPE GetFlags (void)
|
||||
{
|
||||
return bmdFrameFlagDefault;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBytes (void **buffer)
|
||||
{
|
||||
*buffer = GST_VIDEO_FRAME_PLANE_DATA (&frame_, 0);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetTimecode (BMDTimecodeFormat format, IDeckLinkTimecode ** timecode)
|
||||
{
|
||||
if (timecode_) {
|
||||
*timecode = timecode_;
|
||||
timecode_->AddRef ();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetAncillaryData (IDeckLinkVideoFrameAncillary ** ancillary)
|
||||
{
|
||||
if (ancillary_) {
|
||||
*ancillary = ancillary_;
|
||||
ancillary_->AddRef ();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Non-interface methods */
|
||||
HRESULT STDMETHODCALLTYPE SetTimecode (GstVideoTimeCode * timecode)
|
||||
{
|
||||
GST_DECKLINK2_CLEAR_COM (timecode_);
|
||||
|
||||
if (timecode)
|
||||
timecode_ = new IGstDeckLinkTimecode (timecode);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
SetAncillaryData (IDeckLinkVideoFrameAncillary * ancillary)
|
||||
{
|
||||
GST_DECKLINK2_CLEAR_COM (ancillary_);
|
||||
|
||||
ancillary_ = ancillary;
|
||||
if (ancillary_)
|
||||
ancillary_->AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IGstDeckLinkVideoFrame *Clone (void)
|
||||
{
|
||||
IGstDeckLinkVideoFrame *cloned;
|
||||
g_assert (frame_.buffer);
|
||||
GstVideoFrame frame;
|
||||
|
||||
if (!gst_video_frame_map (&frame,
|
||||
&frame_.info, frame_.buffer, GST_MAP_READ)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cloned = new IGstDeckLinkVideoFrame (&frame_);
|
||||
if (ancillary_)
|
||||
cloned->SetAncillaryData (ancillary_);
|
||||
if (timecode_) {
|
||||
timecode_->AddRef ();
|
||||
cloned->timecode_ = timecode_;
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
void SetScheduledPts (GstClockTime pts)
|
||||
{
|
||||
pts_ = pts;
|
||||
}
|
||||
|
||||
GstClockTime GetScheduledPts (void)
|
||||
{
|
||||
return pts_;
|
||||
}
|
||||
|
||||
void SetScheduledHwTime (GstClockTime hw_time)
|
||||
{
|
||||
hw_time_ = hw_time;
|
||||
}
|
||||
|
||||
GstClockTime GetScheduledHwTime (void)
|
||||
{
|
||||
return hw_time_;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ IGstDeckLinkVideoFrame () {
|
||||
gst_video_frame_unmap (&frame_);
|
||||
GST_DECKLINK2_CLEAR_COM (timecode_);
|
||||
GST_DECKLINK2_CLEAR_COM (ancillary_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic < ULONG > ref_count_;
|
||||
GstVideoFrame frame_;
|
||||
IDeckLinkTimecode *timecode_ = NULL;
|
||||
IDeckLinkVideoFrameAncillary *ancillary_ = NULL;
|
||||
GstClockTime pts_ = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime hw_time_ = GST_CLOCK_TIME_NONE;
|
||||
};
|
||||
|
||||
class GstDecklink2OutputAudioBuffer
|
||||
{
|
||||
public:
|
||||
|
@ -112,13 +427,14 @@ public:
|
|||
guint64 num_samples = next_sample_offset - dup_drop_sample_offset_end_;
|
||||
samples_to_drop_ += num_samples;
|
||||
dup_drop_sample_offset_end_ = next_sample_offset;
|
||||
size_t cur_size = buffer_.size ();
|
||||
guint64 samples_in_queue = cur_size / info_.bpf;
|
||||
|
||||
GST_WARNING_ID (debug_name_.c_str (), "Samples to drop %" G_GUINT64_FORMAT
|
||||
", total samples to drop %" G_GUINT64_FORMAT, num_samples,
|
||||
samples_to_drop_);
|
||||
", total samples to drop %" G_GUINT64_FORMAT ", samples in queue %"
|
||||
G_GUINT64_FORMAT, num_samples, samples_to_drop_, samples_in_queue);
|
||||
|
||||
size_t bytes_to_drop = samples_to_drop_ * info_.bpf;
|
||||
size_t cur_size = buffer_.size ();
|
||||
if (cur_size >= bytes_to_drop) {
|
||||
buffer_.resize (cur_size - bytes_to_drop);
|
||||
samples_to_drop_ = 0;
|
||||
|
@ -187,7 +503,7 @@ private:
|
|||
struct GstDeckLink2OutputPrivate
|
||||
{
|
||||
std::mutex extern_lock;
|
||||
std::mutex schedule_lock;
|
||||
std::recursive_mutex schedule_lock;
|
||||
std::condition_variable cond;
|
||||
GstDecklink2OutputAudioBuffer audio_buf;
|
||||
};
|
||||
|
@ -212,7 +528,7 @@ struct _GstDeckLink2Output
|
|||
IDeckLinkOutput_v10_11 *output_10_11;
|
||||
|
||||
IGstDeckLinkVideoOutputCallback *callback;
|
||||
IDeckLinkVideoFrame *last_frame;
|
||||
IGstDeckLinkVideoFrame *last_frame;
|
||||
|
||||
GstCaps *caps;
|
||||
GArray *format_table;
|
||||
|
@ -230,8 +546,10 @@ struct _GstDeckLink2Output
|
|||
guint n_preroll_frames;
|
||||
guint min_buffered;
|
||||
guint max_buffered;
|
||||
guint gap_frames;
|
||||
GstClockTime pts;
|
||||
BMDTimeValue hw_time;
|
||||
gboolean duplicating;
|
||||
|
||||
gboolean configured;
|
||||
gboolean prerolled;
|
||||
|
@ -245,6 +563,9 @@ static void gst_decklink2_output_finalize (GObject * object);
|
|||
static void gst_decklink2_output_on_stopped (GstDeckLink2Output * self);
|
||||
static void gst_decklink2_output_on_completed (GstDeckLink2Output * self,
|
||||
BMDOutputFrameCompletionResult result);
|
||||
static HRESULT
|
||||
gst_decklink2_output_get_completion_timestamp (GstDeckLink2Output * self,
|
||||
IDeckLinkVideoFrame * frame, BMDTimeScale scale, BMDTimeValue * timestamp);
|
||||
|
||||
#define gst_decklink2_output_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstDeckLink2Output, gst_decklink2_output, GST_TYPE_OBJECT);
|
||||
|
@ -293,15 +614,33 @@ public:
|
|||
ScheduledFrameCompleted (IDeckLinkVideoFrame * frame,
|
||||
BMDOutputFrameCompletionResult result)
|
||||
{
|
||||
IGstDeckLinkVideoFrame *gst_frame = (IGstDeckLinkVideoFrame *) frame;
|
||||
BMDTimeValue timestamp = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime pts, hw_pts;
|
||||
|
||||
pts = gst_frame->GetScheduledPts ();
|
||||
hw_pts = gst_frame->GetScheduledHwTime ();
|
||||
gst_decklink2_output_get_completion_timestamp (output_,
|
||||
frame, GST_SECOND, ×tamp);
|
||||
|
||||
switch (result) {
|
||||
case bmdOutputFrameCompleted:
|
||||
GST_LOG_OBJECT (output_, "Completed frame %p", frame);
|
||||
GST_LOG_OBJECT (output_, "Frame %p completed timestamp %"
|
||||
GST_TIME_FORMAT ", scheduled %" GST_TIME_FORMAT
|
||||
" (gst pts %" GST_TIME_FORMAT ")", frame,
|
||||
GST_TIME_ARGS (timestamp), GST_TIME_ARGS (hw_pts),
|
||||
GST_TIME_ARGS (pts));
|
||||
break;
|
||||
case bmdOutputFrameDisplayedLate:
|
||||
GST_WARNING_OBJECT (output_, "Late Frame %p", frame);
|
||||
GST_LOG_OBJECT (output_, "Frame %p late, completed timestamp %"
|
||||
GST_TIME_FORMAT ", scheduled %" GST_TIME_FORMAT
|
||||
" (gst pts %" GST_TIME_FORMAT ")", frame,
|
||||
GST_TIME_ARGS (timestamp), GST_TIME_ARGS (hw_pts),
|
||||
GST_TIME_ARGS (pts));
|
||||
break;
|
||||
case bmdOutputFrameDropped:
|
||||
GST_WARNING_OBJECT (output_, "Dropped Frame %p", frame);
|
||||
GST_WARNING_OBJECT (output_, "Frame %p dropped, scheduled %"
|
||||
GST_TIME_FORMAT, frame, GST_TIME_ARGS (pts));
|
||||
break;
|
||||
case bmdOutputFrameFlushed:
|
||||
GST_LOG_OBJECT (output_, "Flushed Frame %p", frame);
|
||||
|
@ -936,6 +1275,34 @@ gst_decklink2_output_get_reference_clock (GstDeckLink2Output * self,
|
|||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
gst_decklink2_output_get_completion_timestamp (GstDeckLink2Output * self,
|
||||
IDeckLinkVideoFrame * frame, BMDTimeScale scale, BMDTimeValue * timestamp)
|
||||
{
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
switch (self->api_level) {
|
||||
case GST_DECKLINK2_API_LEVEL_10_11:
|
||||
hr = self->output_10_11->GetFrameCompletionReferenceTimestamp (frame,
|
||||
scale, timestamp);
|
||||
break;
|
||||
case GST_DECKLINK2_API_LEVEL_11_4:
|
||||
hr = self->output_11_4->GetFrameCompletionReferenceTimestamp (frame,
|
||||
scale, timestamp);
|
||||
break;
|
||||
case GST_DECKLINK2_API_LEVEL_11_5_1:
|
||||
case GST_DECKLINK2_API_LEVEL_LATEST:
|
||||
hr = self->output->GetFrameCompletionReferenceTimestamp (frame,
|
||||
scale, timestamp);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
gst_decklink2_output_start (GstDeckLink2Output * self,
|
||||
BMDTimeValue start_time, BMDTimeScale scale, double speed)
|
||||
|
@ -966,7 +1333,8 @@ static HRESULT
|
|||
gst_decklink2_get_current_level (GstDeckLink2Output * self,
|
||||
guint * buffered_video, GstClockTime * video_running_time,
|
||||
guint * buffered_audio, GstClockTime * audio_running_time,
|
||||
GstClockTimeDiff * av_diff, GstClockTime * hw_time)
|
||||
GstClockTimeDiff * av_diff, GstClockTime * hw_time,
|
||||
GstClockTime * hw_now_gst)
|
||||
{
|
||||
BMDTimeValue hw_now, dummy, dummy2;
|
||||
HRESULT hr = S_OK;
|
||||
|
@ -1003,12 +1371,15 @@ gst_decklink2_get_current_level (GstDeckLink2Output * self,
|
|||
*hw_time = hw_now - self->hw_time;
|
||||
}
|
||||
|
||||
if (hw_now_gst)
|
||||
*hw_now_gst = (GstClockTime) hw_now;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT
|
||||
gst_decklink2_output_schedule_video_internal (GstDeckLink2Output * self,
|
||||
IDeckLinkVideoFrame * frame)
|
||||
IGstDeckLinkVideoFrame * frame)
|
||||
{
|
||||
GstDeckLink2OutputPrivate *priv = self->priv;
|
||||
GstClockTime next_pts, dur;
|
||||
|
@ -1021,12 +1392,29 @@ gst_decklink2_output_schedule_video_internal (GstDeckLink2Output * self,
|
|||
GstClockTime video_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime audio_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime hw_time_gst = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime hw_now_gst = GST_CLOCK_TIME_NONE;
|
||||
GstClockTimeDiff diff = GST_CLOCK_STIME_NONE;
|
||||
|
||||
hr = gst_decklink2_get_current_level (self, &buffered_video,
|
||||
&video_running_time, &buffered_audio, &audio_running_time, &diff,
|
||||
&hw_time_gst, &hw_now_gst);
|
||||
if (gst_decklink2_result (hr)) {
|
||||
GST_LOG_OBJECT (self, "Before schedule, video %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u) audio %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u), av-diff: %" GST_STIME_FORMAT
|
||||
", hw-time %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (self->pts), self->n_frames, buffered_video,
|
||||
GST_TIME_ARGS (audio_running_time), self->n_samples, buffered_audio,
|
||||
GST_STIME_ARGS (diff), GST_TIME_ARGS (hw_time_gst));
|
||||
}
|
||||
|
||||
frame->AddRef ();
|
||||
GST_DECKLINK2_CLEAR_COM (self->last_frame);
|
||||
self->last_frame = frame;
|
||||
|
||||
frame->SetScheduledPts (self->pts);
|
||||
frame->SetScheduledHwTime (hw_now_gst);
|
||||
|
||||
self->n_frames++;
|
||||
next_pts = gst_util_uint64_scale (self->n_frames,
|
||||
self->selected_mode.fps_d * GST_SECOND, self->selected_mode.fps_n);
|
||||
|
@ -1132,19 +1520,6 @@ gst_decklink2_output_schedule_video_internal (GstDeckLink2Output * self,
|
|||
priv->audio_buf.Flush ();
|
||||
}
|
||||
|
||||
hr = gst_decklink2_get_current_level (self, &buffered_video,
|
||||
&video_running_time, &buffered_audio, &audio_running_time, &diff,
|
||||
&hw_time_gst);
|
||||
if (gst_decklink2_result (hr)) {
|
||||
GST_LOG_OBJECT (self, "After schedule, video %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u) audio %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u), av-diff: %" GST_STIME_FORMAT
|
||||
", hw-time %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (self->pts), self->n_frames, buffered_video,
|
||||
GST_TIME_ARGS (audio_running_time), self->n_samples, buffered_audio,
|
||||
GST_STIME_ARGS (diff), GST_TIME_ARGS (hw_time_gst));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
@ -1172,18 +1547,32 @@ gst_decklink2_output_schedule_stream (GstDeckLink2Output * output,
|
|||
}
|
||||
|
||||
if (active) {
|
||||
guint32 count = 0;
|
||||
hr = gst_decklink2_output_get_num_buffered (output, &count);
|
||||
std::lock_guard < std::recursive_mutex > slk (priv->schedule_lock);
|
||||
guint buffered_video = 0;
|
||||
guint buffered_audio = 0;
|
||||
GstClockTime video_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime audio_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime hw_time_gst = GST_CLOCK_TIME_NONE;
|
||||
GstClockTimeDiff diff = GST_CLOCK_STIME_NONE;
|
||||
|
||||
hr = gst_decklink2_get_current_level (output, &buffered_video,
|
||||
&video_running_time, &buffered_audio, &audio_running_time, &diff,
|
||||
&hw_time_gst, NULL);
|
||||
|
||||
if (!gst_decklink2_result (hr)) {
|
||||
GST_ERROR_OBJECT (output,
|
||||
"Couldn't query bufferred frame count, hr: 0x%x", (guint) hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (count > output->max_buffered) {
|
||||
GST_WARNING_OBJECT (output, "Skipping frame, buffered count %u > %u",
|
||||
count, output->max_buffered);
|
||||
std::lock_guard < std::mutex > slk (priv->schedule_lock);
|
||||
if (buffered_video > output->max_buffered) {
|
||||
GST_WARNING_OBJECT (output, "Skipping frame, video %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u) audio %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u), av-diff: %" GST_STIME_FORMAT
|
||||
", hw-time %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (output->pts), output->n_frames, buffered_video,
|
||||
GST_TIME_ARGS (audio_running_time), output->n_samples, buffered_audio,
|
||||
GST_STIME_ARGS (diff), GST_TIME_ARGS (hw_time_gst));
|
||||
|
||||
/* audio and video may not be completely aligned. Add this sample
|
||||
* and drop video frame duration amount of audio samples */
|
||||
|
@ -1195,10 +1584,11 @@ gst_decklink2_output_schedule_stream (GstDeckLink2Output * output,
|
|||
}
|
||||
lk.unlock ();
|
||||
|
||||
std::lock_guard < std::mutex > slk (priv->schedule_lock);
|
||||
std::lock_guard < std::recursive_mutex > slk (priv->schedule_lock);
|
||||
priv->audio_buf.Append (audio_buf, audio_buf_size);
|
||||
|
||||
hr = gst_decklink2_output_schedule_video_internal (output, frame);
|
||||
hr = gst_decklink2_output_schedule_video_internal (output,
|
||||
(IGstDeckLinkVideoFrame *) frame);
|
||||
*drop_count = output->drop_count;
|
||||
*late_count = output->late_count;
|
||||
*underrun_count = output->underrun_count;
|
||||
|
@ -1214,7 +1604,7 @@ gst_decklink2_output_stop_internal (GstDeckLink2Output * self)
|
|||
|
||||
GST_DEBUG_OBJECT (self, "Stopping");
|
||||
|
||||
std::unique_lock < std::mutex > lk (priv->schedule_lock);
|
||||
std::unique_lock < std::recursive_mutex > lk (priv->schedule_lock);
|
||||
/* Steal last frame to avoid re-rendering */
|
||||
GST_DECKLINK2_CLEAR_COM (self->last_frame);
|
||||
lk.unlock ();
|
||||
|
@ -1258,277 +1648,6 @@ gst_decklink2_output_stop (GstDeckLink2Output * output)
|
|||
return gst_decklink2_output_stop_internal (output);
|
||||
}
|
||||
|
||||
class IGstDeckLinkTimecode:public IDeckLinkTimecode
|
||||
{
|
||||
public:
|
||||
IGstDeckLinkTimecode (GstVideoTimeCode * timecode):ref_count_ (1)
|
||||
{
|
||||
g_assert (timecode);
|
||||
|
||||
timecode_ = gst_video_time_code_copy (timecode);
|
||||
}
|
||||
|
||||
/* IUnknown */
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, void **object)
|
||||
{
|
||||
if (riid == IID_IDeckLinkTimecode) {
|
||||
*object = static_cast < IDeckLinkTimecode * >(this);
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_add (1);
|
||||
|
||||
return cnt + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_sub (1);
|
||||
if (cnt == 1)
|
||||
delete this;
|
||||
|
||||
return cnt - 1;
|
||||
}
|
||||
|
||||
/* IDeckLinkTimecode */
|
||||
BMDTimecodeBCD STDMETHODCALLTYPE GetBCD (void)
|
||||
{
|
||||
BMDTimecodeBCD bcd = 0;
|
||||
|
||||
bcd |= (timecode_->frames % 10) << 0;
|
||||
bcd |= ((timecode_->frames / 10) & 0x0f) << 4;
|
||||
bcd |= (timecode_->seconds % 10) << 8;
|
||||
bcd |= ((timecode_->seconds / 10) & 0x0f) << 12;
|
||||
bcd |= (timecode_->minutes % 10) << 16;
|
||||
bcd |= ((timecode_->minutes / 10) & 0x0f) << 20;
|
||||
bcd |= (timecode_->hours % 10) << 24;
|
||||
bcd |= ((timecode_->hours / 10) & 0x0f) << 28;
|
||||
|
||||
if (timecode_->config.fps_n == 24 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x0 << 30;
|
||||
else if (timecode_->config.fps_n == 25 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x1 << 30;
|
||||
else if (timecode_->config.fps_n == 30 && timecode_->config.fps_d == 1001)
|
||||
bcd |= 0x2 << 30;
|
||||
else if (timecode_->config.fps_n == 30 && timecode_->config.fps_d == 1)
|
||||
bcd |= 0x3 << 30;
|
||||
|
||||
return bcd;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetComponents (unsigned char *hours, unsigned char *minutes,
|
||||
unsigned char *seconds, unsigned char *frames)
|
||||
{
|
||||
*hours = timecode_->hours;
|
||||
*minutes = timecode_->minutes;
|
||||
*seconds = timecode_->seconds;
|
||||
*frames = timecode_->frames;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetString (dlstring_t * timecode)
|
||||
{
|
||||
gchar *s = gst_video_time_code_to_string (timecode_);
|
||||
*timecode = StdToDlString (s);
|
||||
g_free (s);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
BMDTimecodeFlags STDMETHODCALLTYPE GetFlags (void)
|
||||
{
|
||||
BMDTimecodeFlags flags = (BMDTimecodeFlags) 0;
|
||||
|
||||
if ((timecode_->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) != 0)
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeIsDropFrame);
|
||||
else
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeFlagDefault);
|
||||
|
||||
if (timecode_->field_count == 2)
|
||||
flags = (BMDTimecodeFlags) (flags | bmdTimecodeFieldMark);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetTimecodeUserBits (BMDTimecodeUserBits * userBits)
|
||||
{
|
||||
*userBits = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ IGstDeckLinkTimecode () {
|
||||
gst_video_time_code_free (timecode_);
|
||||
}
|
||||
|
||||
private:
|
||||
GstVideoTimeCode * timecode_;
|
||||
std::atomic < ULONG > ref_count_;
|
||||
};
|
||||
|
||||
class IGstDeckLinkVideoFrame:public IDeckLinkVideoFrame
|
||||
{
|
||||
public:
|
||||
IGstDeckLinkVideoFrame (GstVideoFrame * frame):ref_count_ (1)
|
||||
{
|
||||
frame_ = *frame;
|
||||
}
|
||||
|
||||
/* IUnknown */
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, void **object)
|
||||
{
|
||||
if (riid == IID_IDeckLinkVideoOutputCallback) {
|
||||
*object = static_cast < IDeckLinkVideoFrame * >(this);
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_add (1);
|
||||
|
||||
return cnt + 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release (void)
|
||||
{
|
||||
ULONG cnt = ref_count_.fetch_sub (1);
|
||||
if (cnt == 1)
|
||||
delete this;
|
||||
|
||||
return cnt - 1;
|
||||
}
|
||||
|
||||
/* IDeckLinkVideoFrame */
|
||||
long STDMETHODCALLTYPE GetWidth (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_WIDTH (&frame_);
|
||||
}
|
||||
|
||||
long STDMETHODCALLTYPE GetHeight (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_HEIGHT (&frame_);
|
||||
}
|
||||
|
||||
long STDMETHODCALLTYPE GetRowBytes (void)
|
||||
{
|
||||
return GST_VIDEO_FRAME_PLANE_STRIDE (&frame_, 0);
|
||||
}
|
||||
|
||||
BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat (void)
|
||||
{
|
||||
BMDPixelFormat format = bmdFormatUnspecified;
|
||||
switch (GST_VIDEO_FRAME_FORMAT (&frame_)) {
|
||||
case GST_VIDEO_FORMAT_UYVY:
|
||||
format = bmdFormat8BitYUV;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_v210:
|
||||
format = bmdFormat10BitYUV;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_ARGB:
|
||||
format = bmdFormat8BitARGB;
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_BGRA:
|
||||
format = bmdFormat8BitBGRA;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
BMDFrameFlags STDMETHODCALLTYPE GetFlags (void)
|
||||
{
|
||||
return bmdFrameFlagDefault;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBytes (void **buffer)
|
||||
{
|
||||
*buffer = GST_VIDEO_FRAME_PLANE_DATA (&frame_, 0);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetTimecode (BMDTimecodeFormat format, IDeckLinkTimecode ** timecode)
|
||||
{
|
||||
if (timecode_) {
|
||||
*timecode = timecode_;
|
||||
timecode_->AddRef ();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
GetAncillaryData (IDeckLinkVideoFrameAncillary ** ancillary)
|
||||
{
|
||||
if (ancillary_) {
|
||||
*ancillary = ancillary_;
|
||||
ancillary_->AddRef ();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Non-interface methods */
|
||||
HRESULT STDMETHODCALLTYPE SetTimecode (GstVideoTimeCode * timecode)
|
||||
{
|
||||
GST_DECKLINK2_CLEAR_COM (timecode_);
|
||||
|
||||
if (timecode)
|
||||
timecode_ = new IGstDeckLinkTimecode (timecode);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
SetAncillaryData (IDeckLinkVideoFrameAncillary * ancillary)
|
||||
{
|
||||
GST_DECKLINK2_CLEAR_COM (ancillary_);
|
||||
|
||||
ancillary_ = ancillary;
|
||||
if (ancillary_)
|
||||
ancillary_->AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ IGstDeckLinkVideoFrame () {
|
||||
gst_video_frame_unmap (&frame_);
|
||||
GST_DECKLINK2_CLEAR_COM (timecode_);
|
||||
GST_DECKLINK2_CLEAR_COM (ancillary_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic < ULONG > ref_count_;
|
||||
GstVideoFrame frame_;
|
||||
IDeckLinkTimecode *timecode_ = NULL;
|
||||
IDeckLinkVideoFrameAncillary *ancillary_ = NULL;
|
||||
};
|
||||
|
||||
/* Copied from ext/closedcaption/gstccconverter.c */
|
||||
/* Converts raw CEA708 cc_data and an optional timecode into CDP */
|
||||
static guint
|
||||
|
@ -2090,6 +2209,12 @@ gst_decklink2_output_configure (GstDeckLink2Output * output,
|
|||
output->drop_count = 0;
|
||||
output->late_count = 0;
|
||||
output->underrun_count = 0;
|
||||
output->gap_frames = 1;
|
||||
if (max_buffered > min_buffered) {
|
||||
guint gap = (max_buffered - min_buffered) / 2;
|
||||
output->gap_frames = MAX (2, gap);
|
||||
}
|
||||
output->duplicating = FALSE;
|
||||
|
||||
return S_OK;
|
||||
|
||||
|
@ -2113,7 +2238,7 @@ gst_decklink2_output_on_completed (GstDeckLink2Output * self,
|
|||
GstDeckLink2OutputPrivate *priv = self->priv;
|
||||
dlbool_t active;
|
||||
|
||||
std::lock_guard < std::mutex > lk (priv->schedule_lock);
|
||||
std::lock_guard < std::recursive_mutex > lk (priv->schedule_lock);
|
||||
if (result == bmdOutputFrameDisplayedLate)
|
||||
self->late_count++;
|
||||
else if (result == bmdOutputFrameDropped)
|
||||
|
@ -2129,11 +2254,12 @@ gst_decklink2_output_on_completed (GstDeckLink2Output * self,
|
|||
GstClockTime video_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime audio_running_time = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime hw_time_gst = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime dummy;
|
||||
GstClockTimeDiff diff = GST_CLOCK_STIME_NONE;
|
||||
|
||||
hr = gst_decklink2_get_current_level (self, &buffered_video,
|
||||
&video_running_time, &buffered_audio, &audio_running_time, &diff,
|
||||
&hw_time_gst);
|
||||
&hw_time_gst, &dummy);
|
||||
if (gst_decklink2_result (hr) && buffered_video <= self->min_buffered) {
|
||||
GST_WARNING_OBJECT (self, "Underrun, video %" GST_TIME_FORMAT
|
||||
" (%" G_GUINT64_FORMAT ", buffered %u) audio %" GST_TIME_FORMAT
|
||||
|
@ -2143,8 +2269,22 @@ gst_decklink2_output_on_completed (GstDeckLink2Output * self,
|
|||
GST_TIME_ARGS (audio_running_time), self->n_samples, buffered_audio,
|
||||
GST_STIME_ARGS (diff), GST_TIME_ARGS (hw_time_gst));
|
||||
self->underrun_count++;
|
||||
priv->audio_buf.PrependSilence ();
|
||||
gst_decklink2_output_schedule_video_internal (self, self->last_frame);
|
||||
if (self->duplicating)
|
||||
return;
|
||||
|
||||
self->duplicating = TRUE;
|
||||
for (guint i = 0; i < self->gap_frames; i++) {
|
||||
IGstDeckLinkVideoFrame *copy = self->last_frame->Clone ();
|
||||
if (!copy) {
|
||||
GST_ERROR_OBJECT (self, "Couldn't clone last frame");
|
||||
copy = self->last_frame;
|
||||
self->last_frame->AddRef ();
|
||||
}
|
||||
priv->audio_buf.PrependSilence ();
|
||||
gst_decklink2_output_schedule_video_internal (self, copy);
|
||||
copy->Release ();
|
||||
}
|
||||
self->duplicating = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue