[FFmpeg-devel,4/6] lavc: Add coded bitstream read/write support for VP9

Submitted by Mark Thompson on April 30, 2018, 11:26 p.m.

Details

Message ID 20180430232605.4846-4-sw@jkqxz.net
State New
Headers show

Commit Message

Mark Thompson April 30, 2018, 11:26 p.m.
---
Main change since last time is including the array subscripts.  Constants are also cleaned up a bit, but stay in the cbs header (vp9.h could probably be taken over for this purpose, but currently it's an unnamespaced header used by the decoder so I haven't touched it).


 configure                            |   2 +
 doc/bitstream_filters.texi           |   2 +-
 libavcodec/Makefile                  |   1 +
 libavcodec/cbs.c                     |   6 +
 libavcodec/cbs.h                     |   1 +
 libavcodec/cbs_internal.h            |   1 +
 libavcodec/cbs_vp9.c                 | 679 +++++++++++++++++++++++++++++++++++
 libavcodec/cbs_vp9.h                 | 201 +++++++++++
 libavcodec/cbs_vp9_syntax_template.c | 390 ++++++++++++++++++++
 9 files changed, 1282 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/cbs_vp9.c
 create mode 100644 libavcodec/cbs_vp9.h
 create mode 100644 libavcodec/cbs_vp9_syntax_template.c

Comments

James Almer May 1, 2018, 5:33 p.m.
On 4/30/2018 8:26 PM, Mark Thompson wrote:
> ---
> Main change since last time is including the array subscripts.  Constants are also cleaned up a bit, but stay in the cbs header (vp9.h could probably be taken over for this purpose, but currently it's an unnamespaced header used by the decoder so I haven't touched it).
> 
> 
>  configure                            |   2 +
>  doc/bitstream_filters.texi           |   2 +-
>  libavcodec/Makefile                  |   1 +
>  libavcodec/cbs.c                     |   6 +
>  libavcodec/cbs.h                     |   1 +
>  libavcodec/cbs_internal.h            |   1 +
>  libavcodec/cbs_vp9.c                 | 679 +++++++++++++++++++++++++++++++++++
>  libavcodec/cbs_vp9.h                 | 201 +++++++++++
>  libavcodec/cbs_vp9_syntax_template.c | 390 ++++++++++++++++++++
>  9 files changed, 1282 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/cbs_vp9.c
>  create mode 100644 libavcodec/cbs_vp9.h
>  create mode 100644 libavcodec/cbs_vp9_syntax_template.c

LGTM. No apparent data copy on any of the read methods which is very
promising for the AV1 implementation.
Only CodedBitstreamType->write_unit() still seems a tad sub-optimal with
the temp write_buffer, especially in this module where unlike
mpeg2/h2645 you memcpy the data twice.

Can't really comment on the actual bitstream parsing code, but
trace_headers_bsf doesn't complain about any of the samples i tried.
Mark Thompson May 2, 2018, 12:53 a.m.
On 01/05/18 18:33, James Almer wrote:
> On 4/30/2018 8:26 PM, Mark Thompson wrote:
>> ---
>> Main change since last time is including the array subscripts.  Constants are also cleaned up a bit, but stay in the cbs header (vp9.h could probably be taken over for this purpose, but currently it's an unnamespaced header used by the decoder so I haven't touched it).
>>
>>
>>  configure                            |   2 +
>>  doc/bitstream_filters.texi           |   2 +-
>>  libavcodec/Makefile                  |   1 +
>>  libavcodec/cbs.c                     |   6 +
>>  libavcodec/cbs.h                     |   1 +
>>  libavcodec/cbs_internal.h            |   1 +
>>  libavcodec/cbs_vp9.c                 | 679 +++++++++++++++++++++++++++++++++++
>>  libavcodec/cbs_vp9.h                 | 201 +++++++++++
>>  libavcodec/cbs_vp9_syntax_template.c | 390 ++++++++++++++++++++
>>  9 files changed, 1282 insertions(+), 1 deletion(-)
>>  create mode 100644 libavcodec/cbs_vp9.c
>>  create mode 100644 libavcodec/cbs_vp9.h
>>  create mode 100644 libavcodec/cbs_vp9_syntax_template.c
> 
> LGTM. No apparent data copy on any of the read methods which is very
> promising for the AV1 implementation.
> Only CodedBitstreamType->write_unit() still seems a tad sub-optimal with
> the temp write_buffer, especially in this module where unlike
> mpeg2/h2645 you memcpy the data twice.

It's copied twice in MPEG-2/H.26[45] as well - once from the write buffer to the unit data buffers, then a second time to combine them into the fragment data buffer (which also adds the emulation prevention in the H.26[45] case, so not really a direct copy).

It's not very easy to do better - a possible way would be to allow the write buffer to contain multiple units and make the reference point at the write buffer itself, then we could avoid the first copy to a per-unit buffer.  That requires somewhat more care in writing, though, because some special cases become nastier to handle (re-copying previous units on overflow; allocating a new write buffer if unit data needs to live beyond the reassembly).

In theory that can also elide the second copy in VP9 (because the frames are just concatenated in the write buffer, and the superframe header placed at the end), but in practice that isn't necessarily a benefit because you end up with even more allocation if the following component ever needs to hold more than one output packet at once.

So, I'm not intending to do anything with this for now, but if you want to look at it (or have any other ideas you think I should pursue) then please do.

Thanks,

- Mark
James Almer May 2, 2018, 1:21 a.m.
On 5/1/2018 9:53 PM, Mark Thompson wrote:
> On 01/05/18 18:33, James Almer wrote:
>> On 4/30/2018 8:26 PM, Mark Thompson wrote:
>>> ---
>>> Main change since last time is including the array subscripts.  Constants are also cleaned up a bit, but stay in the cbs header (vp9.h could probably be taken over for this purpose, but currently it's an unnamespaced header used by the decoder so I haven't touched it).
>>>
>>>
>>>  configure                            |   2 +
>>>  doc/bitstream_filters.texi           |   2 +-
>>>  libavcodec/Makefile                  |   1 +
>>>  libavcodec/cbs.c                     |   6 +
>>>  libavcodec/cbs.h                     |   1 +
>>>  libavcodec/cbs_internal.h            |   1 +
>>>  libavcodec/cbs_vp9.c                 | 679 +++++++++++++++++++++++++++++++++++
>>>  libavcodec/cbs_vp9.h                 | 201 +++++++++++
>>>  libavcodec/cbs_vp9_syntax_template.c | 390 ++++++++++++++++++++
>>>  9 files changed, 1282 insertions(+), 1 deletion(-)
>>>  create mode 100644 libavcodec/cbs_vp9.c
>>>  create mode 100644 libavcodec/cbs_vp9.h
>>>  create mode 100644 libavcodec/cbs_vp9_syntax_template.c
>>
>> LGTM. No apparent data copy on any of the read methods which is very
>> promising for the AV1 implementation.
>> Only CodedBitstreamType->write_unit() still seems a tad sub-optimal with
>> the temp write_buffer, especially in this module where unlike
>> mpeg2/h2645 you memcpy the data twice.
> 
> It's copied twice in MPEG-2/H.26[45] as well - once from the write buffer to the unit data buffers, then a second time to combine them into the fragment data buffer (which also adds the emulation prevention in the H.26[45] case, so not really a direct copy).

