add scalability experiment

cmake: -DCONFIG_SCALABILITY=1

Change-Id: Ifa908f809bcf904bdf0ed87b351e1ef3accc2b3f
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 743dd0c..168cbf7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -330,6 +330,12 @@
                  "${AOM_ROOT}/examples/simple_decoder.c"
                  $<TARGET_OBJECTS:aom_common_app_util>
                  $<TARGET_OBJECTS:aom_decoder_app_util>)
+  if (CONFIG_SCALABILITY)
+    add_executable(scalable_decoder
+                   "${AOM_ROOT}/examples/scalable_decoder.c"
+                   $<TARGET_OBJECTS:aom_common_app_util>
+                   $<TARGET_OBJECTS:aom_decoder_app_util>)
+  endif ()
 
   if (CONFIG_ANALYZER)
     add_executable(analyzer
@@ -370,6 +376,11 @@
   set(AOM_DECODER_EXAMPLE_TARGETS ${AOM_DECODER_EXAMPLE_TARGETS}
       aomdec decode_to_md5 decode_with_drops simple_decoder)
 
+  if (CONFIG_SCALABILITY)
+    set(AOM_DECODER_EXAMPLE_TARGETS ${AOM_DECODER_EXAMPLE_TARGETS}
+        scalable_decoder)
+  endif ()
+
   # Add decoder examples to the app targets list.
   set(AOM_APP_TARGETS ${AOM_APP_TARGETS} ${AOM_DECODER_EXAMPLE_TARGETS})
 endif ()
@@ -397,10 +408,22 @@
                    "${AOM_ROOT}/examples/twopass_encoder.c"
                    $<TARGET_OBJECTS:aom_common_app_util>
                    $<TARGET_OBJECTS:aom_encoder_app_util>)
+    if (CONFIG_SCALABILITY)
+      add_executable(scalable_encoder
+                     "${AOM_ROOT}/examples/scalable_encoder.c"
+                     $<TARGET_OBJECTS:aom_common_app_util>
+                     $<TARGET_OBJECTS:aom_encoder_app_util>)
+    endif ()
 
     # Maintain a list of encoder example targets.
     set(AOM_ENCODER_EXAMPLE_TARGETS
         aomenc lossless_encoder set_maps simple_encoder twopass_encoder)
+
+    if (CONFIG_SCALABILITY)
+      set(AOM_ENCODER_EXAMPLE_TARGETS ${AOM_ENCODER_EXAMPLE_TARGETS}
+          scalable_encoder)
+    endif ()
+
   endif ()
 
   if (ENABLE_TOOLS)
diff --git a/aom/aom_codec.h b/aom/aom_codec.h
index 976c2b0..6800bb2 100644
--- a/aom/aom_codec.h
+++ b/aom/aom_codec.h
@@ -496,6 +496,7 @@
   OBU_METADATA_TYPE_PRIVATE_DATA = 0,
   OBU_METADATA_TYPE_HDR_CLL = 1,
   OBU_METADATA_TYPE_HDR_MDCV = 2,
+  OBU_METADATA_TYPE_SCALABILITY = 3,
 } OBU_METADATA_TYPE;
 
 /*!@} - end defgroup codec*/
diff --git a/aom/aom_decoder.h b/aom/aom_decoder.h
index 9525283..d92e43a 100644
--- a/aom/aom_decoder.h
+++ b/aom/aom_decoder.h
@@ -83,9 +83,10 @@
  * stream.
  */
 typedef struct aom_codec_stream_info {
-  unsigned int w;     /**< Width (or 0 for unknown/default) */
-  unsigned int h;     /**< Height (or 0 for unknown/default) */
-  unsigned int is_kf; /**< Current frame is a keyframe */
+  unsigned int w;                      /**< Width (or 0 for unknown/default) */
+  unsigned int h;                      /**< Height (or 0 for unknown/default) */
+  unsigned int is_kf;                  /**< Current frame is a keyframe */
+  unsigned int enhancement_layers_cnt; /**< Enhancement layers */
 } aom_codec_stream_info_t;
 
 /* REQUIRED FUNCTIONS
diff --git a/aom/aom_image.h b/aom/aom_image.h
index f94582e..3636ae1 100644
--- a/aom/aom_image.h
+++ b/aom/aom_image.h
@@ -213,6 +213,9 @@
 
   int bps; /**< bits per sample (for packed formats) */
 
+  int temporal_id;    /**< Temporal layer Id of image */
+  int enhancement_id; /**< Spatial layer Id of image */
+
   /*!\brief The following member may be set by the application to associate
    * data with this image.
    */
diff --git a/aom/aomcx.h b/aom/aomcx.h
index d48b796..1cedd58 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -143,6 +143,10 @@
    */
   AOME_SET_SCALEMODE = 11,
 
+  /*!\brief Codec control function to set encoder enhancement layer id.
+  */
+  AOME_SET_ENHANCEMENT_LAYER_ID = 12,
+
   /*!\brief Codec control function to set encoder internal speed settings.
    *
    * Changes in this value influences, among others, the encoder's selection
@@ -215,6 +219,10 @@
    */
   AOME_SET_MAX_INTRA_BITRATE_PCT,
 
