/* GStreamer * * Copyright (C) 2009 Carl-Anton Ingmarsson . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gsth264dpb.h" #include "gstvdpvideomemory.h" /* Properties */ enum { PROP_0, PROP_NUM_REF_FRAMES, PROP_MAX_LONGTERM_IDX }; GST_DEBUG_CATEGORY_STATIC (h264dpb_debug); #define GST_CAT_DEFAULT h264dpb_debug #define DEBUG_INIT \ GST_DEBUG_CATEGORY_INIT (h264dpb_debug, "vdph264dpb", 0, \ "VDPAU H264 DPB"); G_DEFINE_TYPE_WITH_CODE (GstH264DPB, gst_h264_dpb, G_TYPE_OBJECT, DEBUG_INIT); void gst_h264_dpb_fill_reference_frames (GstH264DPB * dpb, VdpReferenceFrameH264 reference_frames[16]) { GstH264Frame **frames; guint i; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { GstH264Frame *frame = frames[i]; GstVdpVideoMemory *vmem = (GstVdpVideoMemory *) gst_buffer_get_memory (frame->frame-> output_buffer, 0); reference_frames[i].surface = vmem->surface; reference_frames[i].is_long_term = frame->is_long_term; reference_frames[i].top_is_reference = frame->is_reference; reference_frames[i].bottom_is_reference = frame->is_reference; reference_frames[i].field_order_cnt[0] = frame->poc; reference_frames[i].field_order_cnt[1] = frame->poc; reference_frames[i].frame_idx = frame->frame_idx; } for (i = dpb->n_frames; i < 16; i++) { reference_frames[i].surface = VDP_INVALID_HANDLE; reference_frames[i].is_long_term = FALSE; reference_frames[i].top_is_reference = VDP_FALSE; reference_frames[i].bottom_is_reference = VDP_FALSE; reference_frames[i].field_order_cnt[0] = 0; reference_frames[i].field_order_cnt[1] = 0; reference_frames[i].frame_idx = 0; } } static void gst_h264_dpb_remove (GstH264DPB * dpb, guint idx) { GstH264Frame **frames; guint i; frames = dpb->frames; gst_video_codec_frame_unref (frames[idx]->frame); dpb->n_frames--; for (i = idx; i < dpb->n_frames; i++) frames[i] = frames[i + 1]; } static GstFlowReturn gst_h264_dpb_output (GstH264DPB * dpb, guint idx) { GstFlowReturn ret; GstH264Frame *frame = dpb->frames[idx]; gst_video_codec_frame_ref (frame->frame); ret = dpb->output (dpb, frame, dpb->user_data); frame->output_needed = FALSE; if (!frame->is_reference) gst_h264_dpb_remove (dpb, idx); return ret; } static gboolean gst_h264_dpb_bump (GstH264DPB * dpb, guint poc, GstFlowReturn * ret) { GstH264Frame **frames; guint i; gint bump_idx; frames = dpb->frames; bump_idx = -1; for (i = 0; i < dpb->n_frames; i++) { if (frames[i]->output_needed) { bump_idx = i; break; } } if (bump_idx != -1) { for (i = bump_idx + 1; i < dpb->n_frames; i++) { if (frames[i]->output_needed && (frames[i]->poc < frames[bump_idx]->poc)) { bump_idx = i; } } if (frames[bump_idx]->poc < poc) { *ret = gst_h264_dpb_output (dpb, bump_idx); return TRUE; } } return FALSE; } GstFlowReturn gst_h264_dpb_add (GstH264DPB * dpb, GstH264Frame * h264_frame) { GstFlowReturn ret = GST_FLOW_OK; GST_DEBUG ("add frame with poc: %d", h264_frame->poc); if (h264_frame->is_reference && h264_frame->is_long_term && (h264_frame->frame_idx > dpb->max_longterm_frame_idx)) h264_frame->is_reference = FALSE; if (h264_frame->is_reference) { while (dpb->n_frames == dpb->max_frames) { if (!gst_h264_dpb_bump (dpb, G_MAXUINT, &ret)) goto no_room; } GST_DEBUG ("Storing frame in slot %d", dpb->n_frames); dpb->frames[dpb->n_frames++] = h264_frame; } else { while (gst_h264_dpb_bump (dpb, h264_frame->poc, &ret)) { if (ret != GST_FLOW_OK) return ret; } ret = dpb->output (dpb, h264_frame, dpb->user_data); } return ret; /* ERRORS */ no_room: { GST_ERROR_OBJECT (dpb, "Couldn't make room in DPB"); return GST_FLOW_OK; } } void gst_h264_dpb_flush (GstH264DPB * dpb, gboolean output) { GstFlowReturn ret; guint i; GST_DEBUG ("flush"); if (output) while (gst_h264_dpb_bump (dpb, G_MAXUINT, &ret)); for (i = 0; i < dpb->n_frames; i++) gst_video_codec_frame_unref (dpb->frames[i]->frame); dpb->n_frames = 0; } void gst_h264_dpb_mark_sliding (GstH264DPB * dpb) { GstH264Frame **frames; guint i; gint mark_idx = -1; if (dpb->n_frames != dpb->max_frames) return; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { if (frames[i]->is_reference && !frames[i]->is_long_term) { mark_idx = i; break; } } if (mark_idx != -1) { for (i = mark_idx; i < dpb->n_frames; i++) { if (frames[i]->is_reference && !frames[i]->is_long_term && frames[i]->frame_idx < frames[mark_idx]->frame_idx) mark_idx = i; } frames[mark_idx]->is_reference = FALSE; if (!frames[mark_idx]->output_needed) gst_h264_dpb_remove (dpb, mark_idx); } } void gst_h264_dpb_mark_long_term (GstH264DPB * dpb, guint16 pic_num, guint16 long_term_frame_idx) { GstH264Frame **frames; guint i; gint mark_idx = -1; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { if (frames[i]->is_reference && !frames[i]->is_long_term && frames[i]->frame_idx == pic_num) { mark_idx = i; break; } } if (mark_idx != -1) { frames[mark_idx]->is_long_term = TRUE; frames[mark_idx]->frame_idx = long_term_frame_idx; } } void gst_h264_dpb_mark_short_term_unused (GstH264DPB * dpb, guint16 pic_num) { GstH264Frame **frames; guint i; gint mark_idx = -1; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { if (frames[i]->is_reference && !frames[i]->is_long_term && frames[i]->frame_idx == pic_num) { mark_idx = i; break; } } if (mark_idx != -1) { frames[mark_idx]->is_reference = FALSE; if (!frames[mark_idx]->output_needed) gst_h264_dpb_remove (dpb, mark_idx); } } void gst_h264_dpb_mark_long_term_unused (GstH264DPB * dpb, guint16 long_term_pic_num) { GstH264Frame **frames; guint i; gint mark_idx = -1; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { if (frames[i]->is_reference && frames[i]->is_long_term && frames[i]->frame_idx == long_term_pic_num) { mark_idx = i; break; } } if (mark_idx != -1) { frames[mark_idx]->is_reference = FALSE; if (!frames[mark_idx]->output_needed) gst_h264_dpb_remove (dpb, mark_idx); } } void gst_h264_dpb_mark_all_unused (GstH264DPB * dpb) { GstH264Frame **frames; guint i; frames = dpb->frames; for (i = 0; i < dpb->n_frames; i++) { frames[i]->is_reference = FALSE; if (!frames[i]->output_needed) { gst_h264_dpb_remove (dpb, i); i--; } } } void gst_h264_dpb_set_output_func (GstH264DPB * dpb, GstH264DPBOutputFunc func, gpointer user_data) { g_return_if_fail (GST_IS_H264_DPB (dpb)); dpb->output = func; dpb->user_data = user_data; } /* GObject vmethod implementations */ static void gst_h264_dpb_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GstH264DPB *dpb = GST_H264_DPB (object); switch (property_id) { case PROP_NUM_REF_FRAMES: g_value_set_uint (value, dpb->max_frames); break; case PROP_MAX_LONGTERM_IDX: g_value_set_int (value, dpb->max_longterm_frame_idx); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gst_h264_dpb_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GstH264DPB *dpb = GST_H264_DPB (object); switch (property_id) { case PROP_NUM_REF_FRAMES: { GstFlowReturn ret; guint i; dpb->max_frames = g_value_get_uint (value); for (i = dpb->n_frames; i > dpb->max_frames; i--) gst_h264_dpb_bump (dpb, G_MAXUINT, &ret); break; } case PROP_MAX_LONGTERM_IDX: { GstH264Frame **frames; guint i; dpb->max_longterm_frame_idx = g_value_get_int (value); frames = dpb->frames; for (i = dpb->n_frames; i < dpb->n_frames; i++) { if (frames[i]->is_reference && frames[i]->is_long_term && frames[i]->frame_idx > dpb->max_longterm_frame_idx) { frames[i]->is_reference = FALSE; if (!frames[i]->output_needed) { gst_h264_dpb_remove (dpb, i); i--; } } } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gst_h264_dpb_finalize (GObject * object) { GstH264DPB *dpb = GST_H264_DPB (object); guint i; for (i = 0; i < dpb->n_frames; i++) gst_video_codec_frame_unref (dpb->frames[i]->frame); G_OBJECT_CLASS (gst_h264_dpb_parent_class)->finalize (object); } static void gst_h264_dpb_init (GstH264DPB * dpb) { dpb->n_frames = 0; dpb->max_longterm_frame_idx = -1; dpb->max_frames = MAX_DPB_SIZE; } static void gst_h264_dpb_class_init (GstH264DPBClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gst_h264_dpb_finalize; object_class->set_property = gst_h264_dpb_set_property; object_class->get_property = gst_h264_dpb_get_property; g_object_class_install_property (object_class, PROP_NUM_REF_FRAMES, g_param_spec_uint ("num-ref-frames", "Num Ref Frames", "How many reference frames the DPB should hold ", 0, 16, 16, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_MAX_LONGTERM_IDX, g_param_spec_int ("max-longterm-frame-idx", "MaxLongTermFrameIDX", "Maximum long-term frame index", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); }