add scalability experiment

configure:  --enable-experimental --enable-scalability

New applications:  scalable_encoder, scalable_decoder

scalable_encoder:
  * Encodes inputs as 2-layer (same size) stream
  * Encodes as obu file (OBU_NO_IVF must be enabled)
  * Base layer encoded in IPPPP where P's reference
    only the previous (in time) base layer
  * Enhancement layer encoded using its base layer as
    sole reference frame
  * Base layer encoded with fixed high QP
  * Enhancement layer encoded with fixed low QP

scalable_decoder:
  * Able to decode scalable stream generated by
    scalable_encoder
  * Able to decode any single-layer stream encoded
    by aomenc
  * Outputs base layer as out_lyr0.yuv, and enhancement
    layer (if they exist) as out_lyrN.yuv (N = 1, 2, 3, ..)
  * Able to decode N layers (more than 2)

Change-Id: I8555735db71e5b9b6f900ffdf978e0ad6f6bfc00
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 743dd0c..4d524c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -330,6 +330,10 @@
                  "${AOM_ROOT}/examples/simple_decoder.c"
                  $<TARGET_OBJECTS:aom_common_app_util>
                  $<TARGET_OBJECTS:aom_decoder_app_util>)
+  add_executable(scalable_decoder
+                 "${AOM_ROOT}/examples/scalable_decoder.c"
+                 $<TARGET_OBJECTS:aom_common_app_util>
+                 $<TARGET_OBJECTS:aom_decoder_app_util>)
 
   if (CONFIG_ANALYZER)
     add_executable(analyzer
@@ -368,7 +372,7 @@
 
   # Maintain a list of decoder example targets.
   set(AOM_DECODER_EXAMPLE_TARGETS ${AOM_DECODER_EXAMPLE_TARGETS}
-      aomdec decode_to_md5 decode_with_drops simple_decoder)
+      aomdec decode_to_md5 decode_with_drops simple_decoder scalable_decoder)
 
   # Add decoder examples to the app targets list.
   set(AOM_APP_TARGETS ${AOM_APP_TARGETS} ${AOM_DECODER_EXAMPLE_TARGETS})
@@ -393,6 +397,10 @@
                    "${AOM_ROOT}/examples/simple_encoder.c"
                    $<TARGET_OBJECTS:aom_common_app_util>
                    $<TARGET_OBJECTS:aom_encoder_app_util>)
+    add_executable(scalable_encoder
+                   "${AOM_ROOT}/examples/scalable_encoder.c"
+                   $<TARGET_OBJECTS:aom_common_app_util>
+                   $<TARGET_OBJECTS:aom_encoder_app_util>)
     add_executable(twopass_encoder
                    "${AOM_ROOT}/examples/twopass_encoder.c"
                    $<TARGET_OBJECTS:aom_common_app_util>
@@ -400,7 +408,7 @@
 
     # Maintain a list of encoder example targets.
     set(AOM_ENCODER_EXAMPLE_TARGETS
-        aomenc lossless_encoder set_maps simple_encoder twopass_encoder)
+        aomenc lossless_encoder set_maps simple_encoder scalable_encoder twopass_encoder)
   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 ceab934..d8e2b56 100644
--- a/aom/aom_decoder.h
+++ b/aom/aom_decoder.h
@@ -86,6 +86,9 @@
   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 */
+#if CONFIG_SCALABILITY
+  unsigned int enhancement_layers_cnt; /**< Enhancement layers */
+#endif
 } 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 5790145..a053f41 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
@@ -782,6 +790,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)
@@ -831,6 +842,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 0724ee1..3d314ed 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 07178de..5400cc2 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -1382,21 +1382,28 @@
       pkt.data.frame.partition_id = -1;
 
 #if CONFIG_OBU
-      // 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);
+      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();
+        mem_put_le32(ctx->pending_cx_data, obu_size);
+        pkt.data.frame.sz += (obu_size + PRE_OBU_SIZE_BYTES);
       }
-      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();
-      mem_put_le32(ctx->pending_cx_data, obu_size);
-      pkt.data.frame.sz += (obu_size + PRE_OBU_SIZE_BYTES);
-#endif
+#endif  // CONFIG_OBU
 
       pkt.data.frame.pts = ticks_to_timebase_units(timebase, dst_time_stamp);
       pkt.data.frame.flags = get_frame_pkt_flags(cpi, lib_flags);
@@ -1562,6 +1569,32 @@
   }
 }
 
+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);
+  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);
+  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;
@@ -1661,6 +1694,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 },
@@ -1685,6 +1719,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 ccc7851..164a090 100644
--- a/av1/av1_dx_iface.c
+++ b/av1/av1_dx_iface.c
@@ -31,6 +31,10 @@
 
 #include "av1/av1_iface_common.h"
 
+#if CONFIG_OBU
+#include "aom_ports/mem_ops.h"
+#endif
+
 // This limit is due to framebuffer numbers.
 // TODO(hkuang): Remove this limit after implementing ondemand framebuffers.
 #define FRAME_CACHE_SIZE 6  // Cache maximum 6 decoded frames.
@@ -292,10 +296,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;
@@ -925,6 +952,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
           return img;
         }
       } else {
diff --git a/av1/common/onyxc_int.h b/av1/common/onyxc_int.h
index dbf0839..cf54dcf 100644
--- a/av1/common/onyxc_int.h
+++ b/av1/common/onyxc_int.h
@@ -591,6 +591,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 5f8675c..ba076dc 100644
--- a/av1/decoder/obu.c
+++ b/av1/decoder/obu.c
@@ -21,8 +21,28 @@
 #include "av1/decoder/decoder.h"
 #include "av1/decoder/decodeframe.h"
 
+#if CONFIG_SCALABILITY
+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 +51,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 +73,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);
@@ -137,6 +172,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_pressent_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_pressent_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);
@@ -147,6 +227,10 @@
     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;
@@ -160,6 +244,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;
@@ -175,12 +262,25 @@
     // + payload size)
     // The obu size is only needed for tile group OBUs
     const size_t obu_size = mem_get_le32(data);
-    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 & 0xE0) >> 5;
+    cm->enhancement_layer_id = (obu_extension_header & 0x18) >> 3;
+#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 102cd23..f63c9f0 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -4338,14 +4338,29 @@
   return size;
 }
 
-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;
+#if CONFIG_SCALABILITY
+  int i;
+#endif
 
   write_profile(cm->profile, &wb);
 
   aom_wb_write_literal(&wb, 0, 4);
+#if CONFIG_SCALABILITY
+  aom_wb_write_literal(&wb, enhancement_layers_cnt, 2);
+  for (i = 1; i <= enhancement_layers_cnt; i++) {
+    aom_wb_write_literal(&wb, 0, 4);
+  }
+#endif
 
   write_sequence_header(cpi, &wb);
 
@@ -4410,7 +4425,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;
@@ -4577,8 +4593,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,
@@ -4656,13 +4672,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;
-#endif
-  (void)cm;
+#if CONFIG_SCALABILITY
+  uint8_t enhancement_layers_cnt = cm->enhancement_layers_cnt;
+  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
+
 #if CONFIG_BITSTREAM_DEBUG
   bitstream_queue_reset_write();
 #endif
@@ -4674,8 +4697,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
+
     mem_put_le32(data, obu_size);
     data += obu_size + PRE_OBU_SIZE_BYTES;
   }
@@ -4686,7 +4714,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
@@ -4702,17 +4731,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 4f6b039..9b0b90d 100644
--- a/build/cmake/aom_config_defaults.cmake
+++ b/build/cmake/aom_config_defaults.cmake
@@ -170,6 +170,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 0 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 2b56fc1..7066915 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..cc5ddeb 100644
--- a/docs.cmake
+++ b/docs.cmake
@@ -79,6 +79,7 @@
       "${AOM_ROOT}/examples/lossless_encoder.c"
       "${AOM_ROOT}/examples/set_maps.c"
       "${AOM_ROOT}/examples/simple_encoder.c"
+      "${AOM_ROOT}/examples/scalable_encoder.c"
       "${AOM_ROOT}/examples/twopass_encoder.c")
 
   set(AOM_DOXYGEN_EXAMPLE_DESCRIPTIONS
@@ -87,6 +88,7 @@
       "Simplified lossless encoder."
       "Set active and ROI maps."
       "Simplified encoder loop."
+      "Scalable encoder loop."
       "Two-pass encoder loop.")
 
   set(AOM_DOXYGEN_SECTIONS ${AOM_DOXYGEN_SECTIONS} "av1_encoder encoder")
diff --git a/examples/scalable_decoder.c b/examples/scalable_decoder.c
new file mode 100644
index 0000000..3412beb
--- /dev/null
+++ b/examples/scalable_decoder.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2016, 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
+// aom.
+//
+// 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);
+}
+
+#if !CONFIG_SCALABILITY || !CONFIG_OBU
+int main() {
+  warn("scalable_decoder needs CONFIG_SCALABILITY and CONFIG_OBU");
+  return 0;
+#else
+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, 0))
+      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;
+#endif
+}
diff --git a/examples/scalable_encoder.c b/examples/scalable_encoder.c
new file mode 100644
index 0000000..e6e25d5
--- /dev/null
+++ b/examples/scalable_encoder.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2016, 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.
+//
+// Initializing The Codec
+// ----------------------
+// The encoder is initialized by the following code.
+//
+// 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.
+//
+// Error Resiliency Features
+// -------------------------
+// Error resiliency is controlled by the g_error_resilient member of the
+// configuration structure. Use the `decode_with_drops` example to decode with
+// frames 5-10 dropped. Compare the output for a file encoded with this example
+// versus one encoded with the `simple_encoder` example.
+
+#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, AOM_DL_GOOD_QUALITY);
+  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;
+
+#if !CONFIG_SCALABILITY || !CONFIG_OBU
+  warn("scalable_encoder needs CONFIG_SCALABILITY and CONFIG_OBU");
+  return 0;
+#endif
+
+  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 9cc907a..ad847f6 100644
--- a/obudec.c
+++ b/obudec.c
@@ -19,10 +19,18 @@
 #include "av1/common/common.h"
 
 #define OBU_HEADER_SIZE_BYTES 1
+#if CONFIG_SCALABILITY
+#define OBU_HEADER_EXTENSION_SIZE_BYTES 1
+#define OBU_SEQUENCE_HEADER_BYTES 10
+#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 +68,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
     obu_size = mem_get_le32(data);
     // fprintf(stderr, "Found OBU of type %d and size %d\n",
@@ -101,6 +129,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" */