+  /*!\brief Codec control function to set number of spatial layers.
+  */
+  AOME_SET_NUMBER_SPATIAL_LAYERS,
+
   /*!\brief Codec control function to set max data rate for Inter frames.
    *
    * This value controls additional clamping on the maximum size of an
@@ -798,6 +806,9 @@
 AOM_CTRL_USE_TYPE(AOME_SET_SCALEMODE, aom_scaling_mode_t *)
 #define AOM_CTRL_AOME_SET_SCALEMODE
 
+AOM_CTRL_USE_TYPE(AOME_SET_ENHANCEMENT_LAYER_ID, int)
+#define AOM_CTRL_AOME_SET_ENHANCEMENT_LAYER_ID
+
 AOM_CTRL_USE_TYPE(AOME_SET_CPUUSED, int)
 #define AOM_CTRL_AOME_SET_CPUUSED
 AOM_CTRL_USE_TYPE(AOME_SET_DEVSF, int)
@@ -847,6 +858,9 @@
 AOM_CTRL_USE_TYPE(AOME_SET_MAX_INTER_BITRATE_PCT, unsigned int)
 #define AOM_CTRL_AOME_SET_MAX_INTER_BITRATE_PCT
 
+AOM_CTRL_USE_TYPE(AOME_SET_NUMBER_SPATIAL_LAYERS, int)
+#define AOME_CTRL_AOME_SET_NUMBER_SPATIAL_LAYERS
+
 AOM_CTRL_USE_TYPE(AV1E_SET_GF_CBR_BOOST_PCT, unsigned int)
 #define AOM_CTRL_AV1E_SET_GF_CBR_BOOST_PCT
 
diff --git a/aomdec.c b/aomdec.c
index 097d6fe..5f9181f 100644
--- a/aomdec.c
+++ b/aomdec.c
@@ -255,8 +255,12 @@
 #if CONFIG_OBU_NO_IVF
     case FILE_TYPE_OBU:
       return obu_read_temporal_unit(input->aom_input_ctx->file, buf,
+#if CONFIG_SCALABILITY
+                                    bytes_in_buffer, buffer_size, 0);
+#else
                                     bytes_in_buffer, buffer_size);
 #endif
+#endif
     default: return 1;
   }
 }
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 43e5361..024926f 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -29,6 +29,10 @@
 #define MAG_SIZE (4)
 #define MAX_INDEX_SIZE (256)
 
+#if CONFIG_SCALABILITY
+#define MAX_NUM_ENHANCEMENT_LAYERS 128
+#endif
+
 struct av1_extracfg {
   int cpu_used;  // available cpu percentage in 1/16
   int dev_sf;
@@ -1422,28 +1426,34 @@
       pkt.data.frame.partition_id = -1;
 
 #if CONFIG_OBU
-      // move data PRE_OBU_SIZE_BYTES + 1 bytes and insert OBU_TD preceded by
-      // 4 byte size
-      uint32_t obu_size = 1;
-      if (ctx->pending_cx_data) {
-        const size_t index_sz = PRE_OBU_SIZE_BYTES + 1;
-        memmove(ctx->pending_cx_data + index_sz, ctx->pending_cx_data,
-                ctx->pending_cx_data_sz);
-      }
-      obu_size = write_obu_header(
-          OBU_TEMPORAL_DELIMITER, 0,
-          (uint8_t *)(ctx->pending_cx_data + PRE_OBU_SIZE_BYTES));
-      obu_size += write_temporal_delimiter_obu();
+      int write_temporal_delimiter = 1;
+#if CONFIG_SCALABILITY
+      // only write OBU_TD if base layer
+      write_temporal_delimiter = !cpi->common.enhancement_layer_id;
+#endif  // CONFIG_SCALABILITY
+      if (write_temporal_delimiter) {
+        // move data PRE_OBU_SIZE_BYTES + 1 bytes and insert OBU_TD preceded by
+        // optional 4 byte size
+        uint32_t obu_size = 1;
+        if (ctx->pending_cx_data) {
+          const size_t index_sz = PRE_OBU_SIZE_BYTES + 1;
+          memmove(ctx->pending_cx_data + index_sz, ctx->pending_cx_data,
+                  ctx->pending_cx_data_sz);
+        }
+        obu_size = write_obu_header(
+            OBU_TEMPORAL_DELIMITER, 0,
+            (uint8_t *)(ctx->pending_cx_data + PRE_OBU_SIZE_BYTES));
+        obu_size += write_temporal_delimiter_obu();
 #if CONFIG_OBU_SIZING
-      // OBUs are preceded by an unsigned leb128 coded unsigned integer padded
-      // to PRE_OBU_SIZE_BYTES bytes.
-      if (write_uleb_obu_size(obu_size, ctx->pending_cx_data) != AOM_CODEC_OK)
-        return AOM_CODEC_ERROR;
+        // OBUs are preceded by an unsigned leb128 coded unsigned integer padded
+        // to PRE_OBU_SIZE_BYTES bytes.
+        if (write_uleb_obu_size(obu_size, ctx->pending_cx_data) != AOM_CODEC_OK)
+          return AOM_CODEC_ERROR;
 #else
-      mem_put_le32(ctx->pending_cx_data, obu_size);
+        mem_put_le32(ctx->pending_cx_data, obu_size);
 #endif  // CONFIG_OBU_SIZING
-
-      pkt.data.frame.sz += (obu_size + PRE_OBU_SIZE_BYTES);
+        pkt.data.frame.sz += (obu_size + PRE_OBU_SIZE_BYTES);
+      }
 #endif  // CONFIG_OBU
 
       pkt.data.frame.pts = ticks_to_timebase_units(timebase, dst_time_stamp);
@@ -1610,6 +1620,36 @@
   }
 }
 
+static aom_codec_err_t ctrl_set_enhancement_layer_id(aom_codec_alg_priv_t *ctx,
+                                                     va_list args) {
+#if CONFIG_SCALABILITY
+  const int enhancement_layer_id = va_arg(args, int);
+  if (enhancement_layer_id > MAX_NUM_ENHANCEMENT_LAYERS)
+    return AOM_CODEC_INVALID_PARAM;
+  ctx->cpi->common.enhancement_layer_id = enhancement_layer_id;
+  return AOM_CODEC_OK;
+#else
+  (void)ctx;
+  (void)args;
+  return AOM_CODEC_UNSUP_FEATURE;
+#endif
+}
+
+static aom_codec_err_t ctrl_set_number_spatial_layers(aom_codec_alg_priv_t *ctx,
+                                                      va_list args) {
+#if CONFIG_SCALABILITY
+  const int number_spatial_layers = va_arg(args, int);
+  if (number_spatial_layers > MAX_NUM_ENHANCEMENT_LAYERS)
+    return AOM_CODEC_INVALID_PARAM;
+  ctx->cpi->common.enhancement_layers_cnt = number_spatial_layers - 1;
+  return AOM_CODEC_OK;
+#else
+  (void)ctx;
+  (void)args;
+  return AOM_CODEC_UNSUP_FEATURE;
+#endif
+}
+
 static aom_codec_err_t ctrl_set_tune_content(aom_codec_alg_priv_t *ctx,
                                              va_list args) {
   struct av1_extracfg extra_cfg = ctx->extra_cfg;
@@ -1709,6 +1749,7 @@
   { AOME_SET_ROI_MAP, ctrl_set_roi_map },
   { AOME_SET_ACTIVEMAP, ctrl_set_active_map },
   { AOME_SET_SCALEMODE, ctrl_set_scale_mode },
+  { AOME_SET_ENHANCEMENT_LAYER_ID, ctrl_set_enhancement_layer_id },
   { AOME_SET_CPUUSED, ctrl_set_cpuused },
   { AOME_SET_DEVSF, ctrl_set_devsf },
   { AOME_SET_ENABLEAUTOALTREF, ctrl_set_enable_auto_alt_ref },
@@ -1733,6 +1774,7 @@
   { AOME_SET_TUNING, ctrl_set_tuning },
   { AOME_SET_CQ_LEVEL, ctrl_set_cq_level },
   { AOME_SET_MAX_INTRA_BITRATE_PCT, ctrl_set_rc_max_intra_bitrate_pct },
+  { AOME_SET_NUMBER_SPATIAL_LAYERS, ctrl_set_number_spatial_layers },
   { AV1E_SET_MAX_INTER_BITRATE_PCT, ctrl_set_rc_max_inter_bitrate_pct },
   { AV1E_SET_GF_CBR_BOOST_PCT, ctrl_set_rc_gf_cbr_boost_pct },
   { AV1E_SET_LOSSLESS, ctrl_set_lossless },
diff --git a/av1/av1_dx_iface.c b/av1/av1_dx_iface.c
index 8b2d254..6ede519 100644
--- a/av1/av1_dx_iface.c
+++ b/av1/av1_dx_iface.c
@@ -20,6 +20,9 @@
 #include "aom/aom_decoder.h"
 #include "aom_dsp/bitreader_buffer.h"
 #include "aom_dsp/aom_dsp_common.h"
+#if CONFIG_OBU
+#include "aom_ports/mem_ops.h"
+#endif
 #include "aom_util/aom_thread.h"
 
 #include "av1/common/alloccommon.h"
@@ -327,10 +330,33 @@
 
   {
 #if CONFIG_OBU
-    // Proper fix needed
     si->is_kf = 1;
     intra_only_flag = 1;
     si->h = 1;
+
+    struct aom_read_bit_buffer rb = { data + PRE_OBU_SIZE_BYTES, data + data_sz,
+                                      0, NULL, NULL };
+    mem_get_le32(data);
+    aom_rb_read_literal(&rb, 8);  // obu_header
+    av1_read_profile(&rb);        // profile
+    aom_rb_read_literal(&rb, 4);  // level
+#if CONFIG_SCALABILITY
+    int i;
+    si->enhancement_layers_cnt = aom_rb_read_literal(&rb, 2);
+    for (i = 1; i <= (int)si->enhancement_layers_cnt; i++) {
+      aom_rb_read_literal(&rb, 4);  // level for each enhancement layer
+    }
+#endif  // CONFIG_SCALABILITY
+
+#if CONFIG_FRAME_SIZE
+    int num_bits_width = aom_rb_read_literal(&rb, 4) + 1;
+    int num_bits_height = aom_rb_read_literal(&rb, 4) + 1;
+    int max_frame_width = aom_rb_read_literal(&rb, num_bits_width) + 1;
+    int max_frame_height = aom_rb_read_literal(&rb, num_bits_height) + 1;
+    si->w = max_frame_width;
+    si->h = max_frame_height;
+#endif  // CONFIG_FRAME_SIZE
+
 #else
     int show_frame;
     int error_resilient;
@@ -989,6 +1015,10 @@
 
           ctx->img.fb_priv = frame_bufs[cm->new_fb_idx].raw_frame_buffer.priv;
           img = &ctx->img;
+#if CONFIG_SCALABILITY
+          img->temporal_id = cm->temporal_layer_id;
+          img->enhancement_id = cm->enhancement_layer_id;
+#endif
 #if CONFIG_FILM_GRAIN
           return add_grain_if_needed(
               img, ctx->image_with_grain,
diff --git a/av1/common/onyxc_int.h b/av1/common/onyxc_int.h
index fd5c6cb..eb79e5a 100644
--- a/av1/common/onyxc_int.h
+++ b/av1/common/onyxc_int.h
@@ -608,6 +608,12 @@
   DqType dq_type;
 #endif  // CONFIG_NEW_QUANT
 
+#if CONFIG_SCALABILITY
+  int temporal_layer_id;
+  int enhancement_layer_id;
+  int enhancement_layers_cnt;
+#endif
+
 #if TXCOEFF_TIMER
   int64_t cum_txcoeff_timer;
   int64_t txcoeff_timer;
diff --git a/av1/decoder/obu.c b/av1/decoder/obu.c
index 89e9eeb..e1ed8eb 100644
--- a/av1/decoder/obu.c
+++ b/av1/decoder/obu.c
@@ -21,8 +21,29 @@
 #include "av1/decoder/decoder.h"
 #include "av1/decoder/decodeframe.h"
 
+#if CONFIG_SCALABILITY
+// picture prediction structures (0-12 are predefined) in scaiability metadata
+typedef enum {
+  SCALABILITY_L1T2 = 0,
+  SCALABILITY_L1T3 = 1,
+  SCALABILITY_L2T1 = 2,
+  SCALABILITY_L2T2 = 3,
+  SCALABILITY_L2T3 = 4,
+  SCALABILITY_S2T1 = 5,
+  SCALABILITY_S2T2 = 6,
+  SCALABILITY_S2T3 = 7,
+  SCALABILITY_L2T2h = 8,
+  SCALABILITY_L2T3h = 9,
+  SCALABILITY_S2T1h = 10,
+  SCALABILITY_S2T2h = 11,
+  SCALABILITY_S2T3h = 12,
+  SCALABILITY_SS = 13
+} SCALABILITY_STRUCTURES;
+#endif
+
 static OBU_TYPE read_obu_header(struct aom_read_bit_buffer *rb,
-                                size_t *header_size) {
+                                size_t *header_size,
+                                uint8_t *obu_extension_header) {
   *header_size = 1;
 
   // first bit is obu_forbidden_bit (0) according to R19
@@ -31,12 +52,17 @@
   const OBU_TYPE obu_type = (OBU_TYPE)aom_rb_read_literal(rb, 4);
   aom_rb_read_literal(rb, 2);  // reserved
   const int obu_extension_flag = aom_rb_read_bit(rb);
+  if (obu_extension_header) *obu_extension_header = 0;
   if (obu_extension_flag) {
     *header_size += 1;
+#if !CONFIG_SCALABILITY
     aom_rb_read_literal(rb, 3);  // temporal_id
     aom_rb_read_literal(rb, 2);
     aom_rb_read_literal(rb, 2);
     aom_rb_read_literal(rb, 1);  // reserved
+#else
+    *obu_extension_header = (uint8_t)aom_rb_read_literal(rb, 8);
+#endif
   }
 
   return obu_type;
@@ -48,10 +74,20 @@
                                          struct aom_read_bit_buffer *rb) {
   AV1_COMMON *const cm = &pbi->common;
   uint32_t saved_bit_offset = rb->bit_offset;
+#if CONFIG_SCALABILITY
+  int i;
+#endif
 
   cm->profile = av1_read_profile(rb);
   aom_rb_read_literal(rb, 4);  // level
 
+#if CONFIG_SCALABILITY
+  pbi->common.enhancement_layers_cnt = aom_rb_read_literal(rb, 2);
+  for (i = 1; i <= pbi->common.enhancement_layers_cnt; i++) {
+    aom_rb_read_literal(rb, 4);  // level for each enhancement layer
+  }
+#endif
+
   read_sequence_header(&cm->seq_params, rb);
 
   av1_read_bitdepth_colorspace_sampling(cm, rb, pbi->allow_lowbitdepth);
@@ -145,6 +181,51 @@
   mem_get_le16(data);
 }
 
+#if CONFIG_SCALABILITY
+static void scalability_structure(struct aom_read_bit_buffer *rb) {
+  int enhancement_layers_cnt = aom_rb_read_literal(rb, 2);
+  int enhancement_layer_dimensions_present_flag = aom_rb_read_literal(rb, 1);
+  int enhancement_layer_description_present_flag = aom_rb_read_literal(rb, 1);
+  int temporal_group_description_flag = aom_rb_read_literal(rb, 1);
+  aom_rb_read_literal(rb, 3);  // reserved
+
+  if (enhancement_layer_dimensions_present_flag) {
+    int i;
+    for (i = 0; i < enhancement_layers_cnt + 1; i++) {
+      aom_rb_read_literal(rb, 16);
+      aom_rb_read_literal(rb, 16);
+    }
+  }
+  if (enhancement_layer_description_present_flag) {
+    int i;
+    for (i = 0; i < enhancement_layers_cnt + 1; i++) {
+      aom_rb_read_literal(rb, 8);
+    }
+  }
+  if (temporal_group_description_flag) {
+    int i, j, temporal_group_size;
+    temporal_group_size = aom_rb_read_literal(rb, 8);
+    for (i = 0; i < temporal_group_size; i++) {
+      aom_rb_read_literal(rb, 3);
+      aom_rb_read_literal(rb, 1);
+      int temporal_group_ref_cnt = aom_rb_read_literal(rb, 2);
+      aom_rb_read_literal(rb, 2);
+      for (j = 0; j < temporal_group_ref_cnt; j++) {
+        aom_rb_read_literal(rb, 8);
+      }
+    }
+  }
+}
+
+static void read_metadata_scalability(const uint8_t *data, size_t sz) {
+  struct aom_read_bit_buffer rb = { data, data + sz, 0, NULL, NULL };
+  int scalability_mode_idc = aom_rb_read_literal(&rb, 8);
+  if (scalability_mode_idc == SCALABILITY_SS) {
+    scalability_structure(&rb);
+  }
+}
+#endif
+
 static size_t read_metadata(const uint8_t *data, size_t sz) {
   assert(sz >= 2);
   const OBU_METADATA_TYPE metadata_type = (OBU_METADATA_TYPE)mem_get_le16(data);
@@ -155,11 +236,22 @@
     read_metadata_hdr_cll(data + 2);
   } else if (metadata_type == OBU_METADATA_TYPE_HDR_MDCV) {
     read_metadata_hdr_mdcv(data + 2);
+#if CONFIG_SCALABILITY
+  } else if (metadata_type == OBU_METADATA_TYPE_SCALABILITY) {
+    read_metadata_scalability(data + 2, sz - 2);
+#endif
   }
 
   return sz;
 }
 
+#if CONFIG_SCALABILITY
+static const uint32_t kObuExtTemporalIdBitsMask = 0x7;
+static const uint32_t kObuExtTemporalIdBitsShift = 5;
+static const uint32_t kObuExtEnhancementIdBitsMask = 0x3;
+static const uint32_t kObuExtEnhancementIdBitsShift = 3;
+#endif
+
 void av1_decode_frame_from_obus(struct AV1Decoder *pbi, const uint8_t *data,
                                 const uint8_t *data_end,
                                 const uint8_t **p_data_end) {
@@ -168,6 +260,9 @@
   int is_first_tg_obu_received = 1;
   int frame_header_received = 0;
   int frame_header_size = 0;
+#if CONFIG_SCALABILITY
+  uint8_t obu_extension_header = 0;
+#endif
 
   if (data_end < data) {
     cm->error.error_code = AOM_CODEC_CORRUPT_FRAME;
@@ -193,12 +288,28 @@
     const size_t obu_size = mem_get_le32(data);
 #endif  // CONFIG_OBU_SIZING
 
-    const OBU_TYPE obu_type = read_obu_header(&rb, &obu_header_size);
+#if !CONFIG_SCALABILITY
+    const OBU_TYPE obu_type = read_obu_header(&rb, &obu_header_size, NULL);
+#else
+    const OBU_TYPE obu_type =
+        read_obu_header(&rb, &obu_header_size, &obu_extension_header);
+#endif
+
     data += (PRE_OBU_SIZE_BYTES + obu_header_size);
     if (data_end < data) {
       cm->error.error_code = AOM_CODEC_CORRUPT_FRAME;
       return;
     }
+
+#if CONFIG_SCALABILITY
+    cm->temporal_layer_id =
+        (obu_extension_header >> kObuExtTemporalIdBitsShift) &
+        kObuExtTemporalIdBitsMask;
+    cm->enhancement_layer_id =
+        (obu_extension_header >> kObuExtEnhancementIdBitsShift) &
+        kObuExtEnhancementIdBitsMask;
+#endif
+
     switch (obu_type) {
       case OBU_TEMPORAL_DELIMITER:
         obu_payload_size = read_temporal_delimiter_obu();
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index 6275f14..b5a4673 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -4513,7 +4513,13 @@
 }
 #endif  // CONFIG_OBU_SIZING
 
-static uint32_t write_sequence_header_obu(AV1_COMP *cpi, uint8_t *const dst) {
+static uint32_t write_sequence_header_obu(AV1_COMP *cpi, uint8_t *const dst
+#if CONFIG_SCALABILITY
+                                          ,
+                                          uint8_t enhancement_layers_cnt) {
+#else
+                                          ) {
+#endif
   AV1_COMMON *const cm = &cpi->common;
   struct aom_write_bit_buffer wb = { dst, 0 };
   uint32_t size = 0;
@@ -4521,6 +4527,13 @@
   write_profile(cm->profile, &wb);
 
   aom_wb_write_literal(&wb, 0, 4);
+#if CONFIG_SCALABILITY
+  aom_wb_write_literal(&wb, enhancement_layers_cnt, 2);
+  int i;
+  for (i = 1; i <= enhancement_layers_cnt; i++) {
+    aom_wb_write_literal(&wb, 0, 4);
+  }
+#endif
 
   write_sequence_header(cpi, &wb);
 
@@ -4594,7 +4607,8 @@
 #if CONFIG_EXT_TILE
                                        struct aom_write_bit_buffer *saved_wb,
 #endif
-                                       int insert_frame_header_obu_flag) {
+                                       int insert_frame_header_obu_flag,
+                                       uint8_t obu_extension_header) {
   AV1_COMMON *const cm = &cpi->common;
   const int num_planes = av1_num_planes(cm);
   aom_writer mode_bc;
@@ -4761,8 +4775,8 @@
           data = dst + total_size;
           // A new tile group begins at this tile.  Write the obu header and
           // tile group header
-          curr_tg_data_size =
-              write_obu_header(OBU_TILE_GROUP, 0, data + PRE_OBU_SIZE_BYTES);
+          curr_tg_data_size = write_obu_header(
+              OBU_TILE_GROUP, obu_extension_header, data + PRE_OBU_SIZE_BYTES);
           if (n_log2_tiles)
             curr_tg_data_size += write_tile_group_header(
                 data + curr_tg_data_size + PRE_OBU_SIZE_BYTES, tile_idx,
@@ -4845,13 +4859,20 @@
   uint32_t data_size;
   unsigned int max_tile_size;
   unsigned int max_tile_col_size;
-  AV1_COMMON *const cm = &cpi->common;
 #if CONFIG_OBU
+  AV1_COMMON *const cm = &cpi->common;
   uint32_t obu_size;
   uint8_t *frame_header_location;
   uint32_t frame_header_size;
+#if CONFIG_SCALABILITY
+  const uint8_t enhancement_layers_cnt = cm->enhancement_layers_cnt;
+  const uint8_t obu_extension_header =
+      cm->temporal_layer_id << 5 | cm->enhancement_layer_id << 3 | 0;
+#else
+  uint8_t obu_extension_header = 0;
+#endif  // CONFIG_SCALABILITY
 #endif  // CONFIG_OBU
-  (void)cm;
+
 #if CONFIG_BITSTREAM_DEBUG
   bitstream_queue_reset_write();
 #endif
@@ -4863,8 +4884,13 @@
   if (cm->frame_type == KEY_FRAME) {
     obu_size =
         write_obu_header(OBU_SEQUENCE_HEADER, 0, data + PRE_OBU_SIZE_BYTES);
-    obu_size +=
-        write_sequence_header_obu(cpi, data + PRE_OBU_SIZE_BYTES + obu_size);
+
+    obu_size += write_sequence_header_obu(
+#if CONFIG_SCALABILITY
+        cpi, data + PRE_OBU_SIZE_BYTES + obu_size, enhancement_layers_cnt);
+#else
+        cpi, data + PRE_OBU_SIZE_BYTES + obu_size);
+#endif  // CONFIG_SCALABILITY
 
 #if CONFIG_OBU_SIZING
     if (write_uleb_obu_size(obu_size, data) != AOM_CODEC_OK)
@@ -4882,7 +4908,8 @@
 
   // write frame header obu, preceded by 4-byte size
   frame_header_location = data + PRE_OBU_SIZE_BYTES;
-  obu_size = write_obu_header(OBU_FRAME_HEADER, 0, frame_header_location);
+  obu_size = write_obu_header(OBU_FRAME_HEADER, obu_extension_header,
+                              frame_header_location);
   frame_header_size =
       write_frame_header_obu(cpi,
 #if CONFIG_EXT_TILE
@@ -4905,17 +4932,17 @@
   } else {
     //  Each tile group obu will be preceded by 4-byte size of the tile group
     //  obu
-    data_size =
-        write_tiles_in_tg_obus(cpi, data, &max_tile_size, &max_tile_col_size,
-                               frame_header_location - PRE_OBU_SIZE_BYTES,
-                               obu_size + PRE_OBU_SIZE_BYTES,
+    data_size = write_tiles_in_tg_obus(
+        cpi, data, &max_tile_size, &max_tile_col_size,
+        frame_header_location - PRE_OBU_SIZE_BYTES,
+        obu_size + PRE_OBU_SIZE_BYTES,
 #if CONFIG_EXT_TILE
-                               &saved_wb,
+        &saved_wb,
 #endif
-                               1 /* cm->error_resilient_mode */);
+        1 /* cm->error_resilient_mode */, obu_extension_header);
   }
 
