diff --git a/libavutil/Makefile b/libavutil/Makefile
index dc9012f9a8..b6115859fa 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -17,6 +17,7 @@ HEADERS = adler32.h                                                     \
           buffer.h                                                      \
           cast5.h                                                       \
           camellia.h                                                    \
+          ccfifo.h                                                      \
           channel_layout.h                                              \
           common.h                                                      \
           cpu.h                                                         \
@@ -113,6 +114,7 @@ OBJS = adler32.o                                                        \
        buffer.o                                                         \
        cast5.o                                                          \
        camellia.o                                                       \
+       ccfifo.o                                                         \
        channel_layout.o                                                 \
        cpu.o                                                            \
        crc.o                                                            \
diff --git a/libavutil/ccfifo.c b/libavutil/ccfifo.c
new file mode 100644
index 0000000000..98ee365eee
--- /dev/null
+++ b/libavutil/ccfifo.c
@@ -0,0 +1,192 @@
+/*
+ * CEA-708 Closed Captioning FIFO
+ * Copyright (c) 2023 LTN Global Communications
+ *
+ * Author: Devin Heitmueller <dheitmueller@ltnglobal.com>
+ *
+ * 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 "ccfifo.h"
+
+struct AVCCFifo {
+    AVFifo *cc_608_fifo;
+    AVFifo *cc_708_fifo;
+    int expected_cc_count;
+    int expected_608;
+    int cc_detected;
+    void *log_ctx;
+};
+
+#define MAX_CC_ELEMENTS 128
+#define CC_BYTES_PER_ENTRY 3
+
+struct cc_lookup {
+    int num;
+    int den;
+    int cc_count;
+    int num_608;
+};
+
+const static struct cc_lookup cc_lookup_vals[] = {
+    { 15, 1, 40, 4 },
+    { 24, 1, 25, 3 },
+    { 24000, 1001, 25, 3 },
+    { 30, 1, 20, 2 },
+    { 30000, 1001, 20, 2},
+    { 60, 1, 10, 1 },
+    { 60000, 1001, 10, 1},
+};
+
+void av_ccfifo_freep(AVCCFifo **ccf)
+{
+    if (ccf && *ccf) {
+        AVCCFifo *tmp = *ccf;
+        if (tmp->cc_608_fifo)
+            av_fifo_freep2(&tmp->cc_608_fifo);
+        if (tmp->cc_708_fifo)
+            av_fifo_freep2(&tmp->cc_708_fifo);
+        av_freep(*ccf);
+    }
+}
+
+AVCCFifo *av_ccfifo_alloc(AVRational *framerate, void *log_ctx)
+{
+    AVCCFifo *ccf;
+    int i;
+
+    ccf = av_mallocz(sizeof(*ccf));
+    if (!ccf)
+        return NULL;
+
+    if (!(ccf->cc_708_fifo = av_fifo_alloc2(MAX_CC_ELEMENTS, CC_BYTES_PER_ENTRY, 0)))
+        goto error;
+
+    if (!(ccf->cc_608_fifo = av_fifo_alloc2(MAX_CC_ELEMENTS, CC_BYTES_PER_ENTRY, 0)))
+        goto error;
+
+    printf("cc_fifo framerate=%d/%d\n", framerate->num, framerate->den);
+    /* Based on the target FPS, figure out the expected cc_count and number of
+       608 tuples per packet.  See ANSI/CTA-708-E Sec 4.3.6.1. */
+    for (i = 0; i < (sizeof(cc_lookup_vals) / sizeof(struct cc_lookup)); i++) {
+        if (framerate->num == cc_lookup_vals[i].num &&
+            framerate->den == cc_lookup_vals[i].den) {
+            ccf->expected_cc_count = cc_lookup_vals[i].cc_count;
+            ccf->expected_608 = cc_lookup_vals[i].num_608;
+            break;
+        }
+    }
+
+    if (ccf->expected_608 == 0) {
+        av_log(ccf->log_ctx, AV_LOG_WARNING, "cc_fifo cannot transcode captions fps=%d/%d\n",
+               framerate->num, framerate->den);
+        return NULL;
+    }
+
+    return ccf;
+
+error:
+    av_ccfifo_freep(&ccf);
+    return NULL;
+}
+
+int av_ccfifo_inject(AVCCFifo *ccf, AVFrame *frame)
+{
+    AVFrameSideData *sd;
+    int cc_filled = 0;
+    int i;
+
+    if (!ccf)
+        return 0;
+
+    if (ccf->cc_detected == 0 || ccf->expected_cc_count == 0)
+        return 0;
+
+    sd = av_frame_new_side_data(frame, AV_FRAME_DATA_A53_CC,
+                                ccf->expected_cc_count * CC_BYTES_PER_ENTRY);
+    if (!sd)
+        return 0;
+
+    for (i = 0; i < ccf->expected_608; i++) {
+        if (av_fifo_can_read(ccf->cc_608_fifo) >= CC_BYTES_PER_ENTRY) {
+            av_fifo_read(ccf->cc_608_fifo, &sd->data[cc_filled * CC_BYTES_PER_ENTRY],
+                         CC_BYTES_PER_ENTRY);
+            cc_filled++;
+        } else {
+            break;
+        }
+    }
+
+    /* Insert any available data from the 708 FIFO */
+    while (cc_filled < ccf->expected_cc_count) {
+        if (av_fifo_can_read(ccf->cc_708_fifo) >= CC_BYTES_PER_ENTRY) {
+            av_fifo_read(ccf->cc_708_fifo, &sd->data[cc_filled * CC_BYTES_PER_ENTRY],
+                         CC_BYTES_PER_ENTRY);
+            cc_filled++;
+        } else {
+            break;
+        }
+    }
+
+    /* Insert 708 padding into any remaining fields */
+    while (cc_filled < ccf->expected_cc_count) {
+        sd->data[cc_filled * CC_BYTES_PER_ENTRY]     = 0xfa;
+        sd->data[cc_filled * CC_BYTES_PER_ENTRY + 1] = 0x00;
+        sd->data[cc_filled * CC_BYTES_PER_ENTRY + 2] = 0x00;
+        cc_filled++;
+    }
+    printf("enqueue cc_filled=%d\n", cc_filled);
+    return 0;
+}
+
+int av_ccfifo_extract(AVCCFifo *ccf, AVFrame *frame)
+{
+    int i;
+
+    if (!ccf)
+        return 0;
+
+    /* Read the A53 side data, discard padding, and put 608/708 into
+       queues so we can ensure they get into the output frames at
+       the correct rate... */
+    if (ccf->expected_cc_count > 0) {
+        AVFrameSideData *side_data = av_frame_get_side_data(frame, AV_FRAME_DATA_A53_CC);
+        if (side_data) {
+            uint8_t *cc_bytes = side_data->data;
+            int cc_count = side_data->size / CC_BYTES_PER_ENTRY;
+            ccf->cc_detected = 1;
+
+            for (i = 0; i < cc_count; i++) {
+                /* See ANSI/CTA-708-E Sec 4.3, Table 3 */
+                uint8_t cc_valid = (cc_bytes[CC_BYTES_PER_ENTRY*i] & 0x04) >> 2;
+                uint8_t cc_type = cc_bytes[CC_BYTES_PER_ENTRY*i] & 0x03;
+                if (cc_type == 0x00 || cc_type == 0x01) {
+                    av_fifo_write(ccf->cc_608_fifo, &cc_bytes[CC_BYTES_PER_ENTRY*i],
+                                  CC_BYTES_PER_ENTRY);
+                } else if (cc_valid && (cc_type == 0x02 || cc_type == 0x03)) {
+                    av_fifo_write(ccf->cc_708_fifo, &cc_bytes[CC_BYTES_PER_ENTRY*i],
+                                  CC_BYTES_PER_ENTRY);
+                }
+            }
+            printf("dequeue cc_filled=%d\n", cc_count);
+            /* Remove the side data, as we will re-create it on the
+               output as needed */
+            av_frame_remove_side_data(frame, AV_FRAME_DATA_A53_CC);
+        }
+    }
+    return 0;
+}
diff --git a/libavutil/ccfifo.h b/libavutil/ccfifo.h
new file mode 100644
index 0000000000..ad48c0a39c
--- /dev/null
+++ b/libavutil/ccfifo.h
@@ -0,0 +1,85 @@
+/*
+ * CEA-708 Closed Captioning FIFO
+ * Copyright (c) 2023 LTN Global Communications
+ *
+ * Author: Devin Heitmueller <dheitmueller@ltnglobal.com>
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * CC FIFO Buffer
+ */
+
+#ifndef AVUTIL_CCFIFO_H
+#define AVUTIL_CCFIFO_H
+
+#include "avutil.h"
+#include "frame.h"
+#include "fifo.h"
+
+typedef struct AVCCFifo AVCCFifo;
+
+/**
+ * Allocate an AVCCFifo.
+ *
+ * @param sample_fmt  sample format
+ * @param channels    number of channels
+ * @param nb_samples  initial allocation size, in samples
+ * @return            newly allocated AVCCFifo, or NULL on error
+ */
+AVCCFifo *av_ccfifo_alloc(AVRational *framerate, void *log_ctx);
+
+/**
+ * Free an AVCCFifo
+ *
+ * @param ccf Pointer to the pointer to the AVCCFifo which should be freed
+ * @note `*ptr = NULL` is safe and leads to no action.
+ */
+void av_ccfifo_freep(AVCCFifo **ccf);
+
+
+/**
+ * Read a frame into a CC Fifo
+ *
+ * Extract CC bytes from the AVFrame, insert them into our queue, and
+ * remove the side data from the AVFrame.  The side data is removed
+ * as it will be re-inserted at the appropriate rate later in the
+ * filter.
+ *
+ * @param af          AVCCFifo to write to
+ * @param frame       AVFrame with the video frame to operate on
+ * @return            Zero on success, or negative AVERROR
+ *                    code on failure.
+ */
+int av_ccfifo_extract(AVCCFifo *af, AVFrame *frame);
+
+/**
+ * Insert CC data from the FIFO into an AVFrame (as side data)
+ *
+ * Dequeue the appropriate number of CC tuples based on the
+ * frame rate, and insert them into the AVFrame
+ *
+ * @param af          AVCCFifo to read from
+ * @param frame       AVFrame with the video frame to operate on
+ * @return            Zero on success, or negative AVERROR
+ *                    code on failure.
+ */
+int av_ccfifo_inject(AVCCFifo *af, AVFrame *frame);
+
+#endif /* AVUTIL_CCFIFO_H */
