diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.c b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.c
index 065ae668eb..fb19179b6a 100644
--- a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.c
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.c
@@ -473,7 +473,7 @@ gst_h264_decoder_clear_dpb (GstH264Decoder * self, gboolean flush)
   if (!flush) {
     while ((picture = gst_h264_dpb_bump (priv->dpb, TRUE)) != NULL) {
       GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
-          picture->system_frame_number);
+          GST_CODEC_PICTURE_FRAME_NUMBER (picture));
 
       if (frame)
         gst_video_decoder_release_frame (decoder, frame);
@@ -796,7 +796,7 @@ gst_h264_decoder_split_frame (GstH264Decoder * self, GstH264Picture * picture)
   other_field->frame_num = picture->frame_num;
   other_field->ref = picture->ref;
   other_field->nonexisting = picture->nonexisting;
-  other_field->system_frame_number = picture->system_frame_number;
+  GST_CODEC_PICTURE_COPY_FRAME_NUMBER (other_field, picture);
   other_field->field_pic_flag = picture->field_pic_flag;
 
   return other_field;
@@ -1034,11 +1034,12 @@ gst_h264_decoder_start_current_picture (GstH264Decoder * self)
   g_assert (priv->active_sps != NULL);
   g_assert (priv->active_pps != NULL);
 
+  current_picture = priv->current_picture;
+
   /* If subclass didn't update output state at this point,
    * marking this picture as a discont and stores current input state */
   if (priv->input_state_changed) {
-    priv->current_picture->discont_state =
-        gst_video_codec_state_ref (self->input_state);
+    gst_h264_picture_set_discont_state (current_picture, self->input_state);
     priv->input_state_changed = FALSE;
   }
 
@@ -1056,8 +1057,6 @@ gst_h264_decoder_start_current_picture (GstH264Decoder * self)
   if (!gst_h264_decoder_init_current_picture (self))
     return GST_FLOW_ERROR;
 
-  current_picture = priv->current_picture;
-
   /* If the new picture is an IDR, flush DPB */
   if (current_picture->idr) {
     if (!current_picture->dec_ref_pic_marking.no_output_of_prior_pics_flag) {
@@ -1294,7 +1293,8 @@ gst_h264_decoder_parse_slice (GstH264Decoder * self, GstH264NalUnit * nalu)
     }
 
     /* This allows accessing the frame from the picture. */
-    picture->system_frame_number = priv->current_frame->system_frame_number;
+    GST_CODEC_PICTURE_FRAME_NUMBER (picture) =
+        priv->current_frame->system_frame_number;
     priv->current_picture = picture;
 
     ret = gst_h264_decoder_start_current_picture (self);
@@ -1798,7 +1798,7 @@ gst_h264_decoder_do_output_picture (GstH264Decoder * self,
 #endif
 
   frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self),
-      picture->system_frame_number);
+      GST_CODEC_PICTURE_FRAME_NUMBER (picture));
 
   if (!frame) {
     /* The case where the end_picture() got failed and corresponding
@@ -1808,7 +1808,7 @@ gst_h264_decoder_do_output_picture (GstH264Decoder * self,
     } else {
       GST_ERROR_OBJECT (self,
           "No available codec frame with frame number %d",
-          picture->system_frame_number);
+          GST_CODEC_PICTURE_FRAME_NUMBER (picture));
       UPDATE_FLOW_RETURN (ret, GST_FLOW_ERROR);
     }
 
@@ -2109,10 +2109,10 @@ gst_h264_decoder_finish_picture (GstH264Decoder * self,
    * drop codec frame of the second field because we are consuming
    * only the first codec frame via GstH264Decoder::output_picture() method */
   if (picture->second_field && picture->other_field &&
-      picture->system_frame_number !=
-      picture->other_field->system_frame_number) {
+      GST_CODEC_PICTURE_FRAME_NUMBER (picture) !=
+      GST_CODEC_PICTURE_FRAME_NUMBER (picture->other_field)) {
     GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
-        picture->system_frame_number);
+        GST_CODEC_PICTURE_FRAME_NUMBER (picture));
 
     gst_video_decoder_release_frame (decoder, frame);
   }
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.h b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.h
index faf66de450..72e0c7886d 100644
--- a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.h
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264decoder.h
@@ -132,7 +132,7 @@ struct _GstH264DecoderClass
    *
    * Optional. Called whenever new #GstH264Picture is created.
    * Subclass can set implementation specific user data