-#endif
+#endif  // CONFIG_OBU
 
 #if CONFIG_EXT_TILE && !CONFIG_OBU
   uint32_t uncompressed_hdr_size;
diff --git a/build/cmake/aom_config_defaults.cmake b/build/cmake/aom_config_defaults.cmake
index 11d261e..f89d11c 100644
--- a/build/cmake/aom_config_defaults.cmake
+++ b/build/cmake/aom_config_defaults.cmake
@@ -148,6 +148,7 @@
 set(CONFIG_RECT_TX_EXT 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_RECT_TX_EXT_INTRA 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_REFERENCE_BUFFER 1 CACHE NUMBER "AV1 experiment flag.")
+set(CONFIG_SCALABILITY 0 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_SEGMENT_GLOBALMV 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_SEGMENT_PRED_LAST 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_SHORT_FILTER 1 CACHE NUMBER "AV1 experiment flag.")
diff --git a/build/cmake/aom_experiment_deps.cmake b/build/cmake/aom_experiment_deps.cmake
index 3a35f02..dfa64f5 100644
--- a/build/cmake/aom_experiment_deps.cmake
+++ b/build/cmake/aom_experiment_deps.cmake
@@ -20,6 +20,15 @@
     endif ()
   endif ()
 
+  if (CONFIG_SCALABILITY)
+    if (NOT CONFIG_OBU)
+      change_config_and_warn(CONFIG_OBU 1 CONFIG_SCALABILITY)
+    endif ()
+    if (NOT CONFIG_OBU_NO_IVF)
+      change_config_and_warn(CONFIG_OBU_NO_IVF 1 CONFIG_SCALABILITY)
+    endif ()
+  endif ()
+
   if (CONFIG_ANALYZER)
     if (NOT CONFIG_INSPECTION)
       change_config_and_warn(CONFIG_INSPECTION 1 CONFIG_ANALYZER)
