Takes a broadcast-format stream containing no IDR frames and using
open-GOP throughout and transforms it into a stored stream with IDR
frames and closed-GOP.
---
 doc/bitstream_filters.texi     |  16 ++
 libavcodec/Makefile            |   2 +
 libavcodec/bitstream_filters.c |   1 +
 libavcodec/h264_closegop_bsf.c | 525 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 544 insertions(+)
 create mode 100644 libavcodec/h264_closegop_bsf.c

diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index dcb8232e0..cc264102c 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -39,6 +39,22 @@ When this option is enabled, the long-term headers are 
removed from the
 bitstream after extraction.
 @end table
 
+@section h264_closegop
+
+Takes a broadcast-format stream containing no IDR frames and using
+open-GOP throughout and transforms it into a stored stream with IDR
+frames and closed-GOP.
+
+The transformation is lossless, but some frames may be dropped around
+the I frames converted to IDR frames (any B frames which follow the I
+frame in decoding order but precede it in display order).
+
+@table @option
+@item gop_size
+Set the new GOP size of the stream.  This must be set higher than the
+maximum distance between I frames; if set lower the filter will fail.
+@end table
+
 @section h264_metadata
 
 Modify metadata attached to the H.264 stream.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e7109041b..d2b7a2367 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -765,6 +765,8 @@ OBJS-$(CONFIG_CHOMP_BSF)                  += chomp_bsf.o
 OBJS-$(CONFIG_DUMP_EXTRADATA_BSF)         += dump_extradata_bsf.o
 OBJS-$(CONFIG_EXTRACT_EXTRADATA_BSF)      += extract_extradata_bsf.o    \
                                              h2645_parse.o
+OBJS-$(CONFIG_H264_CLOSEGOP_BSF)          += h264_closegop_bsf.o \
+                                             h264_raw.o
 OBJS-$(CONFIG_H264_METADATA_BSF)          += h264_metadata_bsf.o \
                                              h264_raw.o
 OBJS-$(CONFIG_H264_MP4TOANNEXB_BSF)       += h264_mp4toannexb_bsf.o
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index d24a2c675..bddf12560 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -28,6 +28,7 @@ extern const AVBitStreamFilter ff_aac_adtstoasc_bsf;
 extern const AVBitStreamFilter ff_chomp_bsf;
 extern const AVBitStreamFilter ff_dump_extradata_bsf;
 extern const AVBitStreamFilter ff_extract_extradata_bsf;
+extern const AVBitStreamFilter ff_h264_closegop_bsf;
 extern const AVBitStreamFilter ff_h264_metadata_bsf;
 extern const AVBitStreamFilter ff_h264_mp4toannexb_bsf;
 extern const AVBitStreamFilter ff_h264_redundant_pps_bsf;
