diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi index c63c20370ffec..57f8682d9f700 100644 --- a/doc/bitstream_filters.texi +++ b/doc/bitstream_filters.texi @@ -243,6 +243,17 @@ ffmpeg -i hapqa_inputfile.mov -c copy -bsf:v hapqa_extract=texture=alpha -tag:v Modify metadata embedded in an H.264 stream. @table @option +@item a53_cc +Insert or remove registered userdata SEI NAL units containing A/53 closed captions. + +@table @samp +@item pass +@item insert +@item remove +@end table + +Default is pass. + @item aud Insert or remove AUD NAL units in all access units of the stream. @@ -387,6 +398,17 @@ confuse other transformations which require correct extradata. Modify metadata embedded in an HEVC stream. @table @option +@item a53_cc +Insert or remove registered userdata SEI NAL units containing A/53 closed captions. + +@table @samp +@item pass +@item insert +@item remove +@end table + +Default is pass. + @item aud Insert or remove AUD NAL units in all access units of the stream. @@ -537,6 +559,17 @@ Decompress non-standard compressed MP3 audio headers. Modify metadata embedded in an MPEG-2 stream. @table @option +@item a53_cc +Insert or remove user data containing A/53 closed captions. + +@table @samp +@item pass +@item insert +@item remove +@end table + +Default is pass. + @item display_aspect_ratio Set the display aspect ratio in the stream. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 1fb963f82087a..156fd6215a8f2 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1212,12 +1212,12 @@ OBJS-$(CONFIG_EXTRACT_EXTRADATA_BSF) += extract_extradata_bsf.o \ av1_parse.o h2645_parse.o OBJS-$(CONFIG_FILTER_UNITS_BSF) += filter_units_bsf.o OBJS-$(CONFIG_H264_METADATA_BSF) += h264_metadata_bsf.o h264_levels.o \ - h2645data.o + h2645data.o cbs_misc.o OBJS-$(CONFIG_H264_MP4TOANNEXB_BSF) += h264_mp4toannexb_bsf.o OBJS-$(CONFIG_H264_REDUNDANT_PPS_BSF) += h264_redundant_pps_bsf.o OBJS-$(CONFIG_HAPQA_EXTRACT_BSF) += hapqa_extract_bsf.o hap.o OBJS-$(CONFIG_HEVC_METADATA_BSF) += h265_metadata_bsf.o h265_profile_level.o \ - h2645data.o + h2645data.o cbs_misc.o OBJS-$(CONFIG_HEVC_MP4TOANNEXB_BSF) += hevc_mp4toannexb_bsf.o OBJS-$(CONFIG_IMX_DUMP_HEADER_BSF) += imx_dump_header_bsf.o OBJS-$(CONFIG_MEDIA100_TO_MJPEGB_BSF) += media100_to_mjpegb_bsf.o @@ -1227,7 +1227,7 @@ OBJS-$(CONFIG_MPEG4_UNPACK_BFRAMES_BSF) += mpeg4_unpack_bframes_bsf.o OBJS-$(CONFIG_MOV2TEXTSUB_BSF) += movsub_bsf.o OBJS-$(CONFIG_MP3_HEADER_DECOMPRESS_BSF) += mp3_header_decompress_bsf.o \ mpegaudiotabs.o -OBJS-$(CONFIG_MPEG2_METADATA_BSF) += mpeg2_metadata_bsf.o +OBJS-$(CONFIG_MPEG2_METADATA_BSF) += mpeg2_metadata_bsf.o cbs_misc.o OBJS-$(CONFIG_NOISE_BSF) += noise_bsf.o OBJS-$(CONFIG_NULL_BSF) += null_bsf.o OBJS-$(CONFIG_OPUS_METADATA_BSF) += opus_metadata_bsf.o diff --git a/libavcodec/cbs_misc.c b/libavcodec/cbs_misc.c new file mode 100644 index 0000000000000..d0ced562f5895 --- /dev/null +++ b/libavcodec/cbs_misc.c @@ -0,0 +1,217 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/attributes.h" +#include "libavutil/avassert.h" + +#include "cbs.h" +#include "cbs_internal.h" +#include "cbs_misc.h" + +#define CHECK(call) do { \ + err = (call); \ + if (err < 0) \ + return err; \ + } while (0) + +#define FUNC_NAME(rw, codec, name) cbs_ ## codec ## _ ## rw ## _ ## name +#define FUNC_MISC(rw, name) FUNC_NAME(rw, misc, name) +#define FUNC(name) FUNC_MISC(READWRITE, name) + + +#define READWRITE read +#define RWContext GetBitContext + +#define xui(width, name, var) do { \ + uint32_t value = 0; \ + CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, NULL, \ + &value, 0, MAX_UINT_BITS(width))); \ + var = value; \ + } while (0) + +#define ui(width, name) \ + xui(width, name, current->name) + +#define fixed(width, name, expected) do { \ + av_unused uint32_t value; \ + CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, NULL, \ + &value, expected, expected)); \ + } while (0) + +#include "cbs_misc_syntax_template.c" + +#undef READWRITE +#undef RWContext +#undef xui +#undef ui +#undef fixed + + +#define READWRITE write +#define RWContext PutBitContext + +#define xui(width, name, var) do { \ + CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, NULL, \ + var, 0, MAX_UINT_BITS(width))); \ + } while (0) + +#define ui(width, name) \ + xui(width, name, current->name) + +#define fixed(width, name, value) do { \ + CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, NULL, \ + value, value, value)); \ + } while (0) + +#include "cbs_misc_syntax_template.c" + +#undef READWRITE +#undef RWContext +#undef xui +#undef ui +#undef fixed + + +int ff_cbs_read_a53_user_data(CodedBitstreamContext *ctx, + A53UserData *data, + const uint8_t *read_buffer, size_t length) +{ + GetBitContext gbc; + int err; + + err = init_get_bits(&gbc, read_buffer, 8 * length); + if (err < 0) + return err; + + return cbs_misc_read_a53_user_data(ctx, &gbc, data); +} + +int ff_cbs_write_a53_user_data(CodedBitstreamContext *ctx, + uint8_t *write_buffer, size_t *length, + A53UserData *data) +{ + PutBitContext pbc; + int err; + + init_put_bits(&pbc, write_buffer, *length); + + err = cbs_misc_write_a53_user_data(ctx, &pbc, data); + if (err < 0) { + // Includes AVERROR(ENOSPC). + return err; + } + + // That output must be aligned. + av_assert0(put_bits_count(&pbc) % 8 == 0); + + *length = put_bits_count(&pbc) / 8; + + flush_put_bits(&pbc); + + return 0; +} + +int ff_cbs_read_a53_cc_side_data(CodedBitstreamContext *ctx, + A53UserData *data, + const uint8_t *side_data, + size_t side_data_size) +{ + GetBitContext gbc; + CEA708CCData *cc; + int err, i, cc_count; + + if (side_data_size % 3) { + av_log(ctx->log_ctx, AV_LOG_ERROR, "A53 CC side data length must " + "be a multiple of 3 (got %zu).\n", side_data_size); + return AVERROR(EINVAL); + } + cc_count = side_data_size / 3; + if (cc_count > 31) { + av_log(ctx->log_ctx, AV_LOG_ERROR, "A53 CC can only fit 31 packets " + "in a single user data block (got %d).\n", cc_count); + return AVERROR(EINVAL); + } + + *data = (A53UserData) { + .user_identifier = A53_USER_IDENTIFIER_ATSC, + + .atsc = { + .user_data_type_code = A53_USER_DATA_TYPE_CODE_CC_DATA, + + .cc_data = { + .process_em_data_flag = 0, + .process_cc_data_flag = 1, + .additional_data_flag = 0, + + .em_data = 0, + + .cc_count = cc_count, + }, + }, + }; + cc = &data->atsc.cc_data; + + err = init_get_bits(&gbc, side_data, 8 * side_data_size); + if (err < 0) + return err; + + for (i = 0; i < cc->cc_count; i++) { + err = cbs_misc_read_cea708_cc_data_packet(ctx, &gbc, + &cc->cc_data_pkts[i]); + if (err < 0) + return err; + } + + return 0; +} + +int ff_cbs_write_a53_cc_side_data(CodedBitstreamContext *ctx, + uint8_t **side_data, + size_t *side_data_size, + A53UserData *data) +{ + PutBitContext pbc; + CEA708CCData *cc; + int err, i; + + if (data->user_identifier != A53_USER_IDENTIFIER_ATSC || + data->atsc.user_data_type_code != A53_USER_DATA_TYPE_CODE_CC_DATA) + return AVERROR(EINVAL); + + cc = &data->atsc.cc_data; + + err = av_reallocp(side_data, *side_data_size + 3 * cc->cc_count); + if (err < 0) + return err; + + init_put_bits(&pbc, *side_data + *side_data_size, 3 * cc->cc_count); + + for (i = 0; i < cc->cc_count; i++) { + err = cbs_misc_write_cea708_cc_data_packet(ctx, &pbc, + &cc->cc_data_pkts[i]); + if (err < 0) { + av_freep(side_data); + return err; + } + } + + flush_put_bits(&pbc); + *side_data_size += 3 * cc->cc_count; + + return 0; +} diff --git a/libavcodec/cbs_misc.h b/libavcodec/cbs_misc.h new file mode 100644 index 0000000000000..0d7ab2c8e73ea --- /dev/null +++ b/libavcodec/cbs_misc.h @@ -0,0 +1,109 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_CBS_MISC_H +#define AVCODEC_CBS_MISC_H + +#include +#include + +#include "libavutil/common.h" + + +enum { + A53_USER_IDENTIFIER_ATSC = MKBETAG('G', 'A', '9', '4'), + A53_USER_IDENTIFIER_AFD = MKBETAG('D', 'T', 'G', '1'), +}; + +enum { + A53_USER_DATA_TYPE_CODE_CC_DATA = 0x03, + A53_USER_DATA_TYPE_CODE_BAR_DATA = 0x06, +}; + +typedef struct A53BarData { + uint8_t top_bar_flag; + uint8_t bottom_bar_flag; + uint8_t left_bar_flag; + uint8_t right_bar_flag; + + uint16_t line_number_end_of_top_bar; + uint16_t line_number_end_of_bottom_bar; + uint16_t line_number_end_of_left_bar; + uint16_t line_number_end_of_right_bar; +} A53BarData; + +typedef struct CEA708CCDataPacket { + uint8_t cc_valid; + uint8_t cc_type; + uint8_t cc_data_1; + uint8_t cc_data_2; +} CEA708CCDataPacket; + +typedef struct CEA708CCData { + uint8_t process_em_data_flag; + uint8_t process_cc_data_flag; + uint8_t additional_data_flag; + + uint8_t em_data; + + uint8_t cc_count; + CEA708CCDataPacket cc_data_pkts[31]; +} CEA708CCData; + +typedef struct A53ATSCUserData { + uint8_t user_data_type_code; + union { + CEA708CCData cc_data; + A53BarData bar_data; + }; +} A53ATSCUserData; + +typedef struct A53AFDData { + uint8_t active_format_flag; + uint8_t active_format; +} A53AFDData; + +typedef struct A53UserData { + uint32_t user_identifier; + union { + A53ATSCUserData atsc; + A53AFDData afd; + }; +} A53UserData; + + +int ff_cbs_read_a53_user_data(CodedBitstreamContext *ctx, + A53UserData *data, + const uint8_t *read_buffer, size_t length); + +int ff_cbs_write_a53_user_data(CodedBitstreamContext *ctx, + uint8_t *write_buffer, size_t *length, + A53UserData *data); + +int ff_cbs_read_a53_cc_side_data(CodedBitstreamContext *ctx, + A53UserData *data, + const uint8_t *side_data, + size_t side_data_size); + +int ff_cbs_write_a53_cc_side_data(CodedBitstreamContext *ctx, + uint8_t **side_data, + size_t *side_data_length, + A53UserData *data); + + +#endif /* AVCODEC_CBS_MISC_H */ diff --git a/libavcodec/cbs_misc_syntax_template.c b/libavcodec/cbs_misc_syntax_template.c new file mode 100644 index 0000000000000..7b98c7cc85890 --- /dev/null +++ b/libavcodec/cbs_misc_syntax_template.c @@ -0,0 +1,150 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +static int FUNC(a53_bar_data)(CodedBitstreamContext *ctx, RWContext *rw, + A53BarData *current) +{ + int err; + + ui(1, top_bar_flag); + ui(1, bottom_bar_flag); + ui(1, left_bar_flag); + ui(1, right_bar_flag); + fixed(4, reserved, 0xf); + + if (current->top_bar_flag) { + fixed(2, one_bits, 3); + ui(14, line_number_end_of_top_bar); + } + if (current->bottom_bar_flag) { + fixed(2, one_bits, 3); + ui(14, line_number_end_of_bottom_bar); + } + if (current->left_bar_flag) { + fixed(2, one_bits, 3); + ui(14, line_number_end_of_left_bar); + } + if (current->right_bar_flag) { + fixed(2, one_bits, 3); + ui(14, line_number_end_of_right_bar); + } + + return 0; +} + +static int FUNC(cea708_cc_data_packet)(CodedBitstreamContext *ctx, + RWContext *rw, + CEA708CCDataPacket *current) +{ + int err; + + fixed(5, marker_bits, 0x1f); + ui(1, cc_valid); + ui(2, cc_type); + + ui(8, cc_data_1); + ui(8, cc_data_2); + + return 0; +} + +static int FUNC(cea708_cc_data)(CodedBitstreamContext *ctx, RWContext *rw, + CEA708CCData *current) +{ + int err, i; + + ui(1, process_em_data_flag); + ui(1, process_cc_data_flag); + ui(1, additional_data_flag); + + ui(5, cc_count); + + ui(8, em_data); + + for (i = 0; i < current->cc_count; i++) { + CHECK(FUNC(cea708_cc_data_packet)(ctx, rw, + ¤t->cc_data_pkts[i])); + } + + fixed(8, marker_bits, 0xff); + + if (current->additional_data_flag) { + // Ignored. + } + + return 0; +} + +static int FUNC(a53_atsc_user_data)(CodedBitstreamContext *ctx, RWContext *rw, + A53ATSCUserData *current) +{ + int err; + + ui(8, user_data_type_code); + + switch (current->user_data_type_code) { + case A53_USER_DATA_TYPE_CODE_CC_DATA: + return FUNC(cea708_cc_data)(ctx, rw, ¤t->cc_data); + case A53_USER_DATA_TYPE_CODE_BAR_DATA: + return FUNC(a53_bar_data)(ctx, rw, ¤t->bar_data); + default: + av_log(ctx->log_ctx, AV_LOG_WARNING, + "Unknown ATSC user data found: type code %#02x.\n", + current->user_data_type_code); + } + + return 0; +} + +static int FUNC(a53_afd_data)(CodedBitstreamContext *ctx, RWContext *rw, + A53AFDData *current) +{ + int err; + + fixed(1, zero_bit, 0); + ui(1, active_format_flag); + fixed(6, alignment_bits, 1); + + if (current->active_format_flag) { + fixed(4, reserved, 0xf); + ui(4, active_format); + } + + return 0; +} + +static int FUNC(a53_user_data)(CodedBitstreamContext *ctx, RWContext *rw, + A53UserData *current) +{ + int err; + + ui(32, user_identifier); + + switch (current->user_identifier) { + case A53_USER_IDENTIFIER_ATSC: + return FUNC(a53_atsc_user_data)(ctx, rw, ¤t->atsc); + case A53_USER_IDENTIFIER_AFD: + return FUNC(a53_afd_data)(ctx, rw, ¤t->afd); + default: + av_log(ctx->log_ctx, AV_LOG_WARNING, + "Unknown registered user data found: identifier %#08x.\n", + current->user_identifier); + } + + return 0; +} diff --git a/libavcodec/cbs_sei.c b/libavcodec/cbs_sei.c index 50a513f592aa2..1ea2bbc109800 100644 --- a/libavcodec/cbs_sei.c +++ b/libavcodec/cbs_sei.c @@ -347,6 +347,31 @@ static void cbs_sei_delete_message(SEIRawMessageList *list, } } +int ff_cbs_sei_delete_message(CodedBitstreamContext *ctx, + CodedBitstreamFragment *au, + SEIRawMessage *message) +{ + int err, i, j; + + for (i = 0; i < au->nb_units; i++) { + CodedBitstreamUnit *unit = &au->units[i]; + SEIRawMessageList *list; + + err = cbs_sei_get_message_list(ctx, unit, &list); + if (err < 0) + continue; + + for (j = 0; j < list->nb_messages; j++) { + if (message == &list->messages[j]) { + cbs_sei_delete_message(list, j); + return 0; + } + } + } + + return AVERROR(ENOENT); +} + void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx, CodedBitstreamFragment *au, uint32_t payload_type) diff --git a/libavcodec/cbs_sei.h b/libavcodec/cbs_sei.h index 1c327a4689e3a..96bd116a53699 100644 --- a/libavcodec/cbs_sei.h +++ b/libavcodec/cbs_sei.h @@ -202,4 +202,11 @@ void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx, CodedBitstreamFragment *au, uint32_t payload_type); +/** + * Delete a message from the access unit. + */ +int ff_cbs_sei_delete_message(CodedBitstreamContext *ctx, + CodedBitstreamFragment *au, + SEIRawMessage *message); + #endif /* AVCODEC_CBS_SEI_H */ diff --git a/libavcodec/h264_metadata_bsf.c b/libavcodec/h264_metadata_bsf.c index d318bf0ceec16..bbe74c3cee0d4 100644 --- a/libavcodec/h264_metadata_bsf.c +++ b/libavcodec/h264_metadata_bsf.c @@ -26,6 +26,8 @@ #include "cbs.h" #include "cbs_bsf.h" #include "cbs_h264.h" +#include "cbs_misc.h" +#include "cbs_sei.h" #include "h264.h" #include "h264_levels.h" #include "h264_sei.h" @@ -81,6 +83,7 @@ typedef struct H264MetadataContext { H264RawSEIDisplayOrientation display_orientation_payload; int level; + int a53_cc; } H264MetadataContext; @@ -463,6 +466,137 @@ static int h264_metadata_handle_display_orientation(AVBSFContext *bsf, return 0; } +static int h264_metadata_handle_a53_cc(AVBSFContext *bsf, AVPacket *pkt, + CodedBitstreamFragment *au) +{ + H264MetadataContext *ctx = bsf->priv_data; + SEIRawMessage *message; + int err; + + message = NULL; + while (ff_cbs_sei_find_message(ctx->common.output, au, + SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35, + &message) == 0) { + SEIRawUserDataRegistered *udr = message->payload; + A53UserData a53_ud; + uint8_t *a53_side_data = NULL; + size_t a53_side_data_size = 0; + + if (udr->data_length < 2) { + // Too short to contain a provider code. + continue; + } + + if (AV_RB16(udr->data) != 0x31) { // provider_code as atsc_provider_code + // Not ATSC. + continue; + } + + // The first two bytes of the message is provider_code so we offset the data by that + err = ff_cbs_read_a53_user_data(ctx->common.output, + &a53_ud, + udr->data + 2, + udr->data_length - 2); + if (err < 0) { + // Invalid or something completely different. + continue; + } + if (a53_ud.user_identifier != A53_USER_IDENTIFIER_ATSC || + a53_ud.atsc.user_data_type_code != + A53_USER_DATA_TYPE_CODE_CC_DATA) { + // Valid but something else (e.g. AFD). + continue; + } + + err = ff_cbs_write_a53_cc_side_data(ctx->common.output, + &a53_side_data, + &a53_side_data_size, + &a53_ud); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to write " + "A/53 user data for packet side data.\n"); + return err; + } + + if (a53_side_data) { + err = av_packet_add_side_data(pkt, AV_PKT_DATA_A53_CC, + a53_side_data, a53_side_data_size); + if (err) { + av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted A/53 " + "side data to packet.\n"); + av_freep(&a53_side_data); + return err; + } + + if (ctx->a53_cc == BSF_ELEMENT_REMOVE || + ctx->a53_cc == BSF_ELEMENT_INSERT) { + ff_cbs_sei_delete_message(ctx->common.output, au, message); + + // Reset iteration + message = NULL; + } + } + } + + if (ctx->a53_cc == BSF_ELEMENT_INSERT) { + uint8_t *data; + size_t size; + int offset = 0; + + data = av_packet_get_side_data(pkt, AV_PKT_DATA_A53_CC, &size); + while (size > 0) { + A53UserData a53_ud; + int rsize = FFMIN(93, size); + + err = ff_cbs_read_a53_cc_side_data(ctx->common.output, &a53_ud, + data + offset, rsize); + offset += rsize; + size -= rsize; + if (err < 0) { + av_log(bsf, AV_LOG_WARNING, "Invalid A/53 closed captions " + "in packet side data dropped.\n"); + } else { + AVBufferRef *udr_buf = av_buffer_allocz(sizeof(SEIRawUserDataRegistered)); + SEIRawUserDataRegistered *udr = (SEIRawUserDataRegistered*)udr_buf->data; + size_t size = 9 + 3 * a53_ud.atsc.cc_data.cc_count; + + udr->data_ref = av_buffer_allocz(2 + size); + if (!udr->data_ref) { + return AVERROR(ENOMEM); + } + udr->data = udr->data_ref->data; + udr->data_length = udr->data_ref->size; + + udr->itu_t_t35_country_code = 0xB5; // usa_country_code + AV_WB16(udr->data, 0x31); // provider_code as atsc_provider_code + + err = ff_cbs_write_a53_user_data(ctx->common.output, udr->data + 2, + &size, &a53_ud); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to write " + "A/53 user data.\n"); + av_buffer_unref(&udr->data_ref); + av_buffer_unref(&udr_buf); + return err; + } + + err = ff_cbs_sei_add_message(ctx->common.output, au, 1, + SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35, + udr, udr_buf); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to add A/53 user data " + "SEI message to access unit.\n"); + av_buffer_unref(&udr->data_ref); + av_buffer_unref(&udr_buf); + return err; + } + } + } + } + + return 0; +} + static int h264_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt, CodedBitstreamFragment *au) { @@ -533,6 +667,12 @@ static int h264_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt, return err; } + if (pkt && ctx->a53_cc != BSF_ELEMENT_PASS) { + err = h264_metadata_handle_a53_cc(bsf, pkt, au); + if (err < 0) + return err; + } + if (pkt) ctx->done_first_au = 1; @@ -696,6 +836,10 @@ static const AVOption h264_metadata_options[] = { { LEVEL("6.2", 62) }, #undef LEVEL + BSF_ELEMENT_OPTIONS_PIRE("a53_cc", + "A/53 Closed Captions in SEI NAL units", + a53_cc, FLAGS), + { NULL } }; diff --git a/libavcodec/h265_metadata_bsf.c b/libavcodec/h265_metadata_bsf.c index 6787bd14a123c..c3acbfb167013 100644 --- a/libavcodec/h265_metadata_bsf.c +++ b/libavcodec/h265_metadata_bsf.c @@ -24,6 +24,8 @@ #include "cbs.h" #include "cbs_bsf.h" #include "cbs_h265.h" +#include "cbs_misc.h" +#include "cbs_sei.h" #include "h2645data.h" #include "hevc.h" #include "h265_profile_level.h" @@ -62,6 +64,7 @@ typedef struct H265MetadataContext { int level; int level_guess; int level_warned; + int a53_cc; } H265MetadataContext; @@ -322,6 +325,137 @@ static int h265_metadata_update_sps(AVBSFContext *bsf, return 0; } +static int h265_metadata_extract_a53_cc(AVBSFContext *bsf, AVPacket *pkt, + CodedBitstreamFragment *au) +{ + H265MetadataContext *ctx = bsf->priv_data; + SEIRawMessage *message; + int err; + + message = NULL; + while (ff_cbs_sei_find_message(ctx->common.output, au, + SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35, + &message) == 0) { + SEIRawUserDataRegistered *udr = message->payload; + A53UserData a53_ud; + uint8_t *a53_side_data = NULL; + size_t a53_side_data_size = 0; + + if (udr->data_length < 2) { + // Too short to contain a provider code. + continue; + } + + if (AV_RB16(udr->data) != 0x31) { // provider_code as atsc_provider_code + // Not ATSC. + continue; + } + + // The first two bytes of the message is provider_code + err = ff_cbs_read_a53_user_data(ctx->common.output, + &a53_ud, + udr->data + 2, + udr->data_length - 2); + if (err < 0) { + // Invalid or something completely different. + continue; + } + if (a53_ud.user_identifier != A53_USER_IDENTIFIER_ATSC || + a53_ud.atsc.user_data_type_code != + A53_USER_DATA_TYPE_CODE_CC_DATA) { + // Valid but something else (e.g. AFD). + continue; + } + + err = ff_cbs_write_a53_cc_side_data(ctx->common.output, + &a53_side_data, + &a53_side_data_size, + &a53_ud); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to write " + "A/53 user data for packet side data.\n"); + return err; + } + + if (a53_side_data) { + err = av_packet_add_side_data(pkt, AV_PKT_DATA_A53_CC, + a53_side_data, a53_side_data_size); + if (err) { + av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted A/53 " + "side data to packet.\n"); + av_freep(&a53_side_data); + return err; + } + + if (ctx->a53_cc == BSF_ELEMENT_REMOVE || + ctx->a53_cc == BSF_ELEMENT_INSERT) { + ff_cbs_sei_delete_message(ctx->common.output, au, message); + + // Reset iteration + message = NULL; + } + } + } + + if (ctx->a53_cc == BSF_ELEMENT_INSERT) { + uint8_t *data; + size_t size; + int offset = 0; + + data = av_packet_get_side_data(pkt, AV_PKT_DATA_A53_CC, &size); + while (size > 0) { + A53UserData a53_ud; + int rsize = FFMIN(93, size); + + err = ff_cbs_read_a53_cc_side_data(ctx->common.output, &a53_ud, + data + offset, rsize); + offset += rsize; + size -= rsize; + if (err < 0) { + av_log(bsf, AV_LOG_WARNING, "Invalid A/53 closed captions " + "in packet side data dropped.\n"); + } else { + AVBufferRef *udr_buf = av_buffer_allocz(sizeof(SEIRawUserDataRegistered)); + SEIRawUserDataRegistered *udr = (SEIRawUserDataRegistered*)udr_buf->data; + size_t size = 9 + 3 * a53_ud.atsc.cc_data.cc_count; + + udr->data_ref = av_buffer_allocz(2 + size); + if (!udr->data_ref) { + return AVERROR(ENOMEM); + } + udr->data = udr->data_ref->data; + udr->data_length = udr->data_ref->size; + + udr->itu_t_t35_country_code = 0xB5; // usa_country_code + AV_WB16(udr->data, 0x31); // provider_code as atsc_provider_code + + err = ff_cbs_write_a53_user_data(ctx->common.output, udr->data + 2, + &size, &a53_ud); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to write " + "A/53 user data.\n"); + av_buffer_unref(&udr->data_ref); + av_buffer_unref(&udr_buf); + return err; + } + + err = ff_cbs_sei_add_message(ctx->common.output, au, 1, + SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35, + udr, udr_buf); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to add A/53 user data " + "SEI message to access unit.\n"); + av_buffer_unref(&udr->data_ref); + av_buffer_unref(&udr_buf); + return err; + } + } + } + } + + return 0; +} + static int h265_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt, CodedBitstreamFragment *au) { @@ -387,6 +521,12 @@ static int h265_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt, } } + if (pkt && ctx->a53_cc != BSF_ELEMENT_PASS) { + err = h265_metadata_extract_a53_cc(bsf, pkt, au); + if (err < 0) + return err; + } + return 0; } @@ -478,6 +618,10 @@ static const AVOption h265_metadata_options[] = { { LEVEL("8.5", 255) }, #undef LEVEL + BSF_ELEMENT_OPTIONS_PIRE("a53_cc", + "A/53 Closed Captions in SEI NAL units", + a53_cc, FLAGS), + { NULL } }; diff --git a/libavcodec/mpeg2_metadata_bsf.c b/libavcodec/mpeg2_metadata_bsf.c index 1aa6e027914ec..888bace6b25a2 100644 --- a/libavcodec/mpeg2_metadata_bsf.c +++ b/libavcodec/mpeg2_metadata_bsf.c @@ -23,6 +23,7 @@ #include "bsf_internal.h" #include "cbs.h" #include "cbs_bsf.h" +#include "cbs_misc.h" #include "cbs_mpeg2.h" #include "mpeg12.h" @@ -39,10 +40,79 @@ typedef struct MPEG2MetadataContext { int colour_primaries; int transfer_characteristics; int matrix_coefficients; + int a53_cc; int mpeg1_warned; } MPEG2MetadataContext; +static int mpeg2_metadata_handle_a53_cc(AVBSFContext *bsf, + AVPacket *pkt, + CodedBitstreamFragment *frag) +{ + MPEG2MetadataContext *ctx = bsf->priv_data; + int err, i; + uint8_t *a53_side_data = NULL; + size_t a53_side_data_size = 0; + + if (ctx->a53_cc == BSF_ELEMENT_REMOVE || ctx->a53_cc == BSF_ELEMENT_EXTRACT) { + for (i = 0; i < frag->nb_units; i++) { + MPEG2RawUserData *ud; + A53UserData a53_ud; + + if (frag->units[i].type != MPEG2_START_USER_DATA) + continue; + ud = frag->units[i].content; + + err = ff_cbs_read_a53_user_data(ctx->common.output, &a53_ud, ud->user_data, + ud->user_data_length); + if (err < 0) { + // Invalid or something completely different. + continue; + } + if (a53_ud.user_identifier != A53_USER_IDENTIFIER_ATSC || + a53_ud.atsc.user_data_type_code != + A53_USER_DATA_TYPE_CODE_CC_DATA) { + // Valid but something else (e.g. AFD). + continue; + } + + if (ctx->a53_cc == BSF_ELEMENT_REMOVE) { + ff_cbs_delete_unit(frag, i); + --i; + break; + } else if(ctx->a53_cc == BSF_ELEMENT_EXTRACT) { + err = ff_cbs_write_a53_cc_side_data(ctx->common.output, + &a53_side_data, + &a53_side_data_size, + &a53_ud); + if (err < 0) { + av_log(bsf, AV_LOG_ERROR, "Failed to write " + "A/53 user data for packet side data.\n"); + goto fail; + } + + if (a53_side_data) { + err = av_packet_add_side_data(pkt, AV_PKT_DATA_A53_CC, + a53_side_data, a53_side_data_size); + if (err) { + av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted A/53 " + "side data to packet.\n"); + goto fail; + } + a53_side_data = NULL; + } + } + } + } + + err = 0; + +fail: + av_freep(&a53_side_data); + + return err; +} + static int mpeg2_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt, @@ -54,6 +124,13 @@ static int mpeg2_metadata_update_fragment(AVBSFContext *bsf, MPEG2RawSequenceDisplayExtension *sde = NULL; int i, se_pos; + if (pkt && ctx->a53_cc != BSF_ELEMENT_PASS) { + int err; + err = mpeg2_metadata_handle_a53_cc(bsf, pkt, frag); + if (err < 0) + return err; + } + for (i = 0; i < frag->nb_units; i++) { if (frag->units[i].type == MPEG2_START_SEQUENCE_HEADER) { sh = frag->units[i].content; @@ -218,6 +295,9 @@ static const AVOption mpeg2_metadata_options[] = { OFFSET(matrix_coefficients), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, FLAGS }, + BSF_ELEMENT_OPTIONS_PIRE("a53_cc", + "A/53 Closed Captions in user data", + a53_cc, FLAGS), { NULL } };