diff --git a/docs.cmake b/docs.cmake
index 7fc7563..aa4cf16 100644
--- a/docs.cmake
+++ b/docs.cmake
@@ -89,6 +89,16 @@
       "Simplified encoder loop."
       "Two-pass encoder loop.")
 
+  if (CONFIG_SCALABILITY)
+    set(AOM_DOXYGEN_EXAMPLE_SOURCES
+        ${AOM_DOXYGEN_EXAMPLE_SOURCES}
+        "${AOM_ROOT}/examples/scalable_encoder.c")
+
+    set(AOM_DOXYGEN_EXAMPLE_DESCRIPTIONS
+        ${AOM_DOXYGEN_EXAMPLE_DESCRIPTIONS}
+        "Scalable encoder loop.")
+  endif ()
+
   set(AOM_DOXYGEN_SECTIONS ${AOM_DOXYGEN_SECTIONS} "av1_encoder encoder")
 
   set(AOM_DOXYGEN_SOURCES
diff --git a/examples/scalable_decoder.c b/examples/scalable_decoder.c
new file mode 100644
index 0000000..70d3428
--- /dev/null
+++ b/examples/scalable_decoder.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+// Scalable Decoder
+// ==============
+//
+// This is an example of a scalable decoder loop. It takes a 2-spatial-layer
+// input file
+// containing the compressed data (in OBU format), passes it through the
+// decoder, and writes the decompressed frames to disk. The base layer and
+// enhancement layers are stored as separate files, lyr0.yuv and lyr1.yuv,
+// respectively.
+//
+// Standard Includes
+// -----------------
+// For decoders, you only have to include `aom_decoder.h` and then any
+// header files for the specific codecs you use. In this case, we're using
+// av1.
+//
+// Initializing The Codec
+// ----------------------
+// The libaom decoder is initialized by the call to aom_codec_dec_init().
+// Determining the codec interface to use is handled by AvxVideoReader and the
+// functions prefixed with aom_video_reader_. Discussion of those functions is
+// beyond the scope of this example, but the main gist is to open the input file
+// and parse just enough of it to determine if it's a AVx file and which AVx
+// codec is contained within the file.
+// Note the NULL pointer passed to aom_codec_dec_init(). We do that in this
+// example because we want the algorithm to determine the stream configuration
+// (width/height) and allocate memory automatically.
+//
+// Decoding A Frame
+// ----------------
+// Once the frame has been read into memory, it is decoded using the
+// `aom_codec_decode` function. The call takes a pointer to the data
+// (`frame`) and the length of the data (`frame_size`). No application data
+// is associated with the frame in this example, so the `user_priv`
+// parameter is NULL. The `deadline` parameter is left at zero for this
+// example. This parameter is generally only used when doing adaptive post
+// processing.
+//
+// Codecs may produce a variable number of output frames for every call to
+// `aom_codec_decode`. These frames are retrieved by the
+// `aom_codec_get_frame` iterator function. The iterator variable `iter` is
+// initialized to NULL each time `aom_codec_decode` is called.
+// `aom_codec_get_frame` is called in a loop, returning a pointer to a
+// decoded image or NULL to indicate the end of list.
+//
+// Processing The Decoded Data
+// ---------------------------
+// In this example, we simply write the encoded data to disk. It is
+// important to honor the image's `stride` values.
+//
+// Cleanup
+// -------
+// The `aom_codec_destroy` call frees any memory allocated by the codec.
+//
+// Error Handling
+// --------------
+// This example does not special case any error return codes. If there was
+// an error, a descriptive message is printed and the program exits. With
+// few exceptions, aom_codec functions return an enumerated error status,
+// with the value `0` indicating success.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "aom/aom_decoder.h"
+
+#include "../tools_common.h"
+#include "../video_reader.h"
+#include "./aom_config.h"
+#if CONFIG_SCALABILITY
+#include "./obudec.h"
+#endif
+
+static const char *exec_name;
+
+#define MAX_LAYERS 5
+
+struct AvxDecInputContext {
+  struct AvxInputContext *aom_input_ctx;
+  struct WebmInputContext *webm_ctx;
+};
+
+void usage_exit(void) {
+  fprintf(stderr, "Usage: %s <infile>\n", exec_name);
+  exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv) {
+  int frame_cnt = 0;
+  FILE *outfile[MAX_LAYERS];
+  char filename[80];
+  aom_codec_ctx_t codec;
+  const AvxInterface *decoder = NULL;
+  FILE *inputfile = NULL;
+  uint8_t *buf = NULL;
+  size_t bytes_in_buffer = 0;
+  size_t buffer_size = 0;
+  int next_layer_id = 0;
+  struct AvxDecInputContext input = { NULL, NULL };
+  struct AvxInputContext aom_input_ctx;
+  input.aom_input_ctx = &aom_input_ctx;
+  aom_codec_stream_info_t si;
+  uint8_t tmpbuf[32];
+  unsigned int i;
+
+  exec_name = argv[0];
+
+  if (argc != 2) die("Invalid number of arguments.");
+
+  if (!(inputfile = fopen(argv[1], "rb")))
+    die("Failed to open %s for read.", argv[1]);
+  input.aom_input_ctx->file = inputfile;
+
+  decoder = get_aom_decoder_by_index(0);
+  printf("Using %s\n", aom_codec_iface_name(decoder->codec_interface()));
+
+  if (aom_codec_dec_init(&codec, decoder->codec_interface(), NULL, 0))
+    die_codec(&codec, "Failed to initialize decoder.");
+
+  if (!file_is_obu(input.aom_input_ctx))
+    die_codec(&codec, "Input is not a valid obu file");
+
+  // peak sequence header OBU to get enhancement layer count, if any
+  fread(tmpbuf, 1, 32, inputfile);
+  if (aom_codec_peek_stream_info(decoder->codec_interface(), tmpbuf, 32, &si)) {
+    die_codec(&codec, "Input is not a valid obu file");
+  }
+  fseek(inputfile, -32, SEEK_CUR);
+
+  // open output yuv files
+  for (i = 0; i <= si.enhancement_layers_cnt; i++) {
+    snprintf(filename, sizeof(filename), "out_lyr%d.yuv", i);
+    if (!(outfile[i] = fopen(filename, "wb")))
+      die("Failed to open output for writing.");
+  }
+
+  while (!obu_read_temporal_unit(inputfile, &buf, &bytes_in_buffer,
+                                 &buffer_size, next_layer_id)) {
+    aom_codec_iter_t iter = NULL;
+    aom_image_t *img = NULL;
+    if (aom_codec_decode(&codec, buf, (unsigned int)bytes_in_buffer, NULL))
+      die_codec(&codec, "Failed to decode frame.");
+
+    while ((img = aom_codec_get_frame(&codec, &iter)) != NULL) {
+      if (img->enhancement_id == 0) {
+        printf("Writing       base layer 0 %d\n", frame_cnt);
+        aom_img_write(img, outfile[0]);
+        next_layer_id++;
+      } else if (img->enhancement_id <= (int)si.enhancement_layers_cnt) {
+        printf("Writing enhancemnt layer %d %d\n", img->enhancement_id,
+               frame_cnt);
+        aom_img_write(img, outfile[img->enhancement_id]);
+        if (img->enhancement_id == (int)si.enhancement_layers_cnt)
+          next_layer_id = 0;
+        else
+          next_layer_id++;
+      } else {
+        die_codec(&codec, "Invalid bitstream.  Layer id exceeds layer count");
+      }
+      if (img->enhancement_id == (int)si.enhancement_layers_cnt) ++frame_cnt;
+    }
+  }
+
+  printf("Processed %d frames.\n", frame_cnt);
+  if (aom_codec_destroy(&codec)) die_codec(&codec, "Failed to destroy codec");
+
+  for (i = 0; i <= si.enhancement_layers_cnt; i++) fclose(outfile[i]);
+
+  fclose(inputfile);
+
+  return EXIT_SUCCESS;
+}
diff --git a/examples/scalable_encoder.c b/examples/scalable_encoder.c
new file mode 100644
index 0000000..10b3587
--- /dev/null
+++ b/examples/scalable_encoder.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2018, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+// Scalable Encoder
+// ==============
+//
+// This is an example of a scalable encoder loop. It takes two input files in
+// YV12 format, passes it through the encoder, and writes the compressed
+// frames to disk in OBU format.
+//
+// Getting The Default Configuration
+// ---------------------------------
+// Encoders have the notion of "usage profiles." For example, an encoder
+// may want to publish default configurations for both a video
+// conferencing application and a best quality offline encoder. These
+// obviously have very different default settings. Consult the
+// documentation for your codec to see if it provides any default
+// configurations. All codecs provide a default configuration, number 0,
+// which is valid for material in the vacinity of QCIF/QVGA.
+//
+// Updating The Configuration
+// ---------------------------------
+// Almost all applications will want to update the default configuration
+// with settings specific to their usage. Here we set the width and height
+// of the video file to that specified on the command line. We also scale
+// the default bitrate based on the ratio between the default resolution
+// and the resolution specified on the command line.
+//
+// Encoding A Frame
+// ----------------
+// The frame is read as a continuous block (size = width * height * 3 / 2)
+// from the input file. If a frame was read (the input file has not hit
+// EOF) then the frame is passed to the encoder. Otherwise, a NULL
+// is passed, indicating the End-Of-Stream condition to the encoder. The
+// `frame_cnt` is reused as the presentation time stamp (PTS) and each
+// frame is shown for one frame-time in duration. The flags parameter is
+// unused in this example.
+
+// Forced Keyframes
+// ----------------
+// Keyframes can be forced by setting the AOM_EFLAG_FORCE_KF bit of the
+// flags passed to `aom_codec_control()`. In this example, we force a
+// keyframe every <keyframe-interval> frames. Note, the output stream can
+// contain additional keyframes beyond those that have been forced using the
+// AOM_EFLAG_FORCE_KF flag because of automatic keyframe placement by the
+// encoder.
+//
+// Processing The Encoded Data
+// ---------------------------
+// Each packet of type `AOM_CODEC_CX_FRAME_PKT` contains the encoded data
+// for this frame. We write a IVF frame header, followed by the raw data.
+//
+// Cleanup
+// -------
+// The `aom_codec_destroy` call frees any memory allocated by the codec.
+//
+// Error Handling
+// --------------
+// This example does not special case any error return codes. If there was
+// an error, a descriptive message is printed and the program exits. With
+// few exeptions, aom_codec functions return an enumerated error status,
+// with the value `0` indicating success.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "aom/aom_encoder.h"
+#include "aom/aomcx.h"
+#include "av1/common/enums.h"
+
+#include "../tools_common.h"
+#include "../video_writer.h"
+
+static const char *exec_name;
+
+void usage_exit(void) {
+  fprintf(stderr,
+          "Usage: %s <codec> <width> <height> <infile0> <infile1> "
+          "<outfile> <frames to encode>\n"
+          "See comments in scalable_encoder.c for more information.\n",
+          exec_name);
+  exit(EXIT_FAILURE);
+}
+
+static int encode_frame(aom_codec_ctx_t *codec, aom_image_t *img,
+                        int frame_index, int flags, FILE *outfile) {
+  int got_pkts = 0;
+  aom_codec_iter_t iter = NULL;
+  const aom_codec_cx_pkt_t *pkt = NULL;
+  const aom_codec_err_t res =
+      aom_codec_encode(codec, img, frame_index, 1, flags);
+  if (res != AOM_CODEC_OK) die_codec(codec, "Failed to encode frame");
+
+  while ((pkt = aom_codec_get_cx_data(codec, &iter)) != NULL) {
+    got_pkts = 1;
+
+    if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
+      const int keyframe = (pkt->data.frame.flags & AOM_FRAME_IS_KEY) != 0;
+      if (fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, outfile) !=
+          pkt->data.frame.sz) {
+        die_codec(codec, "Failed to write compressed frame");
+      }
+      printf(keyframe ? "K" : ".");
+      printf(" %6d\n", (int)pkt->data.frame.sz);
+      fflush(stdout);
+    }
+  }
+
+  return got_pkts;
+}
+
+int main(int argc, char **argv) {
+  FILE *infile0 = NULL;
+  FILE *infile1 = NULL;
+  aom_codec_ctx_t codec;
+  aom_codec_enc_cfg_t cfg;
+  int frame_count = 0;
+  aom_image_t raw0, raw1;
+  aom_codec_err_t res;
+  AvxVideoInfo info;
+  const AvxInterface *encoder = NULL;
+  const int fps = 30;
+  const int bitrate = 200;
+  int keyframe_interval = 0;
+  int max_frames = 0;
+  int frames_encoded = 0;
+  const char *codec_arg = NULL;
+  const char *width_arg = NULL;
+  const char *height_arg = NULL;
+  const char *infile0_arg = NULL;
+  const char *infile1_arg = NULL;
+  const char *outfile_arg = NULL;
+  //  const char *keyframe_interval_arg = NULL;
+  FILE *outfile = NULL;
+
+  exec_name = argv[0];
+
+  // Clear explicitly, as simply assigning "{ 0 }" generates
+  // "missing-field-initializers" warning in some compilers.
+  memset(&info, 0, sizeof(info));
+
+  if (argc != 8) die("Invalid number of arguments");
+
+  codec_arg = argv[1];
+  width_arg = argv[2];
+  height_arg = argv[3];
+  infile0_arg = argv[4];
+  infile1_arg = argv[5];
+  outfile_arg = argv[6];
+  max_frames = (int)strtol(argv[7], NULL, 0);
+
+  encoder = get_aom_encoder_by_name(codec_arg);
+  if (!encoder) die("Unsupported codec.");
+
+  info.codec_fourcc = encoder->fourcc;
+  info.frame_width = (int)strtol(width_arg, NULL, 0);
+  info.frame_height = (int)strtol(height_arg, NULL, 0);
+  info.time_base.numerator = 1;
+  info.time_base.denominator = fps;
+
+  if (info.frame_width <= 0 || info.frame_height <= 0 ||
+      (info.frame_width % 2) != 0 || (info.frame_height % 2) != 0) {
+    die("Invalid frame size: %dx%d", info.frame_width, info.frame_height);
+  }
+
+  if (!aom_img_alloc(&raw0, AOM_IMG_FMT_I420, info.frame_width,
+                     info.frame_height, 1)) {
+    die("Failed to allocate image for layer 0.");
+  }
+  if (!aom_img_alloc(&raw1, AOM_IMG_FMT_I420, info.frame_width,
+                     info.frame_height, 1)) {
+    die("Failed to allocate image for layer 1.");
+  }
+
+  //  keyframe_interval = (int)strtol(keyframe_interval_arg, NULL, 0);
+  keyframe_interval = 100;
+  if (keyframe_interval < 0) die("Invalid keyframe interval value.");
+
+  printf("Using %s\n", aom_codec_iface_name(encoder->codec_interface()));
+
+  res = aom_codec_enc_config_default(encoder->codec_interface(), &cfg, 0);
+  if (res) die_codec(&codec, "Failed to get default codec config.");
+
+  cfg.g_w = info.frame_width;
+  cfg.g_h = info.frame_height;
+  cfg.g_timebase.num = info.time_base.numerator;
+  cfg.g_timebase.den = info.time_base.denominator;
+  cfg.rc_target_bitrate = bitrate;
+  cfg.g_error_resilient = 0;
+  cfg.g_lag_in_frames = 0;
+  cfg.rc_end_usage = AOM_Q;
+
+  outfile = fopen(outfile_arg, "wb");
+  if (!outfile) die("Failed to open %s for writing.", outfile_arg);
+
+  if (!(infile0 = fopen(infile0_arg, "rb")))
+    die("Failed to open %s for reading.", infile0_arg);
+  if (!(infile1 = fopen(infile1_arg, "rb")))
+    die("Failed to open %s for reading.", infile0_arg);
+
+  if (aom_codec_enc_init(&codec, encoder->codec_interface(), &cfg, 0))
+    die_codec(&codec, "Failed to initialize encoder");
+  if (aom_codec_control(&codec, AOME_SET_CPUUSED, 8))
+    die_codec(&codec, "Failed to set cpu to 8");
+
+  if (aom_codec_control(&codec, AOME_SET_NUMBER_SPATIAL_LAYERS, 2))
+    die_codec(&codec, "Failed to set number of spatial layers to 2");
+
+  // Encode frames.
+  while (aom_img_read(&raw0, infile0)) {
+    int flags = 0;
+
+    // configure and encode base layer
+
+    if (keyframe_interval > 0 && frames_encoded % keyframe_interval == 0)
+      flags |= AOM_EFLAG_FORCE_KF;
+    else
+      // use previous base layer (LAST) as sole reference
+      // save this frame as LAST to be used as reference by enhanmcent layer
+      // and next base layer
+      flags |= AOM_EFLAG_NO_REF_LAST2 | AOM_EFLAG_NO_REF_LAST3 |
+               AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF |
+               AOM_EFLAG_NO_REF_BWD | AOM_EFLAG_NO_REF_ARF2 |
+               AOM_EFLAG_NO_UPD_GF | AOM_EFLAG_NO_UPD_ARF |
+               AOM_EFLAG_NO_UPD_ENTROPY;
+    cfg.g_w = info.frame_width;
+    cfg.g_h = info.frame_height;
+    if (aom_codec_enc_config_set(&codec, &cfg))
+      die_codec(&codec, "Failed to set enc cfg for layer 0");
+    if (aom_codec_control(&codec, AOME_SET_ENHANCEMENT_LAYER_ID, 0))
+      die_codec(&codec, "Failed to set layer id to 0");
+    if (aom_codec_control(&codec, AOME_SET_CQ_LEVEL, 62))
+      die_codec(&codec, "Failed to set cq level");
+    encode_frame(&codec, &raw0, frame_count++, flags, outfile);
+
+    // configure and encode enhancement layer
+
+    //  use LAST (base layer) as sole reference
+    flags = AOM_EFLAG_NO_REF_LAST2 | AOM_EFLAG_NO_REF_LAST3 |
+            AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF | AOM_EFLAG_NO_REF_BWD |
+            AOM_EFLAG_NO_REF_ARF2 | AOM_EFLAG_NO_UPD_LAST |
+            AOM_EFLAG_NO_UPD_GF | AOM_EFLAG_NO_UPD_ARF |
+            AOM_EFLAG_NO_UPD_ENTROPY;
+    cfg.g_w = info.frame_width;
+    cfg.g_h = info.frame_height;
+    aom_img_read(&raw1, infile1);
+    if (aom_codec_enc_config_set(&codec, &cfg))
+      die_codec(&codec, "Failed to set enc cfg for layer 1");
+    if (aom_codec_control(&codec, AOME_SET_ENHANCEMENT_LAYER_ID, 1))
+      die_codec(&codec, "Failed to set layer id to 1");
+    if (aom_codec_control(&codec, AOME_SET_CQ_LEVEL, 10))
+      die_codec(&codec, "Failed to set cq level");
+    encode_frame(&codec, &raw1, frame_count++, flags, outfile);
+
+    frames_encoded++;
+
+    if (max_frames > 0 && frames_encoded >= max_frames) break;
+  }
+
+  // Flush encoder.
+  while (encode_frame(&codec, NULL, -1, 0, outfile)) continue;
+
+  printf("\n");
+  fclose(infile0);
+  fclose(infile1);
+  printf("Processed %d frames.\n", frame_count / 2);
+
+  aom_img_free(&raw0);
+  aom_img_free(&raw1);
+  if (aom_codec_destroy(&codec)) die_codec(&codec, "Failed to destroy codec.");
+
+  fclose(outfile);
+
+  return EXIT_SUCCESS;
+}
diff --git a/obudec.c b/obudec.c
index 8f1ca00..f859a68 100644
--- a/obudec.c
+++ b/obudec.c
@@ -19,10 +19,17 @@
 #include "av1/common/common.h"
 
 #define OBU_HEADER_SIZE_BYTES 1
