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
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel