diff --git a/gstajacommon.cpp b/gstajacommon.cpp index dbb7da8c8d..ce343cb63c 100644 --- a/gstajacommon.cpp +++ b/gstajacommon.cpp @@ -684,6 +684,134 @@ ShmMutexLocker::~ShmMutexLocker() { if (s != SEM_FAILED) sem_post(s); } +static guint gst_aja_device_get_frame_multiplier(GstAjaNtv2Device *device, + NTV2Channel channel) { + // quad formats use 4x as many frames, quad-quad formats 8x + bool quad_enabled = false; + device->device->GetQuadFrameEnable(quad_enabled, channel); + bool quad_quad_enabled = false; + device->device->GetQuadQuadFrameEnable(quad_quad_enabled, channel); + + NTV2VideoFormat format = NTV2_FORMAT_UNKNOWN; + device->device->GetVideoFormat(format, channel); + + GST_TRACE("Channel %d uses mode %d (quad: %d, quad quad: %d)", (gint)channel, + (gint)format, quad_enabled, quad_quad_enabled); + + // Similarly, 2k/UHD use 4x as many frames and 4k/UHD2 use 8x as many + // frames + if (format != NTV2_FORMAT_UNKNOWN) { + guint width = ::GetDisplayWidth(format); + guint height = ::GetDisplayHeight(format); + + if (height <= 1080 && width <= 1920) { + // SD and HD but not 2k! + } else if (height <= 2160 && width <= 3840) { + // 2k and UHD but not 4k + quad_enabled = true; + } else if (height <= 4320 && width <= 7680) { + // 4k and UHD2 but not 8k + quad_quad_enabled = true; + } else { + // 8k FIXME + quad_quad_enabled = true; + } + } + + if (quad_enabled) { + g_assert(!quad_quad_enabled); + + return 4; + } else if (quad_quad_enabled) { + g_assert(!quad_enabled); + + return 8; + } + + return 1; +} + +// Returns -1 on failure or otherwise the start_frame. +// end_frame would be start_frame + frame_count - 1 +gint gst_aja_ntv2_device_find_unallocated_frames(GstAjaNtv2Device *device, + NTV2Channel channel, + guint frame_count) { + g_assert(frame_count != 0); + g_assert(device != NULL); + g_assert(device->device->IsOpen()); + + // Adapted from CNTV2Card::FindUnallocatedFrames() with + // quad/quad-quad/UHD/UHD2 support + std::set used_frames; + + for (NTV2Channel c = ::NTV2_CHANNEL1; c < NTV2_MAX_NUM_CHANNELS; + c = (NTV2Channel)(c + 1)) { + AUTOCIRCULATE_STATUS ac_status; + + if (device->device->AutoCirculateGetStatus(c, ac_status) && + !ac_status.IsStopped()) { + guint16 start_frame = ac_status.GetStartFrame(); + guint16 end_frame = ac_status.GetEndFrame(); + + guint multiplier = gst_aja_device_get_frame_multiplier(device, c); + + GST_TRACE("Channel %d uses frames %u-%u (multiplier: %u)", c, start_frame, + end_frame, multiplier); + + start_frame *= multiplier; + end_frame *= multiplier; + end_frame += (multiplier - 1); + + GST_TRACE("Channel %d uses HD frames %u-%u", c, start_frame, end_frame); + for (guint16 i = start_frame; i <= end_frame; i++) { + used_frames.insert(i); + } + } + } + + guint multiplier = gst_aja_device_get_frame_multiplier(device, channel); + frame_count *= multiplier; + + const guint16 last_frame = + ::NTV2DeviceGetNumberFrameBuffers(device->device->GetDeviceID()) - 1; + guint16 start_frame = 0; + guint16 end_frame = start_frame + frame_count - 1; + + auto iter = used_frames.cbegin(); + while (iter != used_frames.cend()) { + guint16 allocated_start_frame = *iter; + guint16 allocated_end_frame = allocated_start_frame; + + // Find end of the allocation + while (++iter != used_frames.cend() && *iter == (allocated_end_frame + 1)) + allocated_end_frame++; + + // Free block before this allocation + if (start_frame < allocated_start_frame && + end_frame < allocated_start_frame) + break; + + // Move after this allocation and check if there is enough space before + // the next allocation + start_frame = GST_ROUND_UP_N(allocated_end_frame + 1, multiplier); + end_frame = start_frame + frame_count - 1; + } + + // If above we moved after the end of the available frames error out + if (start_frame > last_frame || end_frame > last_frame) { + GST_WARNING("Did not find a contiguous unused range of %u frames", + frame_count); + return -1; + } + + // Otherwise we have enough space after the last allocation + GST_INFO("Using HD frames %u-%u", start_frame, end_frame); + GST_INFO("Using frames %u-%u", start_frame / multiplier, + start_frame / multiplier + frame_count / multiplier - 1); + + return start_frame / multiplier; +} + GType gst_aja_audio_system_get_type(void) { static gsize id = 0; static const GEnumValue modes[] = { diff --git a/gstajacommon.h b/gstajacommon.h index a976ded5a5..619035b8e0 100644 --- a/gstajacommon.h +++ b/gstajacommon.h @@ -65,6 +65,11 @@ GstAjaNtv2Device *gst_aja_ntv2_device_ref(GstAjaNtv2Device *device); G_GNUC_INTERNAL void gst_aja_ntv2_device_unref(GstAjaNtv2Device *device); +G_GNUC_INTERNAL +gint gst_aja_ntv2_device_find_unallocated_frames(GstAjaNtv2Device *device, + NTV2Channel channel, + guint frame_count); + #define GST_AJA_ALLOCATOR_MEMTYPE "aja" #define GST_TYPE_AJA_ALLOCATOR (gst_aja_allocator_get_type()) diff --git a/gstajasink.cpp b/gstajasink.cpp index 500b1b97dc..b08624ec38 100644 --- a/gstajasink.cpp +++ b/gstajasink.cpp @@ -1737,13 +1737,22 @@ restart: guint16 start_frame = self->start_frame; guint16 end_frame = self->end_frame; - // If nothing was configured, work with a number of frames that is half - // the queue size and assume that all other channels work the same. + // If both are the same, try to find queue_size/2 unallocated frames and + // use those. if (start_frame == end_frame) { guint16 num_frames = self->queue_size / 2; - start_frame = self->channel * num_frames; - end_frame = (self->channel + 1) * num_frames - 1; + gint assigned_start_frame = gst_aja_ntv2_device_find_unallocated_frames( + self->device, self->channel, num_frames); + + if (assigned_start_frame == -1) { + GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL), + ("Failed to allocate %u frames", num_frames)); + goto out; + } + + start_frame = assigned_start_frame; + end_frame = start_frame + num_frames - 1; } GST_DEBUG_OBJECT( diff --git a/gstajasrc.cpp b/gstajasrc.cpp index 8b41853bd7..9568ff192b 100644 --- a/gstajasrc.cpp +++ b/gstajasrc.cpp @@ -42,8 +42,8 @@ GST_DEBUG_CATEGORY_STATIC(gst_aja_src_debug); #define DEFAULT_TIMECODE_INDEX (GST_AJA_TIMECODE_INDEX_VITC) #define DEFAULT_REFERENCE_SOURCE (GST_AJA_REFERENCE_SOURCE_FREERUN) #define DEFAULT_QUEUE_SIZE (16) -#define DEFAULT_START_FRAME (0) -#define DEFAULT_END_FRAME (0) +#define DEFAULT_START_FRAME (8) +#define DEFAULT_END_FRAME (8) #define DEFAULT_CAPTURE_CPU_CORE (G_MAXUINT) enum { @@ -156,8 +156,8 @@ static void gst_aja_src_class_init(GstAjaSrcClass *klass) { gobject_class, PROP_START_FRAME, g_param_spec_uint( "start-frame", "Start Frame", - "Start frame buffer to be used for capturing (auto if same number as " - "end-frame).", + "Start frame buffer to be used for capturing (automatically assign " + "that many frames if same number as end-frame).", 0, G_MAXINT, DEFAULT_START_FRAME, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); @@ -165,8 +165,8 @@ static void gst_aja_src_class_init(GstAjaSrcClass *klass) { gobject_class, PROP_END_FRAME, g_param_spec_uint( "end-frame", "End Frame", - "End frame buffer to be used for capturing (auto if same number as " - "start-frame).", + "End frame buffer to be used for capturing (automatically assign " + "that many frames if same number as start-frame).", 0, G_MAXINT, DEFAULT_END_FRAME, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); @@ -1781,13 +1781,20 @@ restart: guint16 start_frame = self->start_frame; guint16 end_frame = self->end_frame; - // If nothing was configured, work with 8 frames and assume that all - // other channels work the same. + // If both are set to the same value, try to find that many unallocated + // frames and use those. if (start_frame == end_frame) { - const guint16 num_frames = 8; + gint assigned_start_frame = gst_aja_ntv2_device_find_unallocated_frames( + self->device, self->channel, self->start_frame); - start_frame = self->channel * num_frames; - end_frame = (self->channel + 1) * num_frames - 1; + if (assigned_start_frame == -1) { + GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL), + ("Failed to allocate %u frames", start_frame)); + goto out; + } + + start_frame = assigned_start_frame; + end_frame = start_frame + self->start_frame - 1; } GST_DEBUG_OBJECT(