+#if CONFIG_SCALABILITY
+#define OBU_HEADER_EXTENSION_SIZE_BYTES 1
+#endif
 
 #if CONFIG_OBU_NO_IVF
 int obu_read_temporal_unit(FILE *infile, uint8_t **buffer, size_t *bytes_read,
+#if CONFIG_SCALABILITY
+                           size_t *buffer_size, int last_layer_id) {
+#else
                            size_t *buffer_size) {
+#endif
   size_t ret;
   const size_t obu_length_header_size =
       PRE_OBU_SIZE_BYTES + OBU_HEADER_SIZE_BYTES;
@@ -60,6 +67,26 @@
       break;
     }
 
+#if CONFIG_SCALABILITY
+    // break if obu_extension_flag is found and enhancement_id change
+    if ((data[PRE_OBU_SIZE_BYTES] & 0x1)) {
+      uint8_t obu_extension_header;
+      int total_obu_header_size =
+          (int)obu_length_header_size + OBU_HEADER_EXTENSION_SIZE_BYTES;
+      int curr_layer_id;
+      fread(&obu_extension_header, 1, OBU_HEADER_EXTENSION_SIZE_BYTES, infile);
+      curr_layer_id = (obu_extension_header >> 3) & 0x3;
+      if (curr_layer_id && (curr_layer_id > last_layer_id)) {
+        // new enhancement layer
+        *bytes_read -= obu_length_header_size;
+        fseek(infile, -total_obu_header_size, SEEK_CUR);
+        break;
+      } else {
+        fseek(infile, -OBU_HEADER_EXTENSION_SIZE_BYTES, SEEK_CUR);
+      }
+    }
+#endif
+
 // otherwise, read the OBU payload into memory
 #if CONFIG_OBU_SIZING
     aom_uleb_decode(data, PRE_OBU_SIZE_BYTES, &obu_size);
@@ -112,6 +139,7 @@
     return 0;
   }
   // fprintf(stderr, "Starting to parse OBU stream\n");
+
   return 1;
 }
 
diff --git a/obudec.h b/obudec.h
index 899a35d..a7c4d1a 100644
--- a/obudec.h
+++ b/obudec.h
@@ -20,7 +20,11 @@
 int file_is_obu(struct AvxInputContext *input_ctx);
 
 int obu_read_temporal_unit(FILE *infile, uint8_t **buffer, size_t *bytes_read,
+#if CONFIG_SCALABILITY
+                           size_t *buffer_size, int last_layer_id);
+#else
                            size_t *buffer_size);
+#endif
 
 #ifdef __cplusplus
 } /* extern "C" */