It's probably impossible to avoid the memcpys when assembling a fragment
out of each unit data for most codecs, so we should focus on optimizing
write_unit().

> 
> It's not very easy to do better - a possible way would be to allow the write buffer to contain multiple units and make the reference point at the write buffer itself, then we could avoid the first copy to a per-unit buffer.  That requires somewhat more care in writing, though, because some special cases become nastier to handle (re-copying previous units on overflow; allocating a new write buffer if unit data needs to live beyond the reassembly).
> 
> In theory that can also elide the second copy in VP9 (because the frames are just concatenated in the write buffer, and the superframe header placed at the end), but in practice that isn't necessarily a benefit because you end up with even more allocation if the following component ever needs to hold more than one output packet at once.

One option in write_unit() could be making the unit take ownership of
the buffer as filled by the codec specific bitstream functions (what's
currently priv->write_buffer) instead of allocating a new one within
ff_cbs_alloc_unit_data and then copying data to it.
This would make priv->write_buffer something local to write_unit(),
allocated anew once per call, and then wrapped as the unit's buffer.

This could be done with a new ff_cbs_wrap_unit_data() function or similar.

> 
> So, I'm not intending to do anything with this for now, but if you want to look at it (or have any other ideas you think I should pursue) then please do.
> 
> Thanks,
> 
> - Mark
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>

Patch hide | download patch | download mbox

diff --git a/configure b/configure
index 0f002dc6eb..57748e1424 100755
--- a/configure
+++ b/configure
@@ -2236,6 +2236,7 @@  CONFIG_EXTRA="
     cbs_h264
     cbs_h265
     cbs_mpeg2
+    cbs_vp9
     dirac_parse
     dvprofile
     exif
@@ -2497,6 +2498,7 @@  threads_if_any="$THREADS_LIST"
 cbs_h264_select="cbs golomb"
 cbs_h265_select="cbs golomb"
 cbs_mpeg2_select="cbs"
+cbs_vp9_select="cbs"
 dct_select="rdft"
 dirac_parse_select="golomb"
 error_resilience_select="me_cmp"
diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index 7322af6550..7f98cb714f 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -505,7 +505,7 @@  Log trace output containing all syntax elements in the coded stream
 headers (everything above the level of individual coded blocks).
 This can be useful for debugging low-level stream issues.
 
-Supports H.264, H.265 and MPEG-2.
+Supports H.264, H.265, MPEG-2 and VP9.
 
 @section vp9_superframe
 
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index d727b218dc..15e8c87385 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -65,6 +65,7 @@  OBJS-$(CONFIG_CBS)                     += cbs.o
 OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o h2645_parse.o
 OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o h2645_parse.o
 OBJS-$(CONFIG_CBS_MPEG2)               += cbs_mpeg2.o
+OBJS-$(CONFIG_CBS_VP9)                 += cbs_vp9.o
 OBJS-$(CONFIG_CRYSTALHD)               += crystalhd.o
 OBJS-$(CONFIG_DCT)                     += dct.o dct32_fixed.o dct32_float.o
 OBJS-$(CONFIG_ERROR_RESILIENCE)        += error_resilience.o