diff --git a/libavcodec/h264_closegop_bsf.c b/libavcodec/h264_closegop_bsf.c
new file mode 100644
index 000000000..3a8e0cf12
--- /dev/null
+++ b/libavcodec/h264_closegop_bsf.c
@@ -0,0 +1,525 @@
+/*
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/common.h"
+#include "libavutil/fifo.h"
+#include "libavutil/opt.h"
+
+#include "bsf.h"
+#include "h264.h"
+#include "h264_raw.h"
+
+typedef struct H264CloseGOPPacket {
+    AVPacket *input_packet;
+    H264RawAccessUnit access_unit;
+} H264CloseGOPPacket;
+
+typedef struct H264CloseGOPContext {
+    H264RawContext input;
+    H264RawContext output;
+
+    int gop_size;
+
+    AVFifoBuffer *fifo;
+    int gop_counter;
+    int last_intra;
+
+    int starting;
+    int initial_drop;
+    int eof;
+    int drain_counter;
+    int idr;
+
+    int poc_delta;
+
+    int frame_num;
+    int idr_pic_id;
+    int poc;
+} H264CloseGOPContext;
+
+
+static int h264_closegop_make_output(AVBSFContext *bsf, AVPacket *out)
+{
+    H264CloseGOPContext *ctx = bsf->priv_data;
+    H264CloseGOPPacket *pkt;
+    H264RawAccessUnit *au;
+    const H264RawSPS *sps;
+    const H264RawPPS *pps;
+    int err, i, advance_frame_num, drop, key;
+
+again:
+    if (ctx->eof) {
+        if (av_fifo_size(ctx->fifo) == 0)
+            return AVERROR_EOF;
+    } else if (ctx->drain_counter == 0) {
+        if (ctx->eof)
+            return AVERROR_EOF;
+        else
+            return AVERROR(EAGAIN);
+    }
+
+    av_fifo_generic_read(ctx->fifo, &pkt, sizeof(pkt), NULL);
+    --ctx->drain_counter;
+
+    av_log(bsf, AV_LOG_DEBUG, "Output: pts %"PRId64" "
+           "(%d left to drain).\n", pkt->input_packet->pts,
+           ctx->drain_counter);
+
+    au = &pkt->access_unit;
+
+    drop = 0;
+    advance_frame_num = 0;
+
+    if (ctx->idr) {
+        int have_sps = 0, have_pps = 0, ps_pos = 0;
+        for (i = 0; i < au->nb_nal_units; i++) {
+            if (au->nal_units[i].type == H264_NAL_AUD)
+                ps_pos = i;
+            if (au->nal_units[i].type == H264_NAL_SPS)
+                have_sps = 1;
+            if (au->nal_units[i].type == H264_NAL_PPS)
+                have_pps = 1;
+        }
+        if (!have_sps || !have_pps) {
+            err = av_reallocp_array(&au->nal_units,
+                                    au->nb_nal_units + !have_sps + !have_pps,
+                                    sizeof(*au->nal_units));
+            if (err < 0)
+                return err;
+
+            memmove(au->nal_units + ps_pos + !have_sps + !have_pps,
+                    au->nal_units + ps_pos,
+                    (au->nb_nal_units - ps_pos) * sizeof(*au->nal_units));
+
+            if (!have_sps) {
+                au->nal_units[ps_pos].type = H264_NAL_SPS;
+                au->nal_units[ps_pos].sps =
+                    av_malloc(sizeof(*au->nal_units[ps_pos].sps));
+                if (!au->nal_units[ps_pos].sps)
+                    return AVERROR(ENOMEM);
+                memcpy(au->nal_units[ps_pos].sps,
+                       ctx->output.active_sps, 
sizeof(*ctx->output.active_sps));
+                ++au->nb_nal_units;
+                ++ps_pos;
+            }
+            if (!have_pps) {
+                au->nal_units[ps_pos].type = H264_NAL_PPS;
+                au->nal_units[ps_pos].pps =
+                    av_malloc(sizeof(*au->nal_units[ps_pos].pps));
+                if (!au->nal_units[ps_pos].pps)
+                    return AVERROR(ENOMEM);
+                memcpy(au->nal_units[ps_pos].pps,
+                       ctx->output.active_pps, 
sizeof(*ctx->output.active_pps));
+                ++au->nb_nal_units;
+                ++ps_pos;
+            }
+        }
+
+        for (i = ps_pos; i < au->nb_nal_units; i++) {
+            if (au->nal_units[i].type == H264_NAL_SLICE ||
+                au->nal_units[i].type == H264_NAL_IDR_SLICE) {
+                H264RawSliceHeader *slice = au->nal_units[i].slice.header;
+
+                pps = ctx->output.pps[slice->pic_parameter_set_id];
+                av_assert0(pps);
+                sps = ctx->output.sps[pps->seq_parameter_set_id];
+                av_assert0(sps);
+
+                au->nal_units[i].type = H264_NAL_IDR_SLICE;
+
+                slice->nal_ref_idc = 3;
+                slice->nal_unit_type = H264_NAL_IDR_SLICE;
+
+                slice->frame_num = 0;
+                slice->idr_pic_id = ctx->idr_pic_id;
+                if (sps->frame_mbs_only_flag || !slice->field_pic_flag)
+                    advance_frame_num = 1;
+
+                av_log(bsf, AV_LOG_VERBOSE, "Rewrite POC %d as 0 (IDR) "
+                       "[pts %"PRId64", dts %"PRId64"].\n",
+                       slice->pic_order_cnt_lsb,
+                       pkt->input_packet->pts, pkt->input_packet->dts);
+
+                ctx->poc_delta = slice->pic_order_cnt_lsb;
+                slice->pic_order_cnt_lsb = 0;
+
+                if (slice->field_pic_flag) {
+                    slice->delta_pic_order_cnt_bottom =
+                        slice->bottom_field_flag ? -1 : +1;
+                }
+
+                slice->no_output_of_prior_pics_flag = 0;
+                slice->long_term_reference_flag = 0;
+            }
+        }
+
+        ctx->frame_num = 0;
+        ctx->idr_pic_id = (ctx->idr_pic_id + 1) % 65536;
+        ctx->poc = 0;
+        ctx->idr = 0;
+        key = 1;
+    } else {
+        int max_poc_lsb, new_poc;
+        int j, k;
+
+        for (i = 0; i < au->nb_nal_units; i++) {
+            if (au->nal_units[i].type == H264_NAL_SLICE) {
+                H264RawSliceHeader *slice = au->nal_units[i].slice.header;
+
+                pps = ctx->output.pps[slice->pic_parameter_set_id];
+                av_assert0(pps);
+                sps = ctx->output.sps[pps->seq_parameter_set_id];
+                av_assert0(sps);
+                max_poc_lsb =
+                    1 << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
+
+                slice->frame_num = ctx->frame_num &
+                    ((1 << (sps->log2_max_frame_num_minus4 + 4)) - 1);
+                if (slice->nal_ref_idc > 0 &&
+                    (sps->frame_mbs_only_flag || !slice->field_pic_flag ||
+                     slice->bottom_field_flag))
+                    advance_frame_num = 1;
+
+                if (ctx->poc + ctx->poc_delta <
+                    slice->pic_order_cnt_lsb - max_poc_lsb / 2) {
+                    // Wrap from bottom to top.
+                    ctx->poc_delta += max_poc_lsb;
+                }
+                if (ctx->poc + ctx->poc_delta >
+                    slice->pic_order_cnt_lsb + max_poc_lsb / 2) {
+                    // Wrap from top to bottom.
+                    ctx->poc_delta -= max_poc_lsb;
+                }
+
+                new_poc = slice->pic_order_cnt_lsb - ctx->poc_delta;
+                av_log(bsf, AV_LOG_VERBOSE, "Rewrite POC %d as %d "
+                       "[pts %"PRId64", dts %"PRId64"].\n",
+                       slice->pic_order_cnt_lsb, new_poc,
+                       pkt->input_packet->pts, pkt->input_packet->dts);
+
+                if (new_poc > max_poc_lsb)
+                    new_poc -= max_poc_lsb;
+                if (new_poc < 0)
+                    drop = 1;
+                slice->pic_order_cnt_lsb = new_poc &
+                    ((1 << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4)) - 1);
+
+                // Fixup references back to the first frame, which may now
+                // point before it because B-pictures in between have been
+                // removed.
+                if (slice->ref_pic_list_modification_flag_l0) {
+                    int old, new, pred_old, pred_new, diff_new;
+                    if (slice->field_pic_flag)
+                        pred_old = pred_new = 2 * ctx->frame_num + 1;
+                    else
+                        pred_old = pred_new = ctx->frame_num;
+
+                    for (k = 0; slice->rplm_l0[k].modification_of_pic_nums_idc 
!= 3; k++) {
+                        if (slice->rplm_l0[k].modification_of_pic_nums_idc == 
0) {
+                            old = pred_old - 
(slice->rplm_l0[k].abs_diff_pic_num_minus1 + 1);
+                            if (old < 0) {
+                                new = -old % 2;
+                                av_log(bsf, AV_LOG_DEBUG, "Rewrite reference "
+                                       "pic_num = %d -> %d.\n", old, new);
+                            } else {
+                                new = old;
+                            }
+                            diff_new = pred_new - new - 1;
+                            if (diff_new < 0) {
+                                slice->rplm_l0[k].modification_of_pic_nums_idc 
= 1;
+                                slice->rplm_l0[k].abs_diff_pic_num_minus1 = 
-diff_new;
+                            } else {
+                                slice->rplm_l0[k].abs_diff_pic_num_minus1 = 
diff_new;
+                            }
+                            pred_new = new;
+                            pred_old = old;
+                        } else if 
(slice->rplm_l0[k].modification_of_pic_nums_idc == 1) {
+                            old = pred_old + 
(slice->rplm_l0[k].abs_diff_pic_num_minus1 + 1);
+                            if (old < 0) {
+                                new = -old % 2;
+                                av_log(bsf, AV_LOG_DEBUG, "Rewrite reference "
+                                       "pic_num = %d -> %d.\n", old, new);
+                            } else {
+                                new = old;
+                            }
+                            diff_new = new - pred_new - 1;
+                            if (diff_new < 0) {
+                                slice->rplm_l0[k].modification_of_pic_nums_idc 
= 0;
+                                slice->rplm_l0[k].abs_diff_pic_num_minus1 = 
-diff_new;
+                            } else {
+                                slice->rplm_l0[k].abs_diff_pic_num_minus1 = 
diff_new;
+                            }
+                            pred_new = new;
+                            pred_old = old;
+                        }
+                    }
+                }
+
+                // Remove MMCOs which refer to frames which no longer exist.
+                if (slice->adaptive_ref_pic_marking_mode_flag) {
+                    int curr_pic, short_pic, remove;
+                    if (slice->field_pic_flag)
+                        curr_pic = 2 * ctx->frame_num + 1;
+                    else
+                        curr_pic = ctx->frame_num;
+
+                    k = 0;
+                    for (j = 0; 
slice->mmco[j].memory_management_control_operation != 0; j++) {
+                        remove = 0;
+                        if (slice->mmco[j].memory_management_control_operation 
== 1) {
+                            short_pic = curr_pic -
+                                (slice->mmco[j].difference_of_pic_nums_minus1 
+ 1);
+                            if (short_pic < 0) {
+                                av_log(bsf, AV_LOG_DEBUG, "Remove MMCO to past 
"
+                                       "picture %d.\n", short_pic);
+                                remove = 1;
+                            }
+                        }
+                        if (!remove)
+                            slice->mmco[k++] = slice->mmco[j];
+                    }
+                    slice->mmco[k].memory_management_control_operation = 0;
+                }
+            }
+        }
+
+        key = 0;
+    }
+
+    if (drop) {
+        av_log(bsf, AV_LOG_VERBOSE, "Drop packet before start of GOP: "
+               "pts %"PRId64".\n", pkt->input_packet->pts);
+        av_packet_free(&pkt->input_packet);
+        ff_h264_raw_access_unit_uninit(au);
+        av_free(pkt);
+        goto again;
+    }
+
+    if (advance_frame_num)
+        ++ctx->frame_num;
+    ++ctx->poc;
+
+    err = ff_h264_raw_write_packet(&ctx->output, out, au);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to write output packet.\n");
+        return err;
+    }
+
+    err = av_packet_copy_props(out, pkt->input_packet);
+    if (err < 0)
+        return err;
+
+    if (key)
+        out->flags |= AV_PKT_FLAG_KEY;
+    else
+        out->flags &= ~AV_PKT_FLAG_KEY;
+
+    av_packet_free(&pkt->input_packet);
+    ff_h264_raw_access_unit_uninit(au);
+    av_free(pkt);
+
+    return 0;
+}
+
+static int h264_closegop_filter(AVBSFContext *bsf, AVPacket *out)
+{
+    H264CloseGOPContext *ctx = bsf->priv_data;
+    H264CloseGOPPacket *pkt;
+    H264RawAccessUnit *au;
+    AVPacket *in;
+    int err, i, idr, intra, slices;
+
+    if (ctx->eof || ctx->drain_counter > 0) {
+        err = h264_closegop_make_output(bsf, out);
+        if (err != AVERROR(EAGAIN))
+            return err;
+    }
+
+    err = ff_bsf_get_packet(bsf, &in);
+    if (err == AVERROR_EOF) {
+        ctx->eof = 1;
+        return h264_closegop_make_output(bsf, out);
+    }
+    if (err < 0)
+        return err;
+
+    pkt = av_mallocz(sizeof(*pkt));
+    if (!pkt)
+        return AVERROR(ENOMEM);
+
+    pkt->input_packet = in;
+    au = &pkt->access_unit;
+
+    err = ff_h264_raw_read_packet(&ctx->input, au, in);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to read input packet.\n");
+        return err;
+    }
+
+    slices = 0;
+    intra = 1;
+    idr = 0;
+    for (i = 0; i < au->nb_nal_units; i++) {
+        if (au->nal_units[i].type == H264_NAL_IDR_SLICE) {
+            idr = 1;
+            ++slices;
+        }
+        if (au->nal_units[i].type == H264_NAL_SLICE) {
+            if (au->nal_units[i].slice.header->slice_type % 5 != 2)
+                intra = 0;
+            ++slices;
+        }
+    }
+    if (slices == 0) {
+        // No slices in access unit?
+        intra = 0;
+    }
+
+    if (ctx->starting && !intra) {
+        ++ctx->initial_drop;
+        av_log(bsf, AV_LOG_DEBUG, "Dropping initial non-intra "
+               "access unit (pts %"PRId64").\n", in->pts);
+        av_packet_free(&in);
+        ff_h264_raw_access_unit_uninit(au);
+        av_free(pkt);
+        return AVERROR(EAGAIN);
+    }
+    if (ctx->initial_drop > 0)
+        av_log(bsf, AV_LOG_VERBOSE, "Dropped %d non-intra access "
+               "units at start of stream.\n", ctx->initial_drop);
+    ctx->starting = 0;
+
+    av_log(bsf, AV_LOG_DEBUG, "Input: pts %"PRId64" "
+           "intra %d idr %d (%d in GOP).\n",
+           in->pts, intra, idr, ctx->gop_counter);
+    if (intra)
+        ctx->last_intra = ctx->gop_counter;
+    ++ctx->gop_counter;
+
+    if (av_fifo_space(ctx->fifo) < sizeof(pkt)) {
+        av_log(ctx, AV_LOG_ERROR, "Access unit fifo overflow - "
+               "gop_size not large enough?\n");
+        return AVERROR(EIO);
+    }
+    av_fifo_generic_write(ctx->fifo, &pkt, sizeof(pkt), NULL);
+
+    if (idr) {
+        av_log(bsf, AV_LOG_VERBOSE, "Finish GOP now "
+               "as next frame is IDR.\n");
+        ctx->drain_counter =
+            (av_fifo_size(ctx->fifo) / sizeof(pkt)) - 1;
+        ctx->idr = 1;
+        ctx->gop_counter = 1;
+        ctx->last_intra  = 0;
+        return h264_closegop_make_output(bsf, out);
+
+    } else if (ctx->gop_counter >= ctx->gop_size) {
+        av_log(bsf, AV_LOG_VERBOSE, "Finish GOP on previous "
+               "intra frame as GOP size is exceeded.\n");
+        ctx->drain_counter = ctx->last_intra;
+        ctx->idr = 1;
+        ctx->gop_counter -= ctx->last_intra;
+        ctx->last_intra   = 0;
+        return h264_closegop_make_output(bsf, out);
+    }
+
+    return AVERROR(EAGAIN);
+}
+
+static int h264_closegop_init(AVBSFContext *bsf)
+{
+    H264CloseGOPContext *ctx = bsf->priv_data;
+    int err;
+
+    ctx->fifo = av_fifo_alloc((ctx->gop_size + 1) *
+                              sizeof(H264CloseGOPPacket*));
+    if (!ctx->fifo)
+        return AVERROR(ENOMEM);
+
+    err = ff_h264_raw_init(&ctx->input, bsf);
+    if (err < 0)
+        return err;
+
+    err = ff_h264_raw_init(&ctx->output, bsf);
+    if (err < 0)
+        return err;
+
+    if (bsf->par_in->extradata) {
+        H264RawAccessUnit extradata;
+
+        err = ff_h264_raw_read_extradata(&ctx->input,
+                                         &extradata, bsf->par_in);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to read extradata.\n");
+            return err;
+        }
+
+        err = ff_h264_raw_write_extradata(&ctx->output,
+                                          bsf->par_out, &extradata);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to write extradata.\n");
+            return err;
+        }
+
+        ff_h264_raw_access_unit_uninit(&extradata);
+    }
+
+    // First frame of output must be IDR.
+    ctx->idr = 1;
+
+    return 0;
+}
+
+static void h264_closegop_close(AVBSFContext *bsf)
+{
+    H264CloseGOPContext *ctx = bsf->priv_data;
+
+    ff_h264_raw_uninit(&ctx->input);
+    ff_h264_raw_uninit(&ctx->output);
+
+    av_fifo_free(ctx->fifo);
+}
+
+#define OFFSET(x) offsetof(H264CloseGOPContext, x)
+static const AVOption h264_closegop_options[] = {
+    { "gop_size", "Set target GOP size", OFFSET(gop_size),
+        AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX },
+    { NULL }
+};
+
+static const AVClass h264_closegop_class = {
+    .class_name = "h264_closegop_bsf",
+    .item_name  = av_default_item_name,
+    .option     = h264_closegop_options,
+    .version    = LIBAVCODEC_VERSION_MAJOR,
+};
+
+static const enum AVCodecID h264_closegop_codec_ids[] = {
+    AV_CODEC_ID_H264, AV_CODEC_ID_NONE,
+};
+
+const AVBitStreamFilter ff_h264_closegop_bsf = {
+    .name           = "h264_closegop",
+    .priv_data_size = sizeof(H264CloseGOPContext),
+    .priv_class     = &h264_closegop_class,
+    .init           = &h264_closegop_init,
+    .close          = &h264_closegop_close,
+    .filter         = &h264_closegop_filter,
+    .codec_ids      = h264_closegop_codec_ids,
+};
-- 
2.11.0

_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to