diff --git a/libavcodec/cbs_misc.c b/libavcodec/cbs_misc.c
new file mode 100644
index 0000000000..d0ced562f5
--- /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 0000000000..0d7ab2c8e7
--- /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 <stddef.h>
+#include <stdint.h>
+
+#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 0000000000..7b98c7cc85
--- /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,
+                                          &current->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, &current->cc_data);
+    case A53_USER_DATA_TYPE_CODE_BAR_DATA:
+        return FUNC(a53_bar_data)(ctx, rw, &current->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, &current->atsc);
+    case A53_USER_IDENTIFIER_AFD:
+        return FUNC(a53_afd_data)(ctx, rw, &current->afd);
+    default:
+        av_log(ctx->log_ctx, AV_LOG_WARNING,
+               "Unknown registered user data found: identifier %#08x.\n",
+               current->user_identifier);
+    }
+
+    return 0;
+}
