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