diff --git a/libavcodec/cbs.c b/libavcodec/cbs.c
index 7091c9abf5..2c0198f799 100644
--- a/libavcodec/cbs.c
+++ b/libavcodec/cbs.c
@@ -38,6 +38,9 @@  static const CodedBitstreamType *cbs_type_table[] = {
 #if CONFIG_CBS_MPEG2
     &ff_cbs_type_mpeg2,
 #endif
+#if CONFIG_CBS_VP9
+    &ff_cbs_type_vp9,
+#endif
 };
 
 const enum AVCodecID ff_cbs_all_codec_ids[] = {
@@ -49,6 +52,9 @@  const enum AVCodecID ff_cbs_all_codec_ids[] = {
 #endif
 #if CONFIG_CBS_MPEG2
     AV_CODEC_ID_MPEG2VIDEO,
+#endif
+#if CONFIG_CBS_VP9
+    AV_CODEC_ID_VP9,
 #endif
     AV_CODEC_ID_NONE
 };
diff --git a/libavcodec/cbs.h b/libavcodec/cbs.h
index 487358afaf..53ac360bb1 100644
--- a/libavcodec/cbs.h
+++ b/libavcodec/cbs.h
@@ -48,6 +48,7 @@  struct CodedBitstreamType;
  * H.264 / AVC: nal_unit_type
  * H.265 / HEVC: nal_unit_type
  * MPEG-2: start code value (without prefix)
+ * VP9: unused, set to zero (every unit is a frame)
  */
 typedef uint32_t CodedBitstreamUnitType;
 
diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h
index 3280e7602d..172b8a2515 100644
--- a/libavcodec/cbs_internal.h
+++ b/libavcodec/cbs_internal.h
@@ -89,6 +89,7 @@  int ff_cbs_write_unsigned(CodedBitstreamContext *ctx, PutBitContext *pbc,
 extern const CodedBitstreamType ff_cbs_type_h264;
 extern const CodedBitstreamType ff_cbs_type_h265;
 extern const CodedBitstreamType ff_cbs_type_mpeg2;
+extern const CodedBitstreamType ff_cbs_type_vp9;
 
 
 #endif /* AVCODEC_CBS_INTERNAL_H */
diff --git a/libavcodec/cbs_vp9.c b/libavcodec/cbs_vp9.c
new file mode 100644
index 0000000000..7498be4b73
--- /dev/null
+++ b/libavcodec/cbs_vp9.c
@@ -0,0 +1,679 @@ 
+/*
+ * 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/avassert.h"
+
+#include "cbs.h"
+#include "cbs_internal.h"
+#include "cbs_vp9.h"
+#include "internal.h"
+
+
+static int cbs_vp9_read_s(CodedBitstreamContext *ctx, GetBitContext *gbc,
+                          int width, const char *name,
+                          const int *subscripts, int32_t *write_to)
+{
+    uint32_t magnitude;
+    int position, sign;
+    int32_t value;
+
+    if (ctx->trace_enable)
+        position = get_bits_count(gbc);
+
+    if (get_bits_left(gbc) < width + 1) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid signed value at "
+               "%s: bitstream ended.\n", name);
+        return AVERROR_INVALIDDATA;
+    }
+
+    magnitude = get_bits(gbc, width);
+    sign      = get_bits1(gbc);
+    value     = sign ? -(int32_t)magnitude : magnitude;
+
+    if (ctx->trace_enable) {
+        char bits[33];
+        int i;
+        for (i = 0; i < width; i++)
+            bits[i] = magnitude >> (width - i - 1) & 1 ? '1' : '0';
+        bits[i] = sign ? '1' : '0';
+        bits[i + 1] = 0;
+
+        ff_cbs_trace_syntax_element(ctx, position, name, subscripts,
+                                    bits, value);
+    }
+
+    *write_to = value;
+    return 0;
+}
+
+static int cbs_vp9_write_s(CodedBitstreamContext *ctx, PutBitContext *pbc,
+                           int width, const char *name,
+                           const int *subscripts, int32_t value)
+{
+    uint32_t magnitude;
+    int sign;
+
+    if (put_bits_left(pbc) < width + 1)
+        return AVERROR(ENOSPC);
+
+    sign      = value < 0;
+    magnitude = sign ? -value : value;
+
+    if (ctx->trace_enable) {
+        char bits[33];
+        int i;
+        for (i = 0; i < width; i++)
+            bits[i] = magnitude >> (width - i - 1) & 1 ? '1' : '0';
+        bits[i] = sign ? '1' : '0';
+        bits[i + 1] = 0;
+
+        ff_cbs_trace_syntax_element(ctx, put_bits_count(pbc),
+                                    name, subscripts, bits, value);
+    }
+
+    put_bits(pbc, width, magnitude);
+    put_bits(pbc, 1, sign);
+
+    return 0;
+}
+
+static int cbs_vp9_read_increment(CodedBitstreamContext *ctx, GetBitContext *gbc,
+                                  uint32_t range_min, uint32_t range_max,
+                                  const char *name, uint32_t *write_to)
+{
+    uint32_t value;
+    int position, i;
+    char bits[8];
+
+    av_assert0(range_min <= range_max && range_max - range_min < sizeof(bits) - 1);
+    if (ctx->trace_enable)
+        position = get_bits_count(gbc);
+
+    for (i = 0, value = range_min; value < range_max;) {
+        if (get_bits_left(gbc) < 1) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid increment value at "
+                   "%s: bitstream ended.\n", name);
+            return AVERROR_INVALIDDATA;
+        }
+        if (get_bits1(gbc)) {
+            bits[i++] = '1';
+            ++value;
+        } else {
+            bits[i++] = '0';
+            break;
+        }
+    }
+
+    if (ctx->trace_enable) {
+        bits[i] = 0;
+        ff_cbs_trace_syntax_element(ctx, position, name, NULL, bits, value);
+    }
+
+    *write_to = value;
+    return 0;
+}
+
+static int cbs_vp9_write_increment(CodedBitstreamContext *ctx, PutBitContext *pbc,
+                                   uint32_t range_min, uint32_t range_max,
+                                   const char *name, uint32_t value)
+{
+    int len;
+
+    av_assert0(range_min <= range_max && range_max - range_min < 8);
+    if (value < range_min || value > range_max) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "%s out of range: "
+               "%"PRIu32", but must be in [%"PRIu32",%"PRIu32"].\n",
+               name, value, range_min, range_max);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (value == range_max)
+        len = range_max - range_min;
+    else
+        len = value - range_min + 1;
+    if (put_bits_left(pbc) < len)
+        return AVERROR(ENOSPC);
+
+    if (ctx->trace_enable) {
+        char bits[8];
+        int i;
+        for (i = 0; i < len; i++) {
+            if (range_min + i == value)
+                bits[i] = '0';
+            else
+                bits[i] = '1';
+        }
+        bits[i] = 0;
+        ff_cbs_trace_syntax_element(ctx, put_bits_count(pbc),
+                                    name, NULL, bits, value);
+    }
+
+    if (len > 0)
+        put_bits(pbc, len, (1 << len) - 1 - (value != range_max));
+
+    return 0;
+}
+
+static int cbs_vp9_read_le(CodedBitstreamContext *ctx, GetBitContext *gbc,
+                           int width, const char *name,
+                           const int *subscripts, uint32_t *write_to)
+{
+    uint32_t value;
+    int position, b;
+
+    av_assert0(width % 8 == 0);
+
+    if (ctx->trace_enable)
+        position = get_bits_count(gbc);
+
+    if (get_bits_left(gbc) < width) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid le value at "
+               "%s: bitstream ended.\n", name);
+        return AVERROR_INVALIDDATA;
+    }
+
+    value = 0;
+    for (b = 0; b < width; b += 8)
+        value |= get_bits(gbc, 8) << b;
+
+    if (ctx->trace_enable) {
+        char bits[33];
+        int i;
+        for (b = 0; b < width; b += 8)
+            for (i = 0; i < 8; i++)
+                bits[b + i] = value >> (b + i) & 1 ? '1' : '0';
+        bits[b] = 0;
+
+        ff_cbs_trace_syntax_element(ctx, position, name, subscripts,
+                                    bits, value);
+    }
+
+    *write_to = value;
+    return 0;
+}
+
+static int cbs_vp9_write_le(CodedBitstreamContext *ctx, PutBitContext *pbc,
+                            int width, const char *name,
+                            const int *subscripts, uint32_t value)
+{
+    int b;
+
+    av_assert0(width % 8 == 0);
+
+    if (put_bits_left(pbc) < width)
+        return AVERROR(ENOSPC);
+
+    if (ctx->trace_enable) {
+        char bits[33];
+        int i;
+        for (b = 0; b < width; b += 8)
+            for (i = 0; i < 8; i++)
+                bits[b + i] = value >> (b + i) & 1 ? '1' : '0';
+        bits[b] = 0;
+
+        ff_cbs_trace_syntax_element(ctx, put_bits_count(pbc),
+                                    name, subscripts, bits, value);
+    }
+
+    for (b = 0; b < width; b += 8)
+        put_bits(pbc, 8, value >> b & 0xff);
+
+    return 0;
+}
+
+#define HEADER(name) do { \
+        ff_cbs_trace_header(ctx, name); \
+    } while (0)
+
+#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_VP9(rw, name) FUNC_NAME(rw, vp9, name)
+#define FUNC(name) FUNC_VP9(READWRITE, name)
+
+#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]){ subs, __VA_ARGS__ }) : NULL)
+
+#define f(width, name) \
+        xf(width, name, current->name, 0)
+#define s(width, name) \
+        xs(width, name, current->name, 0)
+#define fs(width, name, subs, ...) \
+        xf(width, name, current->name, subs, __VA_ARGS__)
+#define ss(width, name, subs, ...) \
+        xs(width, name, current->name, subs, __VA_ARGS__)
+
+
+#define READ
+#define READWRITE read
+#define RWContext GetBitContext
+
+#define xf(width, name, var, subs, ...) do { \
+        uint32_t value = 0; \
+        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
+                                   SUBSCRIPTS(subs, __VA_ARGS__), \
+                                   &value, 0, (1 << width) - 1)); \
+        var = value; \
+    } while (0)
+#define xs(width, name, var, subs, ...) do { \
+        int32_t value = 0; \
+        CHECK(cbs_vp9_read_s(ctx, rw, width, #name, \
+                             SUBSCRIPTS(subs, __VA_ARGS__), &value)); \
+        var = value; \
+    } while (0)
+
+
+#define increment(name, min, max) do { \
+        uint32_t value = 0; \
+        CHECK(cbs_vp9_read_increment(ctx, rw, min, max, #name, &value)); \
+        current->name = value; \
+    } while (0)
+
+#define fle(width, name, subs, ...) do { \
+        CHECK(cbs_vp9_read_le(ctx, rw, width, #name, \
+                              SUBSCRIPTS(subs, __VA_ARGS__), &current->name)); \
+    } while (0)
+
+#define delta_q(name) do { \
+        uint8_t delta_coded; \
+        int8_t delta_q; \
+        xf(1, name.delta_coded, delta_coded, 0); \
+        if (delta_coded) \
+            xs(4, name.delta_q, delta_q, 0); \
+        else \
+            delta_q = 0; \
+        current->name = delta_q; \
+    } while (0)
+
+#define prob(name, subs, ...) do { \
+        uint8_t prob_coded; \
+        int8_t prob; \
+        xf(1, name.prob_coded, prob_coded, subs, __VA_ARGS__); \
+        if (prob_coded) \
+            xf(8, name.prob, prob, subs, __VA_ARGS__); \
+        else \
+            prob = 255; \
+        current->name = prob; \
+    } while (0)
+
+#define infer(name, value) do { \
+        current->name = value; \
+    } while (0)
+
+#define byte_alignment(rw) (get_bits_count(rw) % 8)
+
+#include "cbs_vp9_syntax_template.c"
+
+#undef READ
+#undef READWRITE
+#undef RWContext
+#undef xf
+#undef xs
+#undef increment
+#undef fle
+#undef delta_q
+#undef prob
+#undef infer
+#undef byte_alignment
+
+
+#define WRITE
+#define READWRITE write
+#define RWContext PutBitContext
+
+#define xf(width, name, var, subs, ...) do { \
+        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
+                                    SUBSCRIPTS(subs, __VA_ARGS__), \
+                                    var, 0, (1 << width) - 1)); \
+    } while (0)
+#define xs(width, name, var, subs, ...) do { \
+        CHECK(cbs_vp9_write_s(ctx, rw, width, #name, \
+                              SUBSCRIPTS(subs, __VA_ARGS__), var)); \
+    } while (0)
+
+#define increment(name, min, max) do { \
+        CHECK(cbs_vp9_write_increment(ctx, rw, min, max, #name, current->name)); \
+    } while (0)
+
+#define fle(width, name, subs, ...) do { \
+        CHECK(cbs_vp9_write_le(ctx, rw, width, #name, \
+                               SUBSCRIPTS(subs, __VA_ARGS__), current->name)); \
+    } while (0)
+
+#define delta_q(name) do { \
+        xf(1, name.delta_coded, !!current->name, 0); \
+        if (current->name) \
+            xs(4, name.delta_q, current->name, 0); \
+    } while (0)
+
+#define prob(name, subs, ...) do { \
+        xf(1, name.prob_coded, current->name != 255, subs, __VA_ARGS__); \
+        if (current->name != 255) \
+            xf(8, name.prob, current->name, subs, __VA_ARGS__); \
+    } while (0)
+
+#define infer(name, value) do { \
+        if (current->name != (value)) { \
+            av_log(ctx->log_ctx, AV_LOG_WARNING, "Warning: " \
+                   "%s does not match inferred value: " \
+                   "%"PRId64", but should be %"PRId64".\n", \
+                   #name, (int64_t)current->name, (int64_t)(value)); \
+        } \
+    } while (0)
+
+#define byte_alignment(rw) (put_bits_count(rw) % 8)
+
+#include "cbs_vp9_syntax_template.c"
+
+#undef READ
+#undef READWRITE
+#undef RWContext
+#undef xf
+#undef xs
+#undef increment
+#undef fle
+#undef delta_q
+#undef prob
+#undef infer
+#undef byte_alignment
+
+
+static int cbs_vp9_split_fragment(CodedBitstreamContext *ctx,
+                                  CodedBitstreamFragment *frag,
+                                  int header)
+{
+    uint8_t superframe_header;
+    int err;
+
+    // Last byte in the packet.
+    superframe_header = frag->data[frag->data_size - 1];
+
+    if ((superframe_header & 0xe0) == 0xc0) {
+        VP9RawSuperframeIndex sfi;
+        GetBitContext gbc;
+        size_t index_size, pos;
+        int i;
+
+        index_size = 2 + (((superframe_header & 0x18) >> 3) + 1) *
+                          ((superframe_header & 0x07) + 1);
+
+        err = init_get_bits(&gbc, frag->data + frag->data_size - index_size,
+                            8 * index_size);
+        if (err < 0)
+            return err;
+
+        err = cbs_vp9_read_superframe_index(ctx, &gbc, &sfi);
+        if (err < 0)
+            return err;
+
+        pos = 0;
+        for (i = 0; i <= sfi.frames_in_superframe_minus_1; i++) {
+            if (pos + sfi.frame_sizes[i] + index_size > frag->data_size) {
+                av_log(ctx->log_ctx, AV_LOG_ERROR, "Frame %d too large "
+                       "in superframe: %"PRIu32" bytes.\n",
+                       i, sfi.frame_sizes[i]);
+                return AVERROR_INVALIDDATA;
+            }
+
+            err = ff_cbs_insert_unit_data(ctx, frag, -1, 0,
+                                          frag->data + pos,
+                                          sfi.frame_sizes[i],
+                                          frag->data_ref);
+            if (err < 0)
+                return err;
+
+            pos += sfi.frame_sizes[i];
+        }
+        if (pos + index_size != frag->data_size) {
+            av_log(ctx->log_ctx, AV_LOG_WARNING, "Extra padding at "
+                   "end of superframe: %zu bytes.\n",
+                   frag->data_size - (pos + index_size));
+        }
+
+        return 0;
+
+    } else {
+        err = ff_cbs_insert_unit_data(ctx, frag, -1, 0,
+                                      frag->data, frag->data_size,
+                                      frag->data_ref);
+        if (err < 0)
+            return err;
+    }
+
+    return 0;
+}
+
+static void cbs_vp9_free_frame(void *unit, uint8_t *content)
+{
+    VP9RawFrame *frame = (VP9RawFrame*)content;
+    av_buffer_unref(&frame->data_ref);
+    av_freep(&frame);
+}
+
+static int cbs_vp9_read_unit(CodedBitstreamContext *ctx,
+                             CodedBitstreamUnit *unit)
+{
+    VP9RawFrame *frame;
+    GetBitContext gbc;
+    int err, pos;
+
+    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
+    if (err < 0)
+        return err;
+
+    err = ff_cbs_alloc_unit_content(ctx, unit, sizeof(*frame),
+                                    &cbs_vp9_free_frame);
+    if (err < 0)
+        return err;
+    frame = unit->content;
+
+    err = cbs_vp9_read_frame(ctx, &gbc, frame);
+    if (err < 0)
+        return err;
+
+    pos = get_bits_count(&gbc);
+    av_assert0(pos % 8 == 0);
+    pos /= 8;
+    av_assert0(pos <= unit->data_size);
+
+    if (pos == unit->data_size) {
+        // No data (e.g. a show-existing-frame frame).
+    } else {
+        frame->data_ref = av_buffer_ref(unit->data_ref);
+        if (!frame->data_ref)
+            return AVERROR(ENOMEM);
+
+        frame->data      = unit->data      + pos;
+        frame->data_size = unit->data_size - pos;
+    }
+
+    return 0;
+}
+
+static int cbs_vp9_write_unit(CodedBitstreamContext *ctx,
+                              CodedBitstreamUnit *unit)
+{
+    CodedBitstreamVP9Context *priv = ctx->priv_data;
+    VP9RawFrame *frame = unit->content;
+    PutBitContext pbc;
+    int err;
+
+    if (!priv->write_buffer) {
+        // Initial write buffer size is 1MB.
+        priv->write_buffer_size = 1024 * 1024;
+
+    reallocate_and_try_again:
+        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);
+        if (err < 0) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Unable to allocate a "
+                   "sufficiently large write buffer (last attempt "
+                   "%zu bytes).\n", priv->write_buffer_size);
+            return err;
+        }
+    }
+
+    init_put_bits(&pbc, priv->write_buffer, priv->write_buffer_size);
+
+    err = cbs_vp9_write_frame(ctx, &pbc, frame);
+    if (err == AVERROR(ENOSPC)) {
+        priv->write_buffer_size *= 2;
+        goto reallocate_and_try_again;
+    }
+    if (err < 0)
+        return err;
+
+    // Frame must be byte-aligned.
+    av_assert0(put_bits_count(&pbc) % 8 == 0);
+
+    unit->data_size        = put_bits_count(&pbc) / 8;
+    unit->data_bit_padding = 0;
+    flush_put_bits(&pbc);
+
+    if (frame->data) {
+        if (unit->data_size + frame->data_size >
+            priv->write_buffer_size) {
+            priv->write_buffer_size *= 2;
+            goto reallocate_and_try_again;
+        }
+
+        memcpy(priv->write_buffer + unit->data_size,
+               frame->data, frame->data_size);
+        unit->data_size += frame->data_size;
+    }
+
+    err = ff_cbs_alloc_unit_data(ctx, unit, unit->data_size);
+    if (err < 0)
+        return err;
+
+    memcpy(unit->data, priv->write_buffer, unit->data_size);
+
+    return 0;
+}
+
+static int cbs_vp9_assemble_fragment(CodedBitstreamContext *ctx,
+                                     CodedBitstreamFragment *frag)
+{
+    int err;
+
+    if (frag->nb_units == 1) {
+        // Output is just the content of the single frame.
+
+        CodedBitstreamUnit *frame = &frag->units[0];
+
+        frag->data_ref = av_buffer_ref(frame->data_ref);
+        if (!frag->data_ref)
+            return AVERROR(ENOMEM);
+
+        frag->data      = frame->data;
+        frag->data_size = frame->data_size;
+
+    } else {
+        // Build superframe out of frames.
+
+        VP9RawSuperframeIndex sfi;
+        PutBitContext pbc;
+        AVBufferRef *ref;
+        uint8_t *data;
+        size_t size, max, pos;
+        int i, size_len;
+
+        if (frag->nb_units > 8) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Too many frames to "
+                   "make superframe: %d.\n", frag->nb_units);
+            return AVERROR(EINVAL);
+        }
+
+        max = 0;
+        for (i = 0; i < frag->nb_units; i++)
+            if (max < frag->units[i].data_size)
+                max = frag->units[i].data_size;
+
+        if (max < 2)
+            size_len = 1;
+        else
+            size_len = av_log2(max) / 8 + 1;
+        av_assert0(size_len <= 4);
+
+        sfi.superframe_marker            = VP9_SUPERFRAME_MARKER;
+        sfi.bytes_per_framesize_minus_1  = size_len - 1;
+        sfi.frames_in_superframe_minus_1 = frag->nb_units - 1;
+
+        size = 2;
+        for (i = 0; i < frag->nb_units; i++) {
+            size += size_len + frag->units[i].data_size;
+            sfi.frame_sizes[i] = frag->units[i].data_size;
+        }
+
+        ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
+        if (!ref)
+            return AVERROR(ENOMEM);
+        data = ref->data;
+        memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
+        pos = 0;
+        for (i = 0; i < frag->nb_units; i++) {
+            av_assert0(size - pos > frag->units[i].data_size);
+            memcpy(data + pos, frag->units[i].data,
+                   frag->units[i].data_size);
+            pos += frag->units[i].data_size;
+        }
+        av_assert0(size - pos == 2 + frag->nb_units * size_len);
+
+        init_put_bits(&pbc, data + pos, size - pos);
+
+        err = cbs_vp9_write_superframe_index(ctx, &pbc, &sfi);
+        if (err < 0) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Failed to write "
+                   "superframe index.\n");
+            av_buffer_unref(&ref);
+            return err;
+        }
+
+        av_assert0(put_bits_left(&pbc) == 0);
+        flush_put_bits(&pbc);
+
+        frag->data_ref  = ref;
+        frag->data      = data;
+        frag->data_size = size;
+    }
+
+    return 0;
+}
+
+static void cbs_vp9_close(CodedBitstreamContext *ctx)
+{
+    CodedBitstreamVP9Context *priv = ctx->priv_data;
+
+    av_freep(&priv->write_buffer);
+}
+
+const CodedBitstreamType ff_cbs_type_vp9 = {
+    .codec_id          = AV_CODEC_ID_VP9,
+
+    .priv_data_size    = sizeof(CodedBitstreamVP9Context),
+
+    .split_fragment    = &cbs_vp9_split_fragment,
+    .read_unit         = &cbs_vp9_read_unit,
+    .write_unit        = &cbs_vp9_write_unit,
+    .assemble_fragment = &cbs_vp9_assemble_fragment,
+
+    .close             = &cbs_vp9_close,
+};
diff --git a/libavcodec/cbs_vp9.h b/libavcodec/cbs_vp9.h
new file mode 100644
index 0000000000..5b99c90c2e
--- /dev/null
+++ b/libavcodec/cbs_vp9.h
@@ -0,0 +1,201 @@ 
+/*
+ * 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_VP9_H
+#define AVCODEC_CBS_VP9_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cbs.h"
+
+
+// Miscellaneous constants (section 3).
+enum {
+    VP9_REFS_PER_FRAME = 3,
+
+    VP9_MIN_TILE_WIDTH_B64 = 4,
+    VP9_MAX_TILE_WIDTH_B64 = 64,
+
+    VP9_NUM_REF_FRAMES = 8,
+    VP9_MAX_REF_FRAMES = 4,
+
+    VP9_MAX_SEGMENTS = 8,
+    VP9_SEG_LVL_MAX  = 4,
+};
+
+// Frame types (section 7.2).
+enum {
+    VP9_KEY_FRAME     = 0,
+    VP9_NON_KEY_FRAME = 1,
+};
+
+// Frame sync bytes (section 7.2.1).
+enum {
+    VP9_FRAME_SYNC_0 = 0x49,
+    VP9_FRAME_SYNC_1 = 0x83,
+    VP9_FRAME_SYNC_2 = 0x42,
+};
+
+// Color space values (section 7.2.2).
+enum {
+    VP9_CS_UNKNOWN   = 0,
+    VP9_CS_BT_601    = 1,
+    VP9_CS_BT_709    = 2,
+    VP9_CS_SMPTE_170 = 3,
+    VP9_CS_SMPTE_240 = 4,
+    VP9_CS_BT_2020   = 5,
+    VP9_CS_RESERVED  = 6,
+    VP9_CS_RGB       = 7,
+};
+
+// Reference frame types (section 7.4.12).
+enum {
+    VP9_INTRA_FRAME  = 0,
+    VP9_LAST_FRAME   = 1,
+    VP9_GOLDEN_FRAME = 2,
+    VP9_ALTREF_FRAME = 3,
+};
+
+// Superframe properties (section B.3).
+enum {
+    VP9_MAX_FRAMES_IN_SUPERFRAME = 8,
+
+    VP9_SUPERFRAME_MARKER = 6,
+};
+
+
+typedef struct VP9RawFrameHeader {
+    uint8_t frame_marker;
+    uint8_t profile_low_bit;
+    uint8_t profile_high_bit;
+    uint8_t profile_reserved_zero;
+
+    uint8_t show_existing_frame;
+    uint8_t frame_to_show_map_idx;
+
+    uint8_t frame_type;
+    uint8_t show_frame;
+    uint8_t error_resilient_mode;
+
+    // Color config.
+    uint8_t ten_or_twelve_bit;
+    uint8_t color_space;
+    uint8_t color_range;
+    uint8_t subsampling_x;
+    uint8_t subsampling_y;
+    uint8_t color_config_reserved_zero;
+
+    uint8_t refresh_frame_flags;
+
+    uint8_t intra_only;
+    uint8_t reset_frame_context;
+
+    uint8_t ref_frame_idx[VP9_REFS_PER_FRAME];
+    uint8_t ref_frame_sign_bias[VP9_MAX_REF_FRAMES];
+
+    uint8_t allow_high_precision_mv;
+
+    uint8_t refresh_frame_context;
+    uint8_t frame_parallel_decoding_mode;
+
+    uint8_t frame_context_idx;
+
+    // Frame/render size.
+    uint8_t found_ref[VP9_REFS_PER_FRAME];
+    uint16_t frame_width_minus_1;
+    uint16_t frame_height_minus_1;
+    uint8_t render_and_frame_size_different;
+    uint16_t render_width_minus_1;
+    uint16_t render_height_minus_1;
+
+    // Interpolation filter.
+    uint8_t is_filter_switchable;
+    uint8_t raw_interpolation_filter_type;
+
+    // Loop filter params.
+    uint8_t loop_filter_level;
+    uint8_t loop_filter_sharpness;
+    uint8_t loop_filter_delta_enabled;
+    uint8_t loop_filter_delta_update;
+    uint8_t update_ref_delta[VP9_MAX_REF_FRAMES];
+    int8_t loop_filter_ref_deltas[VP9_MAX_REF_FRAMES];
+    uint8_t update_mode_delta[2];
+    int8_t loop_filter_mode_deltas[2];
+
+    // Quantization params.
+    uint8_t base_q_idx;
+    int8_t delta_q_y_dc;
+    int8_t delta_q_uv_dc;
+    int8_t delta_q_uv_ac;
+
+    // Segmentation params.
+    uint8_t segmentation_enabled;
+    uint8_t segmentation_update_map;
+    uint8_t segmentation_tree_probs[7];
+    uint8_t segmentation_temporal_update;
+    uint8_t segmentation_pred_prob[3];
+    uint8_t segmentation_update_data;
+    uint8_t segmentation_abs_or_delta_update;
+    uint8_t feature_enabled[VP9_MAX_SEGMENTS][VP9_SEG_LVL_MAX];
+    uint8_t feature_value[VP9_MAX_SEGMENTS][VP9_SEG_LVL_MAX];
+    uint8_t feature_sign[VP9_MAX_SEGMENTS][VP9_SEG_LVL_MAX];
+
+    // Tile info.
+    uint8_t tile_cols_log2;
+    uint8_t tile_rows_log2;
+
+    uint16_t header_size_in_bytes;
+} VP9RawFrameHeader;
+
+typedef struct VP9RawFrame {
+    VP9RawFrameHeader header;
+
+    uint8_t     *data;
+    size_t       data_size;
+    AVBufferRef *data_ref;
+} VP9RawFrame;
+
+typedef struct VP9RawSuperframeIndex {
+    uint8_t superframe_marker;
+    uint8_t bytes_per_framesize_minus_1;
+    uint8_t frames_in_superframe_minus_1;
+    uint32_t frame_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME];
+} VP9RawSuperframeIndex;
+
+typedef struct VP9RawSuperframe {
+    VP9RawFrame frames[VP9_MAX_FRAMES_IN_SUPERFRAME];
+    VP9RawSuperframeIndex index;
+} VP9RawSuperframe;
+
+
+typedef struct CodedBitstreamVP9Context {
+    // Frame dimensions in 8x8 mode info blocks.
+    uint16_t mi_cols;
+    uint16_t mi_rows;
+    // Frame dimensions in 64x64 superblocks.
+    uint16_t sb64_cols;
+    uint16_t sb64_rows;
+
+    // Write buffer.
+    uint8_t *write_buffer;
+    size_t write_buffer_size;
+} CodedBitstreamVP9Context;
+
+
+#endif /* AVCODEC_CBS_VP9_H */
diff --git a/libavcodec/cbs_vp9_syntax_template.c b/libavcodec/cbs_vp9_syntax_template.c
new file mode 100644
index 0000000000..0db0f52a6d
--- /dev/null
+++ b/libavcodec/cbs_vp9_syntax_template.c
@@ -0,0 +1,390 @@ 
+/*
+ * 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(frame_sync_code)(CodedBitstreamContext *ctx, RWContext *rw,
+                                 VP9RawFrameHeader *current)
+{
+    uint8_t frame_sync_byte_0 = VP9_FRAME_SYNC_0;
+    uint8_t frame_sync_byte_1 = VP9_FRAME_SYNC_1;
+    uint8_t frame_sync_byte_2 = VP9_FRAME_SYNC_2;
+    int err;
+
+    xf(8, frame_sync_byte_0, frame_sync_byte_0, 0);
+    xf(8, frame_sync_byte_1, frame_sync_byte_1, 0);
+    xf(8, frame_sync_byte_2, frame_sync_byte_2, 0);
+
+    if (frame_sync_byte_0 != VP9_FRAME_SYNC_0 ||
+        frame_sync_byte_1 != VP9_FRAME_SYNC_1 ||
+        frame_sync_byte_2 != VP9_FRAME_SYNC_2) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid frame sync code: "
+               "%02x %02x %02x.\n", frame_sync_byte_0,
+               frame_sync_byte_1, frame_sync_byte_2);
+        return AVERROR_INVALIDDATA;
+    }
+
+    return 0;
+}
+
+static int FUNC(color_config)(CodedBitstreamContext *ctx, RWContext *rw,
+                              VP9RawFrameHeader *current, int profile)
+{
+    int err;
+
+    if (profile >= 2)
+        f(1, ten_or_twelve_bit);
+
+    f(3, color_space);
+
+    if (current->color_space != VP9_CS_RGB) {
+        f(1, color_range);
+        if (profile == 1 || profile == 3) {
+            f(1, subsampling_x);
+            f(1, subsampling_y);
+            f(1, color_config_reserved_zero);
+        } else {
+            infer(subsampling_x, 1);
+            infer(subsampling_y, 1);
+        }
+    } else {
+        infer(color_range, 1);
+        if (profile == 1 || profile == 3) {
+            infer(subsampling_x, 0);
+            infer(subsampling_y, 0);
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(frame_size)(CodedBitstreamContext *ctx, RWContext *rw,
+                            VP9RawFrameHeader *current)
+{
+    CodedBitstreamVP9Context *vp9 = ctx->priv_data;
+    int err;
+
+    f(16, frame_width_minus_1);
+    f(16, frame_height_minus_1);
+
+    vp9->mi_cols = (current->frame_width_minus_1  + 8) >> 3;
+    vp9->mi_rows = (current->frame_height_minus_1 + 8) >> 3;
+    vp9->sb64_cols = (vp9->mi_cols + 7) >> 3;
+    vp9->sb64_rows = (vp9->mi_rows + 7) >> 3;
+
+    return 0;
+}
+
+static int FUNC(render_size)(CodedBitstreamContext *ctx, RWContext *rw,
+                             VP9RawFrameHeader *current)
+{
+    int err;
+
+    f(1, render_and_frame_size_different);
+
+    if (current->render_and_frame_size_different) {
+        f(16, render_width_minus_1);
+        f(16, render_height_minus_1);
+    }
+
+    return 0;
+}
+
+static int FUNC(frame_size_with_refs)(CodedBitstreamContext *ctx, RWContext *rw,
+                                      VP9RawFrameHeader *current)
+{
+    int err, i;
+
+    for (i = 0; i < VP9_REFS_PER_FRAME; i++) {
+        fs(1, found_ref[i], 1, i);
+        if (current->found_ref[i])
+            break;
+    }
+    if (i >= VP9_REFS_PER_FRAME)
+        CHECK(FUNC(frame_size)(ctx, rw, current));
+    CHECK(FUNC(render_size)(ctx, rw, current));
+
+    return 0;
+}
+
+static int FUNC(interpolation_filter)(CodedBitstreamContext *ctx, RWContext *rw,
+                                      VP9RawFrameHeader *current)
+{
+    int err;
+
+    f(1, is_filter_switchable);
+    if (!current->is_filter_switchable)
+        f(2, raw_interpolation_filter_type);
+
+    return 0;
+}
+
+static int FUNC(loop_filter_params)(CodedBitstreamContext *ctx, RWContext *rw,
+                                    VP9RawFrameHeader *current)
+{
+    int err, i;
+
+    f(6, loop_filter_level);
+    f(3, loop_filter_sharpness);
+
+    f(1, loop_filter_delta_enabled);
+    if (current->loop_filter_delta_enabled) {
+        f(1, loop_filter_delta_update);
+        if (current->loop_filter_delta_update) {
+            for (i = 0; i < VP9_MAX_REF_FRAMES; i++) {
+                fs(1, update_ref_delta[i], 1, i);
+                if (current->update_ref_delta[i])
+                    ss(6, loop_filter_ref_deltas[i], 1, i);
+            }
+            for (i = 0; i < 2; i++) {
+                fs(1, update_mode_delta[i], 1, i);
+                if (current->update_mode_delta[i])
+                    ss(6, loop_filter_mode_deltas[i], 1, i);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(quantization_params)(CodedBitstreamContext *ctx, RWContext *rw,
+                                     VP9RawFrameHeader *current)
+{
+    int err;
+
+    f(8, base_q_idx);
+
+    delta_q(delta_q_y_dc);
+    delta_q(delta_q_uv_dc);
+    delta_q(delta_q_uv_ac);
+
+    return 0;
+}
+
+static int FUNC(segmentation_params)(CodedBitstreamContext *ctx, RWContext *rw,
+                                     VP9RawFrameHeader *current)
+{
+    static const int segmentation_feature_bits[VP9_SEG_LVL_MAX]   = { 8, 6, 2, 0 };
+    static const int segmentation_feature_signed[VP9_SEG_LVL_MAX] = { 1, 1, 0, 0 };
+
+    int err, i, j;
+
+    f(1, segmentation_enabled);
+
+    if (current->segmentation_enabled) {
+        f(1, segmentation_update_map);
+        if (current->segmentation_update_map) {
+            for (i = 0; i < 7; i++)
+                prob(segmentation_tree_probs[i], 1, i);
+            f(1, segmentation_temporal_update);
+            for (i = 0; i < 3; i++) {
+                if (current->segmentation_temporal_update)
+                    prob(segmentation_pred_prob[i], 1, i);
+                else
+                    infer(segmentation_pred_prob[i], 255);
+            }
+        }
+
+        f(1, segmentation_update_data);
+        if (current->segmentation_update_data) {
+            f(1, segmentation_abs_or_delta_update);
+            for (i = 0; i < VP9_MAX_SEGMENTS; i++) {
+                for (j = 0; j < VP9_SEG_LVL_MAX; j++) {
+                    fs(1, feature_enabled[i][j], 2, i, j);
+                    if (current->feature_enabled[i][j] &&
+                        segmentation_feature_bits[j]) {
+                        fs(segmentation_feature_bits[j],
+                           feature_value[i][j], 2, i, j);
+                        if (segmentation_feature_signed[j])
+                            fs(1, feature_sign[i][j], 2, i, j);
+                        else
+                            infer(feature_sign[i][j], 0);
+                    } else {
+                        infer(feature_value[i][j], 0);
+                        infer(feature_sign[i][j],  0);
+                    }
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(tile_info)(CodedBitstreamContext *ctx, RWContext *rw,
+                           VP9RawFrameHeader *current)
+{
+    CodedBitstreamVP9Context *vp9 = ctx->priv_data;
+    int min_log2_tile_cols, max_log2_tile_cols;
+    int err;
+
+    min_log2_tile_cols = 0;
+    while ((VP9_MAX_TILE_WIDTH_B64 << min_log2_tile_cols) < vp9->sb64_cols)
+        ++min_log2_tile_cols;
+    max_log2_tile_cols = 0;
+    while ((vp9->sb64_cols >> (max_log2_tile_cols + 1)) >= VP9_MIN_TILE_WIDTH_B64)
+        ++max_log2_tile_cols;
+
+    increment(tile_cols_log2, min_log2_tile_cols, max_log2_tile_cols);
+
+    increment(tile_rows_log2, 0, 2);
+
+    return 0;
+}
+
+static int FUNC(uncompressed_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                                     VP9RawFrameHeader *current)
+{
+    int profile, i;
+    int err;
+
+    f(2, frame_marker);
+
+    f(1, profile_low_bit);
+    f(1, profile_high_bit);
+    profile = (current->profile_high_bit << 1) + current->profile_low_bit;
+    if (profile == 3)
+        f(1, profile_reserved_zero);
+
+    f(1, show_existing_frame);
+    if (current->show_existing_frame) {
+        f(3, frame_to_show_map_idx);
+        infer(header_size_in_bytes, 0);
+        infer(refresh_frame_flags,  0x00);
+        infer(loop_filter_level,    0);
+        return 0;
+    }
+
+    f(1, frame_type);
+    f(1, show_frame);
+    f(1, error_resilient_mode);
+
+    if (current->frame_type == VP9_KEY_FRAME) {
+        CHECK(FUNC(frame_sync_code)(ctx, rw, current));
+        CHECK(FUNC(color_config)(ctx, rw, current, profile));
+        CHECK(FUNC(frame_size)(ctx, rw, current));
+        CHECK(FUNC(render_size)(ctx, rw, current));
+
+        infer(refresh_frame_flags, 0xff);
+
+    } else {
+         if (current->show_frame == 0)
+             f(1, intra_only);
+         else
+             infer(intra_only, 0);
+
+         if (current->error_resilient_mode == 0)
+             f(2, reset_frame_context);
+         else
+             infer(reset_frame_context, 0);
+
+         if (current->intra_only == 1) {
+             CHECK(FUNC(frame_sync_code)(ctx, rw, current));
+
+             if (profile > 0) {
+                 CHECK(FUNC(color_config)(ctx, rw, current, profile));
+             } else {
+                 infer(color_space,   1);
+                 infer(subsampling_x, 1);
+                 infer(subsampling_y, 1);
+             }
+
+             f(8, refresh_frame_flags);
+
+             CHECK(FUNC(frame_size)(ctx, rw, current));
+             CHECK(FUNC(render_size)(ctx, rw, current));
+         } else {
+             f(8, refresh_frame_flags);
+
+             for (i = 0; i < VP9_REFS_PER_FRAME; i++) {
+                 fs(3, ref_frame_idx[i], 1, i);
+                 fs(1, ref_frame_sign_bias[VP9_LAST_FRAME + i],
+                    1, VP9_LAST_FRAME + i);
+             }
+
+             CHECK(FUNC(frame_size_with_refs)(ctx, rw, current));
+             f(1, allow_high_precision_mv);
+             CHECK(FUNC(interpolation_filter)(ctx, rw, current));
+         }
+    }
+
+    if (current->error_resilient_mode == 0) {
+        f(1, refresh_frame_context);
+        f(1, frame_parallel_decoding_mode);
+    } else {
+        infer(refresh_frame_context,        0);
+        infer(frame_parallel_decoding_mode, 1);
+    }
+
+    f(2, frame_context_idx);
+
+    CHECK(FUNC(loop_filter_params)(ctx, rw, current));
+    CHECK(FUNC(quantization_params)(ctx, rw, current));
+    CHECK(FUNC(segmentation_params)(ctx, rw, current));
+    CHECK(FUNC(tile_info)(ctx, rw, current));
+
+    f(16, header_size_in_bytes);
+
+    return 0;
+}
+
+static int FUNC(trailing_bits)(CodedBitstreamContext *ctx, RWContext *rw)
+{
+    int err;
+    av_unused int zero = 0;
+    while (byte_alignment(rw) != 0)
+        xf(1, zero_bit, zero, 0);
+
+    return 0;
+}
+
+static int FUNC(frame)(CodedBitstreamContext *ctx, RWContext *rw,
+                       VP9RawFrame *current)
+{
+    int err;
+
+    HEADER("Frame");
+
+    CHECK(FUNC(uncompressed_header)(ctx, rw, &current->header));
+
+    CHECK(FUNC(trailing_bits)(ctx, rw));
+
+    return 0;
+}
+
+static int FUNC(superframe_index)(CodedBitstreamContext *ctx, RWContext *rw,
+                                  VP9RawSuperframeIndex *current)
+{
+    int err, i;
+
+    HEADER("Superframe Index");
+
+    f(3, superframe_marker);
+    f(2, bytes_per_framesize_minus_1);
+    f(3, frames_in_superframe_minus_1);
+
+    for (i = 0; i <= current->frames_in_superframe_minus_1; i++) {
+        // Surprise little-endian!
+        fle(8 * (current->bytes_per_framesize_minus_1 + 1),
+            frame_sizes[i], 1, i);
+    }
+
+    f(3, superframe_marker);
+    f(2, bytes_per_framesize_minus_1);
+    f(3, frames_in_superframe_minus_1);
+
+    return 0;
+}