-   * on the #GstH264Picture via gst_h264_picture_set_user_data()
+   * on the #GstH264Picture via gst_h264_picture_set_user_data
    */
   GstFlowReturn (*new_picture)      (GstH264Decoder * decoder,
                                      GstVideoCodecFrame * frame,
@@ -146,7 +146,7 @@ struct _GstH264DecoderClass
    *
    * Called when a new field picture is created for interlaced field picture.
    * Subclass can attach implementation specific user data on @second_field via
-   * gst_h264_picture_set_user_data()
+   * gst_h264_picture_set_user_data
    *
    * Since: 1.20
    */
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.c b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.c
index 0511302b3d..b49bfcd0d2 100644
--- a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.c
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.c
@@ -22,6 +22,7 @@
 #endif
 
 #include "gsth264picture-private.h"
+#include "gstcodecpicture-private.h"
 
 #include <stdlib.h>
 
@@ -30,18 +31,6 @@ GST_DEBUG_CATEGORY_EXTERN (gst_h264_decoder_debug);
 
 GST_DEFINE_MINI_OBJECT_TYPE (GstH264Picture, gst_h264_picture);
 
-static void
-_gst_h264_picture_free (GstH264Picture * picture)
-{
-  if (picture->notify)
-    picture->notify (picture->user_data);
-
-  if (picture->discont_state)
-    gst_video_codec_state_unref (picture->discont_state);
-
-  g_free (picture);
-}
-
 /**
  * gst_h264_picture_new:
  *
@@ -62,51 +51,11 @@ gst_h264_picture_new (void)
 
   gst_mini_object_init (GST_MINI_OBJECT_CAST (pic), 0,
       GST_TYPE_H264_PICTURE, NULL, NULL,
-      (GstMiniObjectFreeFunction) _gst_h264_picture_free);
+      (GstMiniObjectFreeFunction) gst_codec_picture_free);
 
   return pic;
 }
 
-/**
- * gst_h264_picture_set_user_data:
- * @picture: a #GstH264Picture
- * @user_data: (nullable): private data
- * @notify: (closure user_data): a #GDestroyNotify
- *
- * Sets @user_data on the picture and the #GDestroyNotify that will be called when
- * the picture is freed.
- *
- * If a @user_data was previously set, then the previous set @notify will be called
- * before the @user_data is replaced.
- */
-void
-gst_h264_picture_set_user_data (GstH264Picture * picture, gpointer user_data,
-    GDestroyNotify notify)
-{
-  g_return_if_fail (GST_IS_H264_PICTURE (picture));
-
-  if (picture->notify)
-    picture->notify (picture->user_data);
-
-  picture->user_data = user_data;
-  picture->notify = notify;
-}
-
-/**
- * gst_h264_picture_get_user_data:
- * @picture: a #GstH264Picture
- *
- * Gets private data set on the picture via
- * gst_h264_picture_set_user_data() previously.
- *
- * Returns: (transfer none) (nullable): The previously set user_data
- */
-gpointer
-gst_h264_picture_get_user_data (GstH264Picture * picture)
-{
-  return picture->user_data;
-}
-
 struct _GstH264Dpb
 {
   GArray *pic_list;
@@ -646,7 +595,7 @@ gst_h264_dpb_get_picture (GstH264Dpb * dpb, guint32 system_frame_number)
     GstH264Picture *picture =
         g_array_index (dpb->pic_list, GstH264Picture *, i);
 
-    if (picture->system_frame_number == system_frame_number) {
+    if (GST_CODEC_PICTURE_FRAME_NUMBER (picture) == system_frame_number) {
       gst_h264_picture_ref (picture);
       return picture;
     }
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.h b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.h
index 73547066d8..e458f01384 100644
--- a/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.h
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecs/gsth264picture.h
@@ -21,6 +21,7 @@
 #define __GST_H264_PICTURE_H__
 
 #include <gst/codecs/codecs-prelude.h>
+#include <gst/codecs/gstcodecpicture.h>
 #include <gst/codecparsers/gsth264parser.h>
 #include <gst/video/video.h>
 
@@ -114,13 +115,10 @@ typedef enum
 struct _GstH264Picture
 {
   /*< private >*/
-  GstMiniObject parent;
+  GstCodecPicture parent;
 
   GstH264SliceType type;
 
-  /* From GstVideoCodecFrame */
-  guint32 system_frame_number;
-
   guint8 pic_order_cnt_type;  /* SPS */
   gint32 top_field_order_cnt;
   gint32 bottom_field_order_cnt;
@@ -160,12 +158,6 @@ struct _GstH264Picture
   GstH264Picture * other_field;
 
   GstVideoBufferFlags buffer_flags;
-
-  /* decoder input state if this picture is discont point */
-  GstVideoCodecState *discont_state;
-
-  gpointer user_data;
-  GDestroyNotify notify;
 };
 
 /**
@@ -218,13 +210,27 @@ gst_clear_h264_picture (GstH264Picture ** picture)
   }
 }
 
-GST_CODECS_API
-void gst_h264_picture_set_user_data (GstH264Picture * picture,
-                                     gpointer user_data,
-                                     GDestroyNotify notify);
+static inline void
+gst_h264_picture_set_user_data (GstH264Picture * picture, gpointer user_data,
+    GDestroyNotify notify)
+{
+  gst_codec_picture_set_user_data (GST_CODEC_PICTURE (picture),
+      user_data, notify);
+}
 
-GST_CODECS_API
-gpointer gst_h264_picture_get_user_data (GstH264Picture * picture);
+static inline gpointer
+gst_h264_picture_get_user_data (GstH264Picture * picture)
+{
+  return gst_codec_picture_get_user_data (GST_CODEC_PICTURE (picture));
+}
+
+static inline void
+gst_h264_picture_set_discont_state (GstH264Picture * picture,
+    GstVideoCodecState * discont_state)
+{
+  gst_codec_picture_set_discont_state (GST_CODEC_PICTURE (picture),
+      discont_state);
+}
 
 /*******************
  * GstH264Dpb *
diff --git a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11h264dec.cpp b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11h264dec.cpp
index 43499ad4ae..f428fde1ad 100644
--- a/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11h264dec.cpp
+++ b/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11h264dec.cpp
@@ -885,8 +885,8 @@ gst_d3d11_h264_dec_output_picture (GstH264Decoder * decoder,
   }
 
   if (!gst_d3d11_decoder_process_output (inner->d3d11_decoder, vdec,
-          picture->discont_state, inner->width, inner->height, view_buffer,
-          &frame->output_buffer)) {
+          GST_CODEC_PICTURE (picture)->discont_state, inner->width,
+          inner->height, view_buffer, &frame->output_buffer)) {
     GST_ERROR_OBJECT (self, "Failed to copy buffer");
     goto error;
   }
diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264dec.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264dec.cpp
index 415fc8dd4c..2e06c76ba2 100644
--- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264dec.cpp
+++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264dec.cpp
@@ -750,7 +750,8 @@ gst_nv_h264_dec_output_picture (GstH264Decoder * decoder,
   }
 
   ret = gst_nv_decoder_finish_surface (self->decoder,
-      vdec, picture->discont_state, surface, &frame->output_buffer);
+      vdec, GST_CODEC_PICTURE (picture)->discont_state, surface,
+      &frame->output_buffer);
   if (ret != GST_FLOW_OK)
     goto error;
 
diff --git a/subprojects/gst-plugins-bad/sys/v4l2codecs/gstv4l2codech264dec.c b/subprojects/gst-plugins-bad/sys/v4l2codecs/gstv4l2codech264dec.c
index b604fc5903..6bde230a1a 100644
--- a/subprojects/gst-plugins-bad/sys/v4l2codecs/gstv4l2codech264dec.c
+++ b/subprojects/gst-plugins-bad/sys/v4l2codecs/gstv4l2codech264dec.c
@@ -628,7 +628,7 @@ gst_v4l2_codec_h264_dec_fill_decoder_params (GstV4l2CodecH264Dec * self,
        * The reference is multiplied by 1000 because it's was set as micro
        * seconds and this TS is nanosecond.
        */
-      .reference_ts = (guint64) ref_pic->system_frame_number * 1000,
+      .reference_ts = (guint64) GST_CODEC_PICTURE_FRAME_NUMBER (ref_pic) * 1000,
       .frame_num = frame_num,
       .pic_num = pic_num,
       .flags = V4L2_H264_DPB_ENTRY_FLAG_VALID
@@ -779,7 +779,7 @@ lookup_dpb_index (struct v4l2_h264_dpb_entry dpb[16], GstH264Picture * ref_pic)
   if (ref_pic->second_field && ref_pic->other_field)
     ref_pic = ref_pic->other_field;
 
-  ref_ts = (guint64) ref_pic->system_frame_number * 1000;
+  ref_ts = (guint64) GST_CODEC_PICTURE_FRAME_NUMBER (ref_pic) * 1000;
   for (i = 0; i < 16; i++) {
     if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE
         && dpb[i].reference_ts == ref_ts)
@@ -1056,21 +1056,23 @@ gst_v4l2_codec_h264_dec_output_picture (GstH264Decoder * decoder,
   GstV4l2CodecH264Dec *self = GST_V4L2_CODEC_H264_DEC (decoder);
   GstVideoDecoder *vdec = GST_VIDEO_DECODER (decoder);
   GstV4l2Request *request = gst_h264_picture_get_user_data (picture);
+  GstCodecPicture *codec_picture = GST_CODEC_PICTURE (picture);
   gint ret;
 
-  if (picture->discont_state) {
+  if (codec_picture->discont_state) {
     if (!gst_video_decoder_negotiate (vdec)) {
       GST_ERROR_OBJECT (vdec, "Could not re-negotiate with updated state");
       return FALSE;
     }
   }
 
-  GST_DEBUG_OBJECT (self, "Output picture %u", picture->system_frame_number);
+  GST_DEBUG_OBJECT (self, "Output picture %u",
+      codec_picture->system_frame_number);
 
   ret = gst_v4l2_request_set_done (request);
   if (ret == 0) {
     GST_ELEMENT_ERROR (self, STREAM, DECODE,
-        ("Decoding frame %u took too long", picture->system_frame_number),
+        ("Decoding frame %u took too long", codec_picture->system_frame_number),
         (NULL));
     goto error;
   } else if (ret < 0) {
@@ -1082,7 +1084,8 @@ gst_v4l2_codec_h264_dec_output_picture (GstH264Decoder * decoder,
 
   if (gst_v4l2_request_failed (request)) {
     GST_ELEMENT_ERROR (self, STREAM, DECODE,
-        ("Failed to decode frame %u", picture->system_frame_number), (NULL));
+        ("Failed to decode frame %u", codec_picture->system_frame_number),
+        (NULL));
     goto error;
   }
 
@@ -1175,16 +1178,17 @@ gst_v4l2_codec_h264_dec_submit_bitstream (GstV4l2CodecH264Dec * self,
         self->bitstream);
   } else {
     GstVideoCodecFrame *frame;
+    guint32 system_frame_number = GST_CODEC_PICTURE_FRAME_NUMBER (picture);
 
     frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self),
-        picture->system_frame_number);
+        system_frame_number);
     g_return_val_if_fail (frame, FALSE);
 
     if (!gst_v4l2_codec_h264_dec_ensure_output_buffer (self, frame))
       goto done;
 
     request = gst_v4l2_decoder_alloc_request (self->decoder,
-        picture->system_frame_number, self->bitstream, frame->output_buffer);
+        system_frame_number, self->bitstream, frame->output_buffer);
 
     gst_video_codec_frame_unref (frame);
   }
diff --git a/subprojects/gst-plugins-bad/sys/va/gstvah264dec.c b/subprojects/gst-plugins-bad/sys/va/gstvah264dec.c
index 44a1ba6d5d..c17dbfda83 100644
--- a/subprojects/gst-plugins-bad/sys/va/gstvah264dec.c
+++ b/subprojects/gst-plugins-bad/sys/va/gstvah264dec.c
@@ -123,8 +123,8 @@ gst_va_h264_dec_output_picture (GstH264Decoder * decoder,
   GST_LOG_OBJECT (self,
       "Outputting picture %p (poc %d)", picture, picture->pic_order_cnt);
 
-  ret = gst_va_base_dec_process_output (base, frame, picture->discont_state,
-      picture->buffer_flags);
+  ret = gst_va_base_dec_process_output (base, frame,
+      GST_CODEC_PICTURE (picture)->discont_state, picture->buffer_flags);
   gst_h264_picture_unref (picture);
 
   if (ret)