[codec][sysmem] codec sysmem - soft transition

Plumb sysmem via StreamProcessor interface.

For now, OnOutputConfig is still sent to make this a soft transition.

Tested: use_media_decoder, use_h264_decoder_test, mediaplayer_test_util, chromium fuchsia_video_decoder test
Change-Id: I13e3ae4f4be66a121dc097ff00bd136e04a56442
diff --git a/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx b/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx
deleted file mode 100644
index f2c4543..0000000
--- a/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-    "facets": {
-        "fuchsia.test": {
-            "injected-services": {
-                "fuchsia.mediacodec.CodecFactory": "fuchsia-pkg://fuchsia.com/codec_factory#meta/codec_factory.cmx"
-            }
-        }
-    },
-    "program": {
-        "binary": "test/use_h264_decoder_test"
-    },
-    "sandbox": {
-        "services": [
-            "fuchsia.mediacodec.CodecFactory"
-        ]
-    }
-}
diff --git a/garnet/bin/media/codec_factory/BUILD.gn b/garnet/bin/media/codec_factory/BUILD.gn
index a05c9a30..e4d9082 100644
--- a/garnet/bin/media/codec_factory/BUILD.gn
+++ b/garnet/bin/media/codec_factory/BUILD.gn
@@ -19,6 +19,6 @@
     "//zircon/public/lib/trace-provider",
   ]
 
-  # codec_factory spawns codec_runner_sw_omx(s) which is a peer package for now,
-  # so no dep is needed here for codec_runner_sw_omx.
+  # codec_factory spawns codec_runner_sw_ffmpeg(s) which is a peer package for now,
+  # so no dep is needed here for codec_runner_sw_ffmpeg.
 }
diff --git a/garnet/bin/media/codec_factory/codec_factory_impl.cc b/garnet/bin/media/codec_factory/codec_factory_impl.cc
index 9533597..e70c0ec 100644
--- a/garnet/bin/media/codec_factory/codec_factory_impl.cc
+++ b/garnet/bin/media/codec_factory/codec_factory_impl.cc
@@ -8,21 +8,20 @@
 
 namespace {
 
-// TODO(dustingreen): Other types of isolates can exist.  For some codecs we may
-// not use an isolate and instead delegate to the client end of a CodecFactory
-// instance that we got via other means (not by starting an isolate, but via a
-// factory registration initiated by a driver process, or by discovering a
-// device, or similar).
-const std::string kIsolateUrlOmx =
-    "fuchsia-pkg://fuchsia.com/codec_runner_sw_omx#meta/"
-    "codec_runner_sw_omx.cmx";
+// Other types of SW isolates can exist, but at the moment we only have one,
+// which uses ffmpeg for SW decode (or potentially encode).
+//
+// For HW-based codecs, we discover their "LocalCodecFactory" by watching for
+// their device and sending the server end of a (local) CodecFactory to the
+// driver.
 const std::string kIsolateUrlFfmpeg =
     "fuchsia-pkg://fuchsia.com/codec_runner_sw_ffmpeg#meta/"
     "codec_runner_sw_ffmpeg.cmx";
 
-// TODO(turnage): Devise a better routing system between ffmpeg and omx codec
-// factories. Using this should be fine for now because omx does not service
-// this mime type and it is the first one we will roll out with ffmpeg.
+// TODO(turnage): Devise a better routing system between SW-based codec
+// factories.  Using this should be fine for now since this is the first/only
+// type that we use ffmpeg for and we don't currently have any other SW-based
+// codecs.
 const std::string kFfmpegMimeType = "video/h264";
 
 }  // namespace
@@ -63,6 +62,11 @@
 void CodecFactoryImpl::OwnSelf(std::unique_ptr<CodecFactoryImpl> self) {
   binding_ = std::make_unique<BindingType>(
       std::move(self), std::move(channel_temp_), app_->loop()->dispatcher());
+  binding_->set_error_handler([this](zx_status_t status){
+    FXL_LOG(INFO) << "CodecFactoryImpl channel failed (INFO) - status: " << status;
+    // this will also ~this
+    binding_.reset();
+  });
 }
 
 void CodecFactoryImpl::CreateDecoder(
@@ -116,7 +120,8 @@
   if (params.input_details().mime_type() == kFfmpegMimeType) {
     url = kIsolateUrlFfmpeg;
   } else {
-    url = kIsolateUrlOmx;
+    // ~decoder
+    return;
   }
   launch_info.url = url;
   launch_info.directory_request = services.NewRequest();
diff --git a/garnet/bin/media/codecs/meta/codec_runner_sw_ffmpeg.cmx b/garnet/bin/media/codecs/meta/codec_runner_sw_ffmpeg.cmx
index 0d40751..0a1060c 100644
--- a/garnet/bin/media/codecs/meta/codec_runner_sw_ffmpeg.cmx
+++ b/garnet/bin/media/codecs/meta/codec_runner_sw_ffmpeg.cmx
@@ -5,7 +5,8 @@
     "sandbox": {
         "services": [
             "fuchsia.sys.Launcher",
-            "fuchsia.tracelink.Registry"
+            "fuchsia.tracelink.Registry",
+            "fuchsia.sysmem.Allocator"
         ]
     }
 }
diff --git a/garnet/bin/media/codecs/sw/codec_adapter_sw.h b/garnet/bin/media/codecs/sw/codec_adapter_sw.h
index d7f94b5..ee0a3da 100644
--- a/garnet/bin/media/codecs/sw/codec_adapter_sw.h
+++ b/garnet/bin/media/codecs/sw/codec_adapter_sw.h
@@ -52,6 +52,14 @@
     return false;
   }
 
+  bool IsCoreCodecMappedBufferNeeded(CodecPort port) override {
+    return true;
+  }
+
+  bool IsCoreCodecHwBased() override {
+    return false;
+  }
+
   void CoreCodecInit(const fuchsia::media::FormatDetails&
                          initial_input_format_details) override {
     if (!initial_input_format_details.has_format_details_version_ordinal()) {
@@ -74,6 +82,21 @@
     }
   }
 
+  void CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) override {
+    if (port != kOutputPort) {
+      return;
+    }
+    // TODO(turnage): The core codec must not emit any output (format, AUs, or
+    // EOS) until CoreCodecMidStreamOutputBufferReConfigFinish has been called.
+    output_buffer_pool_.AddBuffer(buffer);
+  }
+
+  void CoreCodecConfigureBuffers(
+      CodecPort port,
+      const std::vector<std::unique_ptr<CodecPacket>>& packets) override {
+    // Nothing to do here.
+  }
+
   void CoreCodecStartStream() override {
     // It's ok for RecycleInputPacket to make a packet free anywhere in this
     // sequence. Nothing else ought to be happening during CoreCodecStartStream
@@ -90,13 +113,6 @@
         post_result);
   }
 
-  void CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) override {
-    if (port != kOutputPort) {
-      return;
-    }
-    output_buffer_pool_.AddBuffer(buffer);
-  }
-
   void CoreCodecQueueInputFormatDetails(
       const fuchsia::media::FormatDetails& per_stream_override_format_details)
       override {
@@ -139,12 +155,6 @@
     }
   }
 
-  void CoreCodecConfigureBuffers(
-      CodecPort port,
-      const std::vector<std::unique_ptr<CodecPacket>>& packets) override {
-    // Nothing to do here.
-  }
-
   void CoreCodecRecycleOutputPacket(CodecPacket* packet) override {
     if (packet->buffer()) {
       LocalOutput local_output;
@@ -187,21 +197,23 @@
 
   void CoreCodecMidStreamOutputBufferReConfigFinish() override {
     // Nothing to do here for now.
+    //
+    // TODO(turnage): The core codec must not emit any output (format, AUs, or
+    // EOS) until CoreCodecMidStreamOutputBufferReConfigFinish has been called.
   }
 
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) override {
     auto [format_details, per_packet_buffer_bytes] = OutputFormatDetails();
 
-    auto config = std::make_unique<fuchsia::media::StreamOutputConfig>();
+    auto config = std::make_unique<fuchsia::media::StreamOutputConstraints>();
 
     config->set_stream_lifetime_ordinal(stream_lifetime_ordinal);
 
-    // For the moment, there will be only one StreamOutputConfig, and it'll need
+    // For the moment, there will be only one StreamOutputConstraints, and it'll need
     // output buffers configured for it.
     ZX_DEBUG_ASSERT(buffer_constraints_action_required);
     config->set_buffer_constraints_action_required(
@@ -243,13 +255,22 @@
     default_settings->set_per_packet_buffer_bytes(per_packet_buffer_bytes);
     default_settings->set_single_buffer_mode(false);
 
-    *config->mutable_format_details() = std::move(format_details);
-    config->mutable_format_details()->set_format_details_version_ordinal(
-        new_output_format_details_version_ordinal);
-
     return config;
   }
 
+  fuchsia::media::StreamOutputFormat CoreCodecGetOutputFormat(
+      uint64_t stream_lifetime_ordinal,
+      uint64_t new_output_format_details_version_ordinal) override {
+    fuchsia::media::StreamOutputFormat result;
+    // format_details is fuchsia::media::FormatDetails
+    auto [format_details, per_packet_buffer_bytes] = OutputFormatDetails();
+    result.set_stream_lifetime_ordinal(stream_lifetime_ordinal);
+    result.set_format_details(std::move(format_details));
+    result.mutable_format_details()->set_format_details_version_ordinal(
+        new_output_format_details_version_ordinal);
+    return result;
+  }
+
  protected:
   void WaitForInputProcessingLoopToEnd() {
     ZX_DEBUG_ASSERT(thrd_current() != input_processing_thread_);
diff --git a/garnet/bin/media/codecs/sw/codec_runner_app.h b/garnet/bin/media/codecs/sw/codec_runner_app.h
index 538287a..e3dd0cf 100644
--- a/garnet/bin/media/codecs/sw/codec_runner_app.h
+++ b/garnet/bin/media/codecs/sw/codec_runner_app.h
@@ -43,9 +43,12 @@
                   .deprecated_services()
                   ->RemoveService<fuchsia::mediacodec::CodecFactory>();
 
+              fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem;
+              startup_context_->ConnectToEnvironmentService(sysmem.NewRequest());
+
               codec_factory_ =
                   std::make_unique<LocalSingleCodecFactory<Decoder, Encoder>>(
-                      loop_.dispatcher(), std::move(request),
+                      loop_.dispatcher(), std::move(sysmem), std::move(request),
                       [this](
                           std::unique_ptr<CodecImpl> created_codec_instance) {
                         // Own codec implementation and bind it.
@@ -75,4 +78,4 @@
   std::unique_ptr<CodecImpl> codec_instance_;
 };
 
-#endif  // GARNET_BIN_MEDIA_CODECS_SW_CODEC_RUNNER_APP_H_
\ No newline at end of file
+#endif  // GARNET_BIN_MEDIA_CODECS_SW_CODEC_RUNNER_APP_H_
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/avcodec_context.cc b/garnet/bin/media/codecs/sw/ffmpeg/avcodec_context.cc
index e4baa42..038d9e6 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/avcodec_context.cc
+++ b/garnet/bin/media/codecs/sw/ffmpeg/avcodec_context.cc
@@ -173,10 +173,6 @@
         frame->sample_aspect_ratio.den;
   }
 
-  // TODO(dustingreen): Switching to FIDL table should make this not be
-  // required.
-  uncompressed_format.special_formats.set_temp_field_todo_remove(0);
-
   size_t buffer_bytes_needed = av_image_get_buffer_size(
       static_cast<AVPixelFormat>(frame->format), frame->width, frame->height,
       /*align=*/1);
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
index 20f0a22..0ec0b6f 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
+++ b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
@@ -26,6 +26,19 @@
 
 }  // namespace
 
+namespace {
+
+// A client using the min shouldn't necessarily expect performance to be
+// acceptable when running higher bit-rates.
+constexpr uint32_t kInputPerPacketBufferBytesMin = 8 * 1024;
+// This is an arbitrary cap for now.
+constexpr uint32_t kInputPerPacketBufferBytesMax = 4 * 1024 * 1024;
+
+// For now, this is the forced packet count for output.
+static constexpr uint32_t kOutputPacketCount = 21;
+
+}  // namespace
+
 CodecAdapterFfmpegDecoder::CodecAdapterFfmpegDecoder(
     std::mutex& lock, CodecAdapterEvents* codec_adapter_events)
     : CodecAdapterSW(lock, codec_adapter_events) {}
@@ -136,7 +149,7 @@
   }
 
   if (should_config_output) {
-    events_->onCoreCodecMidStreamOutputConfigChange(
+    events_->onCoreCodecMidStreamOutputConstraintsChange(
         /*output_re_config_required=*/need_new_buffers);
   }
 
@@ -224,4 +237,167 @@
                                      /*error_detected_before=*/false,
                                      /*error_detected_during=*/false);
   }
-}
\ No newline at end of file
+}
+
+fuchsia::sysmem::BufferCollectionConstraints
+CodecAdapterFfmpegDecoder::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  std::lock_guard<std::mutex> lock(lock_);
+
+  fuchsia::sysmem::BufferCollectionConstraints result;
+
+  // For now, we didn't report support for single_buffer_mode, and CodecImpl
+  // will have failed the codec already by this point if the client tried to
+  // use single_buffer_mode.
+  //
+  // TODO(dustingreen): Support single_buffer_mode on input (only).
+  ZX_DEBUG_ASSERT(!partial_settings.has_single_buffer_mode() || !partial_settings.single_buffer_mode());
+  // The CodecImpl won't hand us the sysmem token, so we shouldn't expect to
+  // have the token here.
+  ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token());
+
+  ZX_DEBUG_ASSERT(partial_settings.has_packet_count_for_server());
+  ZX_DEBUG_ASSERT(partial_settings.has_packet_count_for_client());
+  uint32_t packet_count = partial_settings.packet_count_for_server() + partial_settings.packet_count_for_client();
+
+  // For now this is true - when we plumb more flexible buffer count range this
+  // will change to account for a range.
+  ZX_DEBUG_ASSERT(port != kOutputPort || packet_count == kOutputPacketCount);
+
+  // TODO(MTWN-250): plumb/permit range of buffer count from further down,
+  // instead of single number frame_count, and set this to the actual
+  // stream-required # of reference frames + # that can concurrently decode.
+  // Packets and buffers are not the same thing, and we should permit the # of
+  // packets to be >= the # of buffers.  We shouldn't be
+  // allocating buffers on behalf of the client here, but until we plumb the
+  // range of frame_count and are more flexible on # of allocated buffers, we
+  // have to make sure there are at least as many buffers as packets.  We
+  // categorize the buffers as for camping and for slack.  This should change to
+  // be just the buffers needed for camping and maybe 1 for shared slack.  If
+  // the client wants more buffers the client can demand buffers in its own
+  // fuchsia::sysmem::BufferCollection::SetConstraints().
+  result.min_buffer_count_for_camping = partial_settings.packet_count_for_server();
+  ZX_DEBUG_ASSERT(result.min_buffer_count_for_dedicated_slack == 0);
+  ZX_DEBUG_ASSERT(result.min_buffer_count_for_shared_slack == 0);
+  // TODO: Uncap max_buffer_count, have both sides infer that packet count is
+  // at least as many as buffer_count.
+  result.max_buffer_count = packet_count;
+
+  uint32_t per_packet_buffer_bytes_min;
+  uint32_t per_packet_buffer_bytes_max;
+  if (port == kInputPort) {
+    per_packet_buffer_bytes_min = kInputPerPacketBufferBytesMin;
+    per_packet_buffer_bytes_max = kInputPerPacketBufferBytesMax;
+  } else {
+    ZX_ASSERT(decoded_output_info_.has_value());
+    auto& [uncompressed_format, per_packet_buffer_bytes] =
+        decoded_output_info_.value();
+
+    ZX_DEBUG_ASSERT(port == kOutputPort);
+    // NV12, based on min stride.
+    per_packet_buffer_bytes_min = uncompressed_format.primary_line_stride_bytes * uncompressed_format.primary_height_pixels * 3 / 2;
+    // At least for now, don't cap the per-packet buffer size for output.  The
+    // HW only cares about the portion we set up for output anyway, and the
+    // client has no way to force output to occur into portions of the output
+    // buffer beyond what's implied by the max supported image dimensions.
+    per_packet_buffer_bytes_max = 0xFFFFFFFF;
+  }
+
+  result.has_buffer_memory_constraints = true;
+  result.buffer_memory_constraints.min_size_bytes = per_packet_buffer_bytes_min;
+  result.buffer_memory_constraints.max_size_bytes = per_packet_buffer_bytes_max;
+
+  // These are all false because SW decode.
+  result.buffer_memory_constraints.physically_contiguous_required = false;
+  result.buffer_memory_constraints.secure_required = false;
+  result.buffer_memory_constraints.secure_permitted = false;
+
+  if (port == kOutputPort) {
+    ZX_ASSERT(decoded_output_info_.has_value());
+    auto& [uncompressed_format, per_packet_buffer_bytes] =
+        decoded_output_info_.value();
+
+    result.image_format_constraints_count = 1;
+    fuchsia::sysmem::ImageFormatConstraints& image_constraints =
+        result.image_format_constraints[0];
+    image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::YV12;
+    // TODO(MTWN-251): confirm that REC709 is always what we want here, or plumb
+    // actual YUV color space if it can ever be REC601_*.  Since 2020 and 2100
+    // are minimum 10 bits per Y sample and we're outputting NV12, 601 is the
+    // only other potential possibility here.
+    image_constraints.color_spaces_count = 1;
+    image_constraints.color_space[0].type =
+        fuchsia::sysmem::ColorSpaceType::REC709;
+
+    // The non-"required_" fields indicate the decoder's ability to potentially
+    // output frames at various dimensions as coded in the stream.  Aside from
+    // the current stream being somewhere in these bounds, these have nothing to
+    // do with the current stream in particular.
+    image_constraints.min_coded_width = 16;
+    image_constraints.max_coded_width = 3840;
+    image_constraints.min_coded_height = 16;
+    // This intentionally isn't the height of a 4k frame.  See
+    // max_coded_width_times_coded_height.  We intentionally constrain the max
+    // dimension in width or height to the width of a 4k frame.  While the HW
+    // might be able to go bigger than that as long as the other dimension is
+    // smaller to compensate, we don't really need to enable any larger than
+    // 4k's width in either dimension, so we don't.
+    image_constraints.max_coded_height = 3840;
+    image_constraints.min_bytes_per_row = 16;
+    // no hard-coded max stride, at least for now
+    image_constraints.max_bytes_per_row = 0xFFFFFFFF;
+    image_constraints.max_coded_width_times_coded_height = 3840 * 2160;
+    image_constraints.layers = 1;
+    image_constraints.coded_width_divisor = 16;
+    image_constraints.coded_height_divisor = 16;
+    image_constraints.bytes_per_row_divisor = 16;
+    // TODO(dustingreen): Since this is a producer that will always produce at
+    // offset 0 of a physical page, we don't really care if this field is
+    // consistent with any constraints re. what the HW can do.
+    image_constraints.start_offset_divisor = 1;
+    // Odd display dimensions are permitted, but these don't imply odd YV12
+    // dimensions - those are constrainted by coded_width_divisor and
+    // coded_height_divisor which are both 16.
+    image_constraints.display_width_divisor = 1;
+    image_constraints.display_height_divisor = 1;
+
+    // The decoder is producing frames and the decoder has no choice but to
+    // produce frames at their coded size.  The decoder wants to potentially be
+    // able to support a stream with dynamic resolution, potentially including
+    // dimensions both less than and greater than the dimensions that led to the
+    // current need to allocate a BufferCollection.  For this reason, the
+    // required_ fields are set to the exact current dimensions, and the
+    // permitted (non-required_) fields is set to the full potential range that
+    // the decoder could potentially output.  If an initiator wants to require a
+    // larger range of dimensions that includes the required range indicated
+    // here (via a-priori knowledge of the potential stream dimensions), an
+    // initiator is free to do so.
+    image_constraints.required_min_coded_width = uncompressed_format.primary_width_pixels;
+    image_constraints.required_max_coded_width = uncompressed_format.primary_width_pixels;
+    image_constraints.required_min_coded_height = uncompressed_format.primary_height_pixels;
+    image_constraints.required_max_coded_height = uncompressed_format.primary_height_pixels;
+    // As needed we might want to plumb more flexibility for the stride.
+    image_constraints.required_min_bytes_per_row = uncompressed_format.primary_line_stride_bytes;
+    image_constraints.required_max_bytes_per_row = uncompressed_format.primary_line_stride_bytes;
+  } else {
+    ZX_DEBUG_ASSERT(result.image_format_constraints_count == 0);
+  }
+
+  // We don't have to fill out usage - CodecImpl takes care of that.
+  ZX_DEBUG_ASSERT(!result.usage.cpu);
+  ZX_DEBUG_ASSERT(!result.usage.display);
+  ZX_DEBUG_ASSERT(!result.usage.vulkan);
+  ZX_DEBUG_ASSERT(!result.usage.video);
+
+  return result;
+}
+
+void CodecAdapterFfmpegDecoder::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  // TODO: Should uncap max_buffer_count and stop asserting this, or assert
+  // instead that buffer_count >= buffers for camping + dedicated slack.
+  ZX_DEBUG_ASSERT(port != kOutputPort || buffer_collection_info.buffer_count == kOutputPacketCount);
+}
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.h b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.h
index 3619b30..5495a75 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.h
+++ b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.h
@@ -17,6 +17,16 @@
                             CodecAdapterEvents* codec_adapter_events);
   ~CodecAdapterFfmpegDecoder();
 
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
+
  protected:
   // Processes input in a loop. Should only execute on input_processing_thread_.
   // Loops for the lifetime of a stream.
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.cc b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.cc
index 4d80a0c..567798a 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.cc
+++ b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.cc
@@ -14,6 +14,20 @@
 
 CodecAdapterFfmpegEncoder::~CodecAdapterFfmpegEncoder() = default;
 
+fuchsia::sysmem::BufferCollectionConstraints
+CodecAdapterFfmpegEncoder::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  ZX_ASSERT_MSG(false, "Not implemented.");
+}
+
+void CodecAdapterFfmpegEncoder::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  ZX_ASSERT(false && "not yet implemented");
+}
+
 void CodecAdapterFfmpegEncoder::ProcessInputLoop() {
   ZX_ASSERT_MSG(false, "Not implemented.");
 }
@@ -26,4 +40,4 @@
 CodecAdapterFfmpegEncoder::OutputFormatDetails() {
   ZX_ASSERT_MSG(false, "Not implemented.");
   return {fuchsia::media::FormatDetails{}, 0};
-}
\ No newline at end of file
+}
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.h b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.h
index 8bccf7c..d644476 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.h
+++ b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_encoder.h
@@ -16,6 +16,16 @@
                             CodecAdapterEvents* codec_adapter_events);
   ~CodecAdapterFfmpegEncoder();
 
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
+
  protected:
   // Processes input in a loop. Should only execute on input_processing_thread_.
   // Loops for the lifetime of a stream.
diff --git a/garnet/bin/media/codecs/sw/local_single_codec_factory.h b/garnet/bin/media/codecs/sw/local_single_codec_factory.h
index 4bd7233d..4857ab1 100644
--- a/garnet/bin/media/codecs/sw/local_single_codec_factory.h
+++ b/garnet/bin/media/codecs/sw/local_single_codec_factory.h
@@ -25,11 +25,13 @@
  public:
   LocalSingleCodecFactory(
       async_dispatcher_t* fidl_dispatcher,
+      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
       fidl::InterfaceRequest<CodecFactory> request,
       fit::function<void(std::unique_ptr<CodecImpl>)> factory_done_callback,
       CodecAdmissionControl* codec_admission_control,
       fit::function<void(zx_status_t)> error_handler)
       : fidl_dispatcher_(fidl_dispatcher),
+        sysmem_(std::move(sysmem)),
         binding_(this),
         factory_done_callback_(std::move(factory_done_callback)),
         codec_admission_control_(codec_admission_control) {
@@ -69,7 +71,14 @@
             return;
           }
 
+          if (!sysmem_) {
+            printf("VendCodecAdapter() only meant to be used once per LocalSingleCodecFactory\n");
+            // ~codec_request closes channel.
+            return;
+          }
+
           auto codec_impl = std::make_unique<CodecImpl>(
+              std::move(sysmem_),
               std::move(codec_admission), fidl_dispatcher_, thrd_current(),
               std::make_unique<Params>(std::move(params)),
               std::move(codec_request));
@@ -100,6 +109,7 @@
   }
 
   async_dispatcher_t* fidl_dispatcher_;
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem_;
   fidl::Binding<CodecFactory, LocalSingleCodecFactory*> binding_;
   // Returns the codec implementation and requests drop of self.
   fit::function<void(std::unique_ptr<CodecImpl>)> factory_done_callback_;
diff --git a/garnet/bin/media/codecs/test/test/raw_frames_test.cc b/garnet/bin/media/codecs/test/test/raw_frames_test.cc
index 297b135..e7e884f 100644
--- a/garnet/bin/media/codecs/test/test/raw_frames_test.cc
+++ b/garnet/bin/media/codecs/test/test/raw_frames_test.cc
@@ -90,12 +90,12 @@
     std::optional<RawFrames::Image> frame;
     size_t frames_sent = 0;
     while ((frame = raw_frames.Frame(frames_sent++))) {
-      auto config = std::make_shared<fuchsia::media::StreamOutputConfig>();
-      config->mutable_format_details()
+      auto format = std::make_shared<fuchsia::media::StreamOutputFormat>();
+      format->mutable_format_details()
           ->mutable_domain()
           ->video()
           .set_uncompressed(std::move(frame->format));
-      frame_sink->PutFrame(frames_sent, frame->vmo, frame->vmo_offset, config,
+      frame_sink->PutFrame(frames_sent, frame->vmo, frame->vmo_offset, format,
                            [] {});
     }
 
diff --git a/garnet/drivers/video/amlogic-decoder/BUILD.gn b/garnet/drivers/video/amlogic-decoder/BUILD.gn
index 0c79433..5d599e7 100644
--- a/garnet/drivers/video/amlogic-decoder/BUILD.gn
+++ b/garnet/drivers/video/amlogic-decoder/BUILD.gn
@@ -110,6 +110,7 @@
     ]
     public_deps = [
       "//zircon/public/banjo/ddk-protocol-amlogiccanvas",
+      "//zircon/public/banjo/ddk-protocol-sysmem",
       "//zircon/public/lib/async-cpp",
       "//zircon/public/lib/async-loop-cpp",
       "//zircon/public/lib/ddk",
diff --git a/garnet/drivers/video/amlogic-decoder/amlogic-video.cc b/garnet/drivers/video/amlogic-decoder/amlogic-video.cc
index b2cbe35..de1e885 100644
--- a/garnet/drivers/video/amlogic-decoder/amlogic-video.cc
+++ b/garnet/drivers/video/amlogic-decoder/amlogic-video.cc
@@ -658,27 +658,48 @@
   video_decoder_->SwappedIn();
 }
 
+fidl::InterfaceHandle<fuchsia::sysmem::Allocator> AmlogicVideo::ConnectToSysmem() {
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> client_end;
+  fidl::InterfaceRequest<fuchsia::sysmem::Allocator> server_end =
+      client_end.NewRequest();
+  zx_status_t connect_status =
+      sysmem_connect(&sysmem_, server_end.TakeChannel().release());
+  if (connect_status != ZX_OK) {
+    // failure
+    return fidl::InterfaceHandle<fuchsia::sysmem::Allocator>();
+  }
+  return client_end;
+}
+
 zx_status_t AmlogicVideo::InitRegisters(zx_device_t* parent) {
   parent_ = parent;
 
   zx_status_t status =
       device_get_protocol(parent_, ZX_PROTOCOL_PDEV, &pdev_);
-
   if (status != ZX_OK) {
     DECODE_ERROR("Failed to get parent protocol");
     return ZX_ERR_NO_MEMORY;
   }
+
+  status = device_get_protocol(parent_, ZX_PROTOCOL_SYSMEM, &sysmem_);
+  if (status != ZX_OK) {
+    DECODE_ERROR("Could not get SYSMEM protocol\n");
+    return status;
+  }
+
   status = device_get_protocol(parent_, ZX_PROTOCOL_AMLOGIC_CANVAS, &canvas_);
   if (status != ZX_OK) {
     DECODE_ERROR("Could not get video CANVAS protocol\n");
     return status;
   }
+
   pdev_device_info_t info;
   status = pdev_get_device_info(&pdev_, &info);
   if (status != ZX_OK) {
     DECODE_ERROR("pdev_get_device_info failed");
     return status;
   }
+
   switch (info.pid) {
     case PDEV_PID_AMLOGIC_S912:
       device_type_ = DeviceType::kGXM;
diff --git a/garnet/drivers/video/amlogic-decoder/amlogic-video.h b/garnet/drivers/video/amlogic-decoder/amlogic-video.h
index 3f916ca2..8bfdd9a 100644
--- a/garnet/drivers/video/amlogic-decoder/amlogic-video.h
+++ b/garnet/drivers/video/amlogic-decoder/amlogic-video.h
@@ -11,6 +11,7 @@
 #include <ddk/driver.h>
 #include <ddk/protocol/amlogiccanvas.h>
 #include <ddk/protocol/platform/device.h>
+#include <ddk/protocol/sysmem.h>
 #include <zircon/errors.h>
 #include <zircon/syscalls.h>
 #include <lib/zx/handle.h>
@@ -150,6 +151,10 @@
   __WARN_UNUSED_RESULT zx_status_t AllocateStreamBuffer(StreamBuffer* buffer,
                                                         uint32_t size);
 
+  // This gets started connecting to sysmem, but returns an InterfaceHandle
+  // instead of InterfacePtr so that the caller can bind to the dispatcher.
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> ConnectToSysmem();
+
  private:
   friend class TestH264;
   friend class TestMpeg2;
@@ -167,8 +172,9 @@
   void SwapInCurrentInstance() __TA_REQUIRES(video_decoder_lock_);
 
   zx_device_t* parent_ = nullptr;
-  pdev_protocol_t pdev_;
-  amlogic_canvas_protocol_t canvas_;
+  pdev_protocol_t pdev_{};
+  sysmem_protocol_t sysmem_{};
+  amlogic_canvas_protocol_t canvas_{};
   DeviceType device_type_ = DeviceType::kUnknown;
   std::unique_ptr<CbusRegisterIo> cbus_;
   std::unique_ptr<DosRegisterIo> dosbus_;
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
index 0d49c16..7e779e4 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
@@ -17,8 +17,8 @@
 //     the HW and send it to the Codec client, the other part to configure
 //     output buffers once the client has configured Codec output config based
 //     on the format info.  Wire up so that
-//     onCoreCodecMidStreamOutputConfigChange() gets called and so that
-//     CoreCodecBuildNewOutputConfig() will pick up the correct current format
+//     onCoreCodecMidStreamOutputConstraintsChange() gets called and so that
+//     CoreCodecBuildNewOutputConstraints() will pick up the correct current format
 //     info (whether still mid-stream, or at the start of a new stream that's
 //     starting before the mid-stream format change was processed for the old
 //     stream).
@@ -86,6 +86,16 @@
          (static_cast<uint32_t>(b) << 8) | static_cast<uint32_t>(a);
 }
 
+// A client using the min shouldn't necessarily expect performance to be
+// acceptable when running higher bit-rates.
+//
+// TODO(MTWN-249): Set this to ~8k or so.  For now, we have to boost the
+// per-packet buffer size up to fit the largest AUs we expect to decode, until
+// MTWN-249 is fixed, in case avcC format is used.
+constexpr uint32_t kInputPerPacketBufferBytesMin = 512 * 1024;
+// This is an arbitrary cap for now.
+constexpr uint32_t kInputPerPacketBufferBytesMax = 4 * 1024 * 1024;
+
 }  // namespace
 
 CodecAdapterH264::CodecAdapterH264(std::mutex& lock,
@@ -113,6 +123,23 @@
   return false;
 }
 
+bool CodecAdapterH264::IsCoreCodecMappedBufferNeeded(CodecPort port) {
+  // If protected buffers, then only in-band AnnexB is supported, because in
+  // that case we can't implement conversion of OOB AnnexB-like oob_bytes to
+  // in-band AnnexB (at least not yet, since that would require one buffer to be
+  // in normal non-protected RAM), and we can't implement avcC format conversion
+  // to AnnexB, because that would require the CPU reading from input buffers.
+  //
+  // TODO(dustingreen): Make the previous paragraph true.  For now we report
+  // true here to ensure we don't get secure buffers since the rest of this
+  // class doesn't yet constrain what it can do based on is_secure or not.
+  return true;
+}
+
+bool CodecAdapterH264::IsCoreCodecHwBased() {
+  return true;
+}
+
 void CodecAdapterH264::CoreCodecInit(
     const fuchsia::media::FormatDetails& initial_input_format_details) {
   zx_status_t result = input_processing_loop_.StartThread(
@@ -324,18 +351,25 @@
 
 void CodecAdapterH264::CoreCodecAddBuffer(CodecPort port,
                                           const CodecBuffer* buffer) {
+  if (port != kOutputPort) {
+    return;
+  }
   all_output_buffers_.push_back(buffer);
 }
 
 void CodecAdapterH264::CoreCodecConfigureBuffers(
     CodecPort port, const std::vector<std::unique_ptr<CodecPacket>>& packets) {
-  if (port == kOutputPort) {
-    ZX_DEBUG_ASSERT(all_output_packets_.empty());
-    ZX_DEBUG_ASSERT(!all_output_buffers_.empty());
-    ZX_DEBUG_ASSERT(all_output_buffers_.size() == packets.size());
-    for (auto& packet : packets) {
-      all_output_packets_.push_back(packet.get());
-    }
+  if (port != kOutputPort) {
+    return;
+  }
+  ZX_DEBUG_ASSERT(all_output_packets_.empty());
+  ZX_DEBUG_ASSERT(!all_output_buffers_.empty());
+  // TODO(dustingreen): Remove this assert - this CodecAdapter needs to stop
+  // forcing this to be true.  Or, set packet count based on buffer collection
+  // buffer_count, or enforce that packet count is >= buffer_count.
+  ZX_DEBUG_ASSERT(all_output_buffers_.size() == packets.size());
+  for (auto& packet : packets) {
+    all_output_packets_.push_back(packet.get());
   }
 }
 
@@ -399,11 +433,10 @@
   }
 }
 
-std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-CodecAdapterH264::CoreCodecBuildNewOutputConfig(
+std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+CodecAdapterH264::CoreCodecBuildNewOutputConstraints(
     uint64_t stream_lifetime_ordinal,
     uint64_t new_output_buffer_constraints_version_ordinal,
-    uint64_t new_output_format_details_version_ordinal,
     bool buffer_constraints_action_required) {
   // bear.h264 decodes into 320x192 YUV buffers, but the video display
   // dimensions are 320x180.  A the bottom of the buffer only .25 of the last
@@ -429,17 +462,17 @@
   // to camp on more frames than this.
   constexpr uint32_t kDefaultPacketCountForClient = kPacketCountForClientForced;
 
-  uint32_t per_packet_buffer_bytes = stride_ * height_ * 3 / 2;
+  uint32_t per_packet_buffer_bytes = min_stride_ * height_ * 3 / 2;
 
-  std::unique_ptr<fuchsia::media::StreamOutputConfig> config =
-      std::make_unique<fuchsia::media::StreamOutputConfig>();
+  std::unique_ptr<fuchsia::media::StreamOutputConstraints> config =
+      std::make_unique<fuchsia::media::StreamOutputConstraints>();
 
   config->set_stream_lifetime_ordinal(stream_lifetime_ordinal);
 
   auto* constraints = config->mutable_buffer_constraints();
   auto* default_settings = constraints->mutable_default_settings();
 
-  // For the moment, there will be only one StreamOutputConfig, and it'll need
+  // For the moment, there will be only one StreamOutputConstraints, and it'll need
   // output buffers configured for it.
   ZX_DEBUG_ASSERT(buffer_constraints_action_required);
   config->set_buffer_constraints_action_required(
@@ -495,9 +528,174 @@
   // not the client.
   constraints->set_very_temp_kludge_bti_handle(std::move(very_temp_kludge_bti));
 
-  config->mutable_format_details()->set_format_details_version_ordinal(
+  return config;
+}
+
+fuchsia::sysmem::BufferCollectionConstraints
+CodecAdapterH264::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  fuchsia::sysmem::BufferCollectionConstraints result;
+
+  // For now, we didn't report support for single_buffer_mode, and CodecImpl
+  // will have failed the codec already by this point if the client tried to
+  // use single_buffer_mode.
+  //
+  // TODO(dustingreen): Support single_buffer_mode on input (only).
+  ZX_DEBUG_ASSERT(!partial_settings.has_single_buffer_mode() || !partial_settings.single_buffer_mode());
+  // The CodecImpl won't hand us the sysmem token, so we shouldn't expect to
+  // have the token here.
+  ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token());
+
+  ZX_DEBUG_ASSERT(partial_settings.has_packet_count_for_server());
+  ZX_DEBUG_ASSERT(partial_settings.has_packet_count_for_client());
+  uint32_t packet_count =
+      partial_settings.packet_count_for_server() +
+      partial_settings.packet_count_for_client();
+
+  // For now this is true - when we plumb more flexible buffer count range this
+  // will change to account for a range.
+  ZX_DEBUG_ASSERT(port != kOutputPort || packet_count == packet_count_total_);
+
+  // TODO(MTWN-250): plumb/permit range of buffer count from further down,
+  // instead of single number frame_count, and set this to the actual
+  // stream-required # of reference frames + # that can concurrently decode.
+  // For the moment we demand that buffer_count equals packet_count equals
+  // packet_count_for_server() + packet_count_for_client(), which is too
+  // inflexible.  Also, we rely on the server setting exactly and only
+  // min_buffer_count_for_camping to packet_count_for_server() and the client
+  // setting exactly and only min_buffer_count_for_camping to
+  // packet_count_for_client().
+  result.min_buffer_count_for_camping =
+      partial_settings.packet_count_for_server();
+  // Some slack is nice overall, but avoid having each participant ask for
+  // dedicated slack.  Using sysmem the client will ask for it's own buffers for
+  // camping and any slack, so the codec doesn't need to ask for any extra on
+  // behalf of the client.
+  ZX_DEBUG_ASSERT(result.min_buffer_count_for_dedicated_slack == 0);
+  ZX_DEBUG_ASSERT(result.min_buffer_count_for_shared_slack == 0);
+  result.max_buffer_count = packet_count;
+
+  uint32_t per_packet_buffer_bytes_min;
+  uint32_t per_packet_buffer_bytes_max;
+  if (port == kInputPort) {
+    per_packet_buffer_bytes_min = kInputPerPacketBufferBytesMin;
+    per_packet_buffer_bytes_max = kInputPerPacketBufferBytesMax;
+  } else {
+    ZX_DEBUG_ASSERT(port == kOutputPort);
+    // NV12, based on min stride.
+    per_packet_buffer_bytes_min = min_stride_ * height_ * 3 / 2;
+    // At least for now, don't cap the per-packet buffer size for output.  The
+    // HW only cares about the portion we set up for output anyway, and the
+    // client has no way to force output to occur into portions of the output
+    // buffer beyond what's implied by the max supported image dimensions.
+    per_packet_buffer_bytes_max = 0xFFFFFFFF;
+  }
+
+  result.has_buffer_memory_constraints = true;
+  result.buffer_memory_constraints.min_size_bytes = per_packet_buffer_bytes_min;
+  result.buffer_memory_constraints.max_size_bytes = per_packet_buffer_bytes_max;
+  // amlogic requires physically contiguous on both input and output
+  result.buffer_memory_constraints.physically_contiguous_required = true;
+  result.buffer_memory_constraints.secure_required = false;
+  // This isn't expected to fully work at first, but allow getting as far as we
+  // can.
+  result.buffer_memory_constraints.secure_permitted = true;
+
+  if (port == kOutputPort) {
+    result.image_format_constraints_count = 1;
+    fuchsia::sysmem::ImageFormatConstraints& image_constraints =
+        result.image_format_constraints[0];
+    image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::NV12;
+    // TODO(MTWN-251): confirm that REC709 is always what we want here, or plumb
+    // actual YUV color space if it can ever be REC601_*.  Since 2020 and 2100
+    // are minimum 10 bits per Y sample and we're outputting NV12, 601 is the
+    // only other potential possibility here.
+    image_constraints.color_spaces_count = 1;
+    image_constraints.color_space[0].type =
+        fuchsia::sysmem::ColorSpaceType::REC709;
+
+    // The non-"required_" fields indicate the decoder's ability to potentially
+    // output frames at various dimensions as coded in the stream.  Aside from
+    // the current stream being somewhere in these bounds, these have nothing to
+    // do with the current stream in particular.
+    image_constraints.min_coded_width = 16;
+    image_constraints.max_coded_width = 3840;
+    image_constraints.min_coded_height = 16;
+    // This intentionally isn't the height of a 4k frame.  See
+    // max_coded_width_times_coded_height.  We intentionally constrain the max
+    // dimension in width or height to the width of a 4k frame.  While the HW
+    // might be able to go bigger than that as long as the other dimension is
+    // smaller to compensate, we don't really need to enable any larger than
+    // 4k's width in either dimension, so we don't.
+    image_constraints.max_coded_height = 3840;
+    image_constraints.min_bytes_per_row = 16;
+    // no hard-coded max stride, at least for now
+    image_constraints.max_bytes_per_row = 0xFFFFFFFF;
+    image_constraints.max_coded_width_times_coded_height = 3840 * 2160;
+    image_constraints.layers = 1;
+    image_constraints.coded_width_divisor = 16;
+    image_constraints.coded_height_divisor = 16;
+    image_constraints.bytes_per_row_divisor = 16;
+    // TODO(dustingreen): Since this is a producer that will always produce at
+    // offset 0 of a physical page, we don't really care if this field is
+    // consistent with any constraints re. what the HW can do.
+    image_constraints.start_offset_divisor = 1;
+    // Odd display dimensions are permitted, but these don't imply odd NV12
+    // dimensions - those are constrainted by coded_width_divisor and
+    // coded_height_divisor which are both 16.
+    image_constraints.display_width_divisor = 1;
+    image_constraints.display_height_divisor = 1;
+
+    // The decoder is producing frames and the decoder has no choice but to
+    // produce frames at their coded size.  The decoder wants to potentially be
+    // able to support a stream with dynamic resolution, potentially including
+    // dimensions both less than and greater than the dimensions that led to the
+    // current need to allocate a BufferCollection.  For this reason, the
+    // required_ fields are set to the exact current dimensions, and the
+    // permitted (non-required_) fields is set to the full potential range that
+    // the decoder could potentially output.  If an initiator wants to require a
+    // larger range of dimensions that includes the required range indicated
+    // here (via a-priori knowledge of the potential stream dimensions), an
+    // initiator is free to do so.
+    image_constraints.required_min_coded_width = width_;
+    image_constraints.required_max_coded_width = width_;
+    image_constraints.required_min_coded_height = height_;
+    image_constraints.required_max_coded_height = height_;
+  } else {
+    ZX_DEBUG_ASSERT(result.image_format_constraints_count == 0);
+  }
+
+  // We don't have to fill out usage - CodecImpl takes care of that.
+  ZX_DEBUG_ASSERT(!result.usage.cpu);
+  ZX_DEBUG_ASSERT(!result.usage.display);
+  ZX_DEBUG_ASSERT(!result.usage.vulkan);
+  ZX_DEBUG_ASSERT(!result.usage.video);
+
+  return result;
+}
+
+void CodecAdapterH264::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  ZX_DEBUG_ASSERT(buffer_collection_info.settings.buffer_settings.is_physically_contiguous);
+  ZX_DEBUG_ASSERT(buffer_collection_info.settings.buffer_settings.coherency_domain == fuchsia::sysmem::CoherencyDomain::Cpu);
+  if (port == kOutputPort) {
+    ZX_DEBUG_ASSERT(buffer_collection_info.settings.has_image_format_constraints);
+    ZX_DEBUG_ASSERT(buffer_collection_info.settings.image_format_constraints.pixel_format.type == fuchsia::sysmem::PixelFormatType::NV12);
+  }
+}
+
+fuchsia::media::StreamOutputFormat CodecAdapterH264::CoreCodecGetOutputFormat(
+    uint64_t stream_lifetime_ordinal,
+    uint64_t new_output_format_details_version_ordinal) {
+  fuchsia::media::StreamOutputFormat result;
+  result.set_stream_lifetime_ordinal(stream_lifetime_ordinal);
+  result.mutable_format_details()->set_format_details_version_ordinal(
       new_output_format_details_version_ordinal);
-  config->mutable_format_details()->set_mime_type("video/raw");
+
+  result.mutable_format_details()->set_mime_type("video/raw");
 
   // For the moment, we'll memcpy to NV12 without any extra padding.
   fuchsia::media::VideoUncompressedFormat video_uncompressed;
@@ -510,11 +708,11 @@
   // specify separately for primary / secondary.
   video_uncompressed.planar = true;
   video_uncompressed.swizzled = false;
-  video_uncompressed.primary_line_stride_bytes = stride_;
-  video_uncompressed.secondary_line_stride_bytes = stride_;
+  video_uncompressed.primary_line_stride_bytes = min_stride_;
+  video_uncompressed.secondary_line_stride_bytes = min_stride_;
   video_uncompressed.primary_start_offset = 0;
-  video_uncompressed.secondary_start_offset = stride_ * height_;
-  video_uncompressed.tertiary_start_offset = stride_ * height_ + 1;
+  video_uncompressed.secondary_start_offset = min_stride_ * height_;
+  video_uncompressed.tertiary_start_offset = min_stride_ * height_ + 1;
   video_uncompressed.primary_pixel_stride = 1;
   video_uncompressed.secondary_pixel_stride = 2;
   video_uncompressed.primary_display_width_pixels = display_width_;
@@ -523,17 +721,13 @@
   video_uncompressed.pixel_aspect_ratio_width = sar_width_;
   video_uncompressed.pixel_aspect_ratio_height = sar_height_;
 
-  // TODO(dustingreen): Switching to FIDL table should make this not be
-  // required.
-  video_uncompressed.special_formats.set_temp_field_todo_remove(0);
-
   fuchsia::media::VideoFormat video_format;
   video_format.set_uncompressed(std::move(video_uncompressed));
 
-  config->mutable_format_details()->mutable_domain()->set_video(
+  result.mutable_format_details()->mutable_domain()->set_video(
       std::move(video_format));
 
-  return config;
+  return result;
 }
 
 void CodecAdapterH264::CoreCodecMidStreamOutputBufferReConfigPrepare() {
@@ -566,7 +760,7 @@
     }
     width = width_;
     height = height_;
-    stride = stride_;
+    stride = min_stride_;
   }  // ~lock
   {  // scope lock
     std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
@@ -1026,7 +1220,7 @@
   // during InitializeStream().  Maybe delaying configuring of a canvas would
   // work, but in that case would the delayed configuring adversely impact
   // decoding performance consistency?  If we can do this, detect when we can,
-  // and call onCoreCodecMidStreamOutputConfigChange() but pass false instead of
+  // and call onCoreCodecMidStreamOutputConstraintsChange() but pass false instead of
   // true, and don't expect a response or block in here.  Still have to return
   // the vector of buffers, and will need to indicate which are actually
   // available to decode into.  The rest will get indicated via
@@ -1050,7 +1244,7 @@
     packet_count_total_ = frame_count;
     width_ = width;
     height_ = height;
-    stride_ = stride;
+    min_stride_ = stride;
     display_width_ = display_width;
     display_height_ = display_height;
     has_sar_ = has_sar;
@@ -1062,7 +1256,7 @@
   // CoreCodecMidStreamOutputBufferReConfigPrepare() and
   // CoreCodecMidStreamOutputBufferReConfigFinish() from the StreamControl
   // thread, _iff_ the client hasn't already moved on to a new stream by then.
-  events_->onCoreCodecMidStreamOutputConfigChange(true);
+  events_->onCoreCodecMidStreamOutputConstraintsChange(true);
 
   return ZX_OK;
 }
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.h b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.h
index 8319df2..cb52bd4 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.h
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.h
@@ -22,6 +22,9 @@
   ~CodecAdapterH264();
 
   bool IsCoreCodecRequiringOutputConfigForFormatDetection() override;
+  bool IsCoreCodecMappedBufferNeeded(CodecPort port) override;
+
+  bool IsCoreCodecHwBased() override;
   void CoreCodecInit(const fuchsia::media::FormatDetails&
                          initial_input_format_details) override;
   void CoreCodecStartStream() override;
@@ -37,12 +40,22 @@
       const std::vector<std::unique_ptr<CodecPacket>>& packets) override;
   void CoreCodecRecycleOutputPacket(CodecPacket* packet) override;
   void CoreCodecEnsureBuffersNotConfigured(CodecPort port) override;
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) override;
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
+  fuchsia::media::StreamOutputFormat CoreCodecGetOutputFormat(
+    uint64_t stream_lifetime_ordinal,
+    uint64_t new_output_format_details_version_ordinal) override;
   void CoreCodecMidStreamOutputBufferReConfigPrepare() override;
   void CoreCodecMidStreamOutputBufferReConfigFinish() override;
 
@@ -98,7 +111,7 @@
   uint32_t packet_count_total_ = 0;
   uint32_t width_ = 0;
   uint32_t height_ = 0;
-  uint32_t stride_ = 0;
+  uint32_t min_stride_ = 0;
   uint32_t display_width_ = 0;
   uint32_t display_height_ = 0;
   bool has_sar_ = false;
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.cc b/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.cc
index ac98b314..055f54a 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.cc
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.cc
@@ -27,11 +27,37 @@
   return false;
 }
 
+bool CodecAdapterMpeg2::IsCoreCodecMappedBufferNeeded(CodecPort port) {
+  // Since protected memory input/output isn't supported for mpeg2, may as well
+  // claim we need mapped buffers for now, in case we end up needing to re-pack
+  // input or fix output.
+  return true;
+}
+
+bool CodecAdapterMpeg2::IsCoreCodecHwBased() {
+  return true;
+}
+
 void CodecAdapterMpeg2::CoreCodecInit(
     const fuchsia::media::FormatDetails& initial_input_format_details) {
   ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
 }
 
+fuchsia::sysmem::BufferCollectionConstraints
+CodecAdapterMpeg2::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
+  return fuchsia::sysmem::BufferCollectionConstraints();
+}
+
+void CodecAdapterMpeg2::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
+}
+
 void CodecAdapterMpeg2::CoreCodecStartStream() {
   ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
 }
@@ -71,14 +97,21 @@
   ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
 }
 
-std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-CodecAdapterMpeg2::CoreCodecBuildNewOutputConfig(
+std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+CodecAdapterMpeg2::CoreCodecBuildNewOutputConstraints(
     uint64_t stream_lifetime_ordinal,
     uint64_t new_output_buffer_constraints_version_ordinal,
-    uint64_t new_output_format_details_version_ordinal,
     bool buffer_constraints_action_required) {
   ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
-  return std::make_unique<const fuchsia::media::StreamOutputConfig>();
+  return std::make_unique<const fuchsia::media::StreamOutputConstraints>();
+}
+
+fuchsia::media::StreamOutputFormat
+CodecAdapterMpeg2::CoreCodecGetOutputFormat(
+    uint64_t stream_lifetime_ordinal,
+    uint64_t new_output_format_details_version_ordinal) {
+  ZX_DEBUG_ASSERT_MSG(false, "not yet implemented");
+  return fuchsia::media::StreamOutputFormat();
 }
 
 void CodecAdapterMpeg2::CoreCodecMidStreamOutputBufferReConfigPrepare() {
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.h b/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.h
index 0209c14..3f3aa25 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.h
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_mpeg2.h
@@ -18,8 +18,19 @@
   ~CodecAdapterMpeg2();
 
   bool IsCoreCodecRequiringOutputConfigForFormatDetection() override;
+  bool IsCoreCodecMappedBufferNeeded(CodecPort port) override;
+  bool IsCoreCodecHwBased() override;
+
   void CoreCodecInit(const fuchsia::media::FormatDetails&
                          initial_input_format_details) override;
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
   void CoreCodecStartStream() override;
   void CoreCodecQueueInputFormatDetails(
       const fuchsia::media::FormatDetails& per_stream_override_format_details)
@@ -33,12 +44,15 @@
       const std::vector<std::unique_ptr<CodecPacket>>& packets) override;
   void CoreCodecRecycleOutputPacket(CodecPacket* packet) override;
   void CoreCodecEnsureBuffersNotConfigured(CodecPort port) override;
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) override;
+  fuchsia::media::StreamOutputFormat
+  CoreCodecGetOutputFormat(
+      uint64_t stream_lifetime_ordinal,
+      uint64_t new_output_format_details_version_ordinal) override;
   void CoreCodecMidStreamOutputBufferReConfigPrepare() override;
   void CoreCodecMidStreamOutputBufferReConfigFinish() override;
 
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
index 920e173..67be20f 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
@@ -18,8 +18,8 @@
 //     the HW and send it to the Codec client, the other part to configure
 //     output buffers once the client has configured Codec output config based
 //     on the format info.  Wire up so that
-//     onCoreCodecMidStreamOutputConfigChange() gets called and so that
-//     CoreCodecBuildNewOutputConfig() will pick up the correct current format
+//     onCoreCodecMidStreamOutputConstraintsChange() gets called and so that
+//     CoreCodecBuildNewOutputConstraints() will pick up the correct current format
 //     info (whether still mid-stream, or at the start of a new stream that's
 //     starting before the mid-stream format change was processed for the old
 //     stream).
@@ -105,6 +105,20 @@
   return false;
 }
 
+bool CodecAdapterVp9::IsCoreCodecMappedBufferNeeded(CodecPort port) {
+  // If buffers are protected, the decoder should/will call secmem TA to re-pack
+  // VP9 headers in the input.  Else the decoder will use a CPU mapping to do
+  // this repack.
+  //
+  // TODO(dustingreen): Make the previous paragraph true.  For now we have to
+  // re-pack using the CPU on REE side.
+  return true;
+}
+
+bool CodecAdapterVp9::IsCoreCodecHwBased() {
+  return true;
+}
+
 void CodecAdapterVp9::CoreCodecInit(
     const fuchsia::media::FormatDetails& initial_input_format_details) {
   zx_status_t result = input_processing_loop_.StartThread(
@@ -121,6 +135,26 @@
   // currently, but we should do more here and less there.
 }
 
+fuchsia::sysmem::BufferCollectionConstraints
+CodecAdapterVp9::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  ZX_ASSERT_MSG(false, "not yet implemented");
+  return fuchsia::sysmem::BufferCollectionConstraints();
+}
+
+void CodecAdapterVp9::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  ZX_DEBUG_ASSERT(buffer_collection_info.settings.buffer_settings.is_physically_contiguous);
+  ZX_DEBUG_ASSERT(buffer_collection_info.settings.buffer_settings.coherency_domain == fuchsia::sysmem::CoherencyDomain::Cpu);
+  if (port == kOutputPort) {
+    ZX_DEBUG_ASSERT(buffer_collection_info.settings.has_image_format_constraints);
+    ZX_DEBUG_ASSERT(buffer_collection_info.settings.image_format_constraints.pixel_format.type == fuchsia::sysmem::PixelFormatType::NV12);
+  }
+}
+
 // TODO(dustingreen): A lot of the stuff created in this method should be able
 // to get re-used from stream to stream. We'll probably want to factor out
 // create/init from stream init further down.
@@ -378,11 +412,10 @@
   }
 }
 
-std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-CodecAdapterVp9::CoreCodecBuildNewOutputConfig(
+std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+CodecAdapterVp9::CoreCodecBuildNewOutputConstraints(
     uint64_t stream_lifetime_ordinal,
     uint64_t new_output_buffer_constraints_version_ordinal,
-    uint64_t new_output_format_details_version_ordinal,
     bool buffer_constraints_action_required) {
   // bear.vp9 decodes into 320x192 YUV buffers, but the video display
   // dimensions are 320x180.  A the bottom of the buffer only .25 of the last
@@ -410,14 +443,14 @@
 
   uint32_t per_packet_buffer_bytes = stride_ * height_ * 3 / 2;
 
-  auto config = std::make_unique<fuchsia::media::StreamOutputConfig>();
+  auto config = std::make_unique<fuchsia::media::StreamOutputConstraints>();
 
   config->set_stream_lifetime_ordinal(stream_lifetime_ordinal);
 
   auto* constraints = config->mutable_buffer_constraints();
   auto* default_settings = constraints->mutable_default_settings();
 
-  // For the moment, there will be only one StreamOutputConfig, and it'll need
+  // For the moment, there will be only one StreamOutputConstraints, and it'll need
   // output buffers configured for it.
   ZX_DEBUG_ASSERT(buffer_constraints_action_required);
   config->set_buffer_constraints_action_required(
@@ -473,9 +506,18 @@
   // not the client.
   constraints->set_very_temp_kludge_bti_handle(std::move(very_temp_kludge_bti));
 
-  config->mutable_format_details()->set_format_details_version_ordinal(
+  return config;
+}
+
+fuchsia::media::StreamOutputFormat
+CodecAdapterVp9::CoreCodecGetOutputFormat(
+    uint64_t stream_lifetime_ordinal,
+    uint64_t new_output_format_details_version_ordinal) {
+  fuchsia::media::StreamOutputFormat result;
+  result.set_stream_lifetime_ordinal(stream_lifetime_ordinal);
+  result.mutable_format_details()->set_format_details_version_ordinal(
       new_output_format_details_version_ordinal);
-  config->mutable_format_details()->set_mime_type("video/raw");
+  result.mutable_format_details()->set_mime_type("video/raw");
 
   // For the moment, we'll memcpy to NV12 without any extra padding.
   fuchsia::media::VideoUncompressedFormat video_uncompressed;
@@ -501,17 +543,13 @@
   video_uncompressed.pixel_aspect_ratio_width = sar_width_;
   video_uncompressed.pixel_aspect_ratio_height = sar_height_;
 
-  // TODO(dustingreen): Switching to FIDL table should make this not be
-  // required.
-  video_uncompressed.special_formats.set_temp_field_todo_remove(0);
-
   fuchsia::media::VideoFormat video_format;
   video_format.set_uncompressed(std::move(video_uncompressed));
 
-  config->mutable_format_details()->mutable_domain()->set_video(
+  result.mutable_format_details()->mutable_domain()->set_video(
       std::move(video_format));
 
-  return config;
+  return result;
 }
 
 void CodecAdapterVp9::CoreCodecMidStreamOutputBufferReConfigPrepare() {
@@ -826,7 +864,7 @@
   // CoreCodecMidStreamOutputBufferReConfigPrepare() and
   // CoreCodecMidStreamOutputBufferReConfigFinish() from the StreamControl
   // thread, _iff_ the client hasn't already moved on to a new stream by then.
-  events_->onCoreCodecMidStreamOutputConfigChange(true);
+  events_->onCoreCodecMidStreamOutputConstraintsChange(true);
 
   return ZX_OK;
 }
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.h b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.h
index fbf8f972..86695dc1 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.h
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.h
@@ -28,8 +28,19 @@
   ~CodecAdapterVp9();
 
   bool IsCoreCodecRequiringOutputConfigForFormatDetection() override;
+  bool IsCoreCodecMappedBufferNeeded(CodecPort port) override;
+  bool IsCoreCodecHwBased() override;
+
   void CoreCodecInit(const fuchsia::media::FormatDetails&
                          initial_input_format_details) override;
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
   void CoreCodecStartStream() override;
   void CoreCodecQueueInputFormatDetails(
       const fuchsia::media::FormatDetails& per_stream_override_format_details)
@@ -43,12 +54,15 @@
       const std::vector<std::unique_ptr<CodecPacket>>& packets) override;
   void CoreCodecRecycleOutputPacket(CodecPacket* packet) override;
   void CoreCodecEnsureBuffersNotConfigured(CodecPort port) override;
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) override;
+  fuchsia::media::StreamOutputFormat
+  CoreCodecGetOutputFormat(
+      uint64_t stream_lifetime_ordinal,
+      uint64_t new_output_format_details_version_ordinal) override;
   void CoreCodecMidStreamOutputBufferReConfigPrepare() override;
   void CoreCodecMidStreamOutputBufferReConfigFinish() override;
 
diff --git a/garnet/drivers/video/amlogic-decoder/local_codec_factory.cc b/garnet/drivers/video/amlogic-decoder/local_codec_factory.cc
index 568f0d2..003d11b 100644
--- a/garnet/drivers/video/amlogic-decoder/local_codec_factory.cc
+++ b/garnet/drivers/video/amlogic-decoder/local_codec_factory.cc
@@ -231,7 +231,14 @@
           return;
         }
 
+        fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem =
+            device_->video()->ConnectToSysmem();
+        if (!sysmem) {
+          return;
+        }
+
         std::unique_ptr<CodecImpl> codec = std::make_unique<CodecImpl>(
+            std::move(sysmem),
             std::move(codec_admission),
             device_->driver()->shared_fidl_loop()->dispatcher(),
             device_->driver()->shared_fidl_thread(),
diff --git a/garnet/examples/media/use_media_decoder/main.cc b/garnet/examples/media/use_media_decoder/main.cc
index 24e532f..dd032cf 100644
--- a/garnet/examples/media/use_media_decoder/main.cc
+++ b/garnet/examples/media/use_media_decoder/main.cc
@@ -51,6 +51,10 @@
       ->ConnectToEnvironmentService<fuchsia::mediacodec::CodecFactory>(
           codec_factory.NewRequest());
 
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem;
+  startup_context->ConnectToEnvironmentService<fuchsia::sysmem::Allocator>(
+    sysmem.NewRequest());
+
   std::string input_file = command_line.positional_args()[0];
   std::string output_file;
   if (command_line.positional_args().size() >= 2) {
@@ -103,22 +107,25 @@
   fit::closure use_decoder;
   if (command_line.HasOption("aac_adts")) {
     use_decoder = [&main_loop, codec_factory = std::move(codec_factory),
+                   sysmem = std::move(sysmem),
                    input_file, output_file, &md]() mutable {
-      use_aac_decoder(&main_loop, std::move(codec_factory), input_file,
+      use_aac_decoder(&main_loop, std::move(codec_factory), std::move(sysmem), input_file,
                       output_file, md);
     };
   } else if (command_line.HasOption("h264")) {
     use_decoder = [&main_loop, codec_factory = std::move(codec_factory),
+                   sysmem = std::move(sysmem),
                    input_file, output_file, &md,
                    frame_sink = frame_sink.get()]() mutable {
-      use_h264_decoder(&main_loop, std::move(codec_factory), input_file,
+      use_h264_decoder(&main_loop, std::move(codec_factory), std::move(sysmem), input_file,
                        output_file, md, nullptr, nullptr, frame_sink);
     };
   } else if (command_line.HasOption("vp9")) {
     use_decoder = [&main_loop, codec_factory = std::move(codec_factory),
+                   sysmem = std::move(sysmem),
                    input_file, output_file, &md,
                    frame_sink = frame_sink.get()]() mutable {
-      use_vp9_decoder(&main_loop, std::move(codec_factory), input_file,
+      use_vp9_decoder(&main_loop, std::move(codec_factory), std::move(sysmem), input_file,
                       output_file, md, nullptr, frame_sink);
     };
   } else {
diff --git a/garnet/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx b/garnet/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx
index 19907f1..a364877 100644
--- a/garnet/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx
+++ b/garnet/examples/media/use_media_decoder/meta/use_h264_decoder_test.cmx
@@ -2,7 +2,8 @@
     "facets": {
         "fuchsia.test": {
             "injected-services": {
-                "fuchsia.mediacodec.CodecFactory": "fuchsia-pkg://fuchsia.com/codec_factory#meta/codec_factory.cmx"
+                "fuchsia.mediacodec.CodecFactory": "fuchsia-pkg://fuchsia.com/codec_factory#meta/codec_factory.cmx",
+                "fuchsia.sysmem.Allocator": "fuchsia-pkg://fuchsia.com/sysmem_connector#meta/sysmem_connector.cmx"
             }
         }
     },
@@ -11,13 +12,15 @@
     },
     "sandbox": {
         "dev": [
-            "class/media-codec"
+            "class/media-codec",
+            "class/sysmem"
         ],
         "services": [
             "fuchsia.sys.Environment",
             "fuchsia.mediacodec.CodecFactory",
             "fuchsia.sys.Launcher",
-            "fuchsia.tracelink.Registry"
+            "fuchsia.tracelink.Registry",
+            "fuchsia.sysmem.Allocator"
         ]
     }
 }
diff --git a/garnet/examples/media/use_media_decoder/meta/use_media_decoder.cmx b/garnet/examples/media/use_media_decoder/meta/use_media_decoder.cmx
index bddffad..443ecba 100644
--- a/garnet/examples/media/use_media_decoder/meta/use_media_decoder.cmx
+++ b/garnet/examples/media/use_media_decoder/meta/use_media_decoder.cmx
@@ -8,6 +8,7 @@
         ],
         "services": [
             "fuchsia.mediacodec.CodecFactory",
+            "fuchsia.sysmem.Allocator",
             "fuchsia.ui.scenic.Scenic"
         ]
     }
diff --git a/garnet/examples/media/use_media_decoder/test/use_h264_decoder_test.cc b/garnet/examples/media/use_media_decoder/test/use_h264_decoder_test.cc
index 4d7265a..11bdac8 100644
--- a/garnet/examples/media/use_media_decoder/test/use_h264_decoder_test.cc
+++ b/garnet/examples/media/use_media_decoder/test/use_h264_decoder_test.cc
@@ -28,10 +28,13 @@
 
 const std::map<uint32_t, const char*> GoldenSha256s = {
     {make_fourcc('Y', 'V', '1', '2'),
-     "f40cd9c876ef429da421cf4ae4a5a0df1795c4519ddac098a5c3f427f5566281"},
+     "39e861466dede78e5be008f85dba53efcee23b7a064170e4c00361383e67690d"},
+     // YV12 without SHA256_Update_VideoParameters():
+     // f3116ef8cf0f69c3d9316246a3896f96684f513ce9664b9b55e195c964cc64a0
     {make_fourcc('N', 'V', '1', '2'),
-     "29c86f37972b5b70768620f8f702c37f3579e0cd84bec7159900680075026460"}};
-
+     "2ab4b1f47636ac367b5cc0da2bf8d901a9e2b5db40126b50f5f75ee5b3b8c8df"}};
+     // NV12 without SHA256_Update_VideoParameters():
+     // 84ae3e279d8b85d3a3b10c06489d9ffb0a968d99baa498d20f28788c0090c1d5
 }  // namespace
 
 int main(int argc, char* argv[]) {
@@ -42,7 +45,7 @@
   codec_factory.set_error_handler([](zx_status_t status) {
     // TODO(dustingreen): get and print CodecFactory channel epitaph once that's
     // possible.
-    FXL_LOG(ERROR) << "codec_factory failed - unexpected";
+    FXL_LOG(FATAL) << "codec_factory failed - unexpected";
   });
 
   std::unique_ptr<component::StartupContext> startup_context =
@@ -51,13 +54,17 @@
       ->ConnectToEnvironmentService<fuchsia::mediacodec::CodecFactory>(
           codec_factory.NewRequest());
 
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem;
+  startup_context->ConnectToEnvironmentService<fuchsia::sysmem::Allocator>(
+    sysmem.NewRequest());
+
   printf("The test file is: %s\n", kInputFilePath);
   printf("Decoding test file and computing sha256...\n");
 
   uint8_t md[SHA256_DIGEST_LENGTH];
   std::vector<std::pair<bool, uint64_t>> timestamps;
   uint32_t fourcc;
-  use_h264_decoder(&main_loop, std::move(codec_factory), kInputFilePath, "", md,
+  use_h264_decoder(&main_loop, std::move(codec_factory), std::move(sysmem), kInputFilePath, "", md,
                    &timestamps, &fourcc, nullptr);
 
   std::set<uint64_t> expected_timestamps;
diff --git a/garnet/examples/media/use_media_decoder/use_aac_decoder.cc b/garnet/examples/media/use_media_decoder/use_aac_decoder.cc
index 74131e8..7a84d6d 100644
--- a/garnet/examples/media/use_media_decoder/use_aac_decoder.cc
+++ b/garnet/examples/media/use_media_decoder/use_aac_decoder.cc
@@ -137,6 +137,7 @@
 // out_md - SHA256_DIGEST_LENGTH bytes long
 void use_aac_decoder(async::Loop* main_loop,
                      fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                     fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem, 
                      const std::string& input_adts_file,
                      const std::string& output_wav_file, uint8_t* out_md) {
   memset(out_md, 0, SHA256_DIGEST_LENGTH);
@@ -244,7 +245,7 @@
   // loop is already running, and we want the error handler to be set up by
   // CodecClient in advance of the channel potentially being closed.
   VLOGF("before CodecClient::CodecClient()...\n");
-  CodecClient codec_client(&loop);
+  CodecClient codec_client(&loop, std::move(sysmem));
   async::PostTask(
       main_loop->dispatcher(),
       [&codec_factory, create_params = std::move(create_params),
@@ -392,13 +393,15 @@
     bool is_wav_initialized = false;
     SHA256_CTX sha256_ctx;
     SHA256_Init(&sha256_ctx);
-    // We allow the server to send multiple output format updates if it wants;
-    // see implementation of BlockingGetEmittedOutput() which will hide
-    // multiple configs before the first packet from this code.
+    // We allow the server to send multiple output constraints updates if it
+    // wants; see implementation of BlockingGetEmittedOutput() which will hide
+    // multiple constraints before the first packet from this code.  In contrast
+    // we assert if the server sends multiple format updates without any
+    // packets in between, as that's not compliant with protocol rules.
     //
     // In this example, we only deal with one output format once we start seeing
     // stream data show up, since WAV only supports a single format per file.
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> stream_config;
+    std::shared_ptr<const fuchsia::media::StreamOutputFormat> stream_format;
     while (true) {
       std::unique_ptr<CodecOutput> output =
           codec_client.BlockingGetEmittedOutput();
@@ -437,19 +440,21 @@
         Exit("broken server sent packet without buffer index");
       }
 
-      std::shared_ptr<const fuchsia::media::StreamOutputConfig> config =
-          output->config();
+      // We don't really care about output->constraints() here, for now.
+
+      std::shared_ptr<const fuchsia::media::StreamOutputFormat> format =
+          output->format();
       // This will remain live long enough because this thread is the only
       // thread that re-allocates output buffers.
       const CodecBuffer& buffer =
           codec_client.GetOutputBufferByIndex(packet.header().packet_index());
 
-      ZX_ASSERT(!stream_config || stream_config->has_format_details());
-      if (stream_config &&
-          (!config->has_format_details() ||
-           !config->format_details().has_format_details_version_ordinal() ||
-           config->format_details().format_details_version_ordinal() !=
-               stream_config->format_details()
+      ZX_ASSERT(!stream_format || stream_format->has_format_details());
+      if (stream_format &&
+          (!format->has_format_details() ||
+           !format->format_details().has_format_details_version_ordinal() ||
+           format->format_details().format_details_version_ordinal() !=
+               stream_format->format_details()
                     .format_details_version_ordinal())) {
         Exit(
             "codec server unexpectedly changed output format mid-stream - "
@@ -469,24 +474,24 @@
 
       // We have a non-empty packet of the stream.
 
-      if (!stream_config) {
+      if (!stream_format) {
         // Every output has a config.  This happens exactly once.
-        stream_config = config;
+        stream_format = format;
 
-        if (!stream_config->has_format_details()) {
+        if (!stream_format->has_format_details()) {
           Exit("!format_details");
         }
 
-        const fuchsia::media::FormatDetails& format =
-            stream_config->format_details();
-        if (!format.has_domain()) {
+        const fuchsia::media::FormatDetails& format_details =
+            stream_format->format_details();
+        if (!format_details.has_domain()) {
           Exit("!format.domain");
         }
 
-        if (!format.domain().is_audio()) {
+        if (!format_details.domain().is_audio()) {
           Exit("!format.domain.is_audio() - unexpected");
         }
-        const fuchsia::media::AudioFormat& audio = format.domain().audio();
+        const fuchsia::media::AudioFormat& audio = format_details.domain().audio();
         if (!audio.is_uncompressed()) {
           Exit("!audio.is_uncompressed() - unexpected");
         }
diff --git a/garnet/examples/media/use_media_decoder/use_aac_decoder.h b/garnet/examples/media/use_media_decoder/use_aac_decoder.h
index d56fe43..6d556b5 100644
--- a/garnet/examples/media/use_media_decoder/use_aac_decoder.h
+++ b/garnet/examples/media/use_media_decoder/use_aac_decoder.h
@@ -34,6 +34,7 @@
 //     be set.
 void use_aac_decoder(async::Loop* main_loop,
                      fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                     fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
                      const std::string& input_adts_file,
                      const std::string& output_wav_file,
                      uint8_t out_md[SHA256_DIGEST_LENGTH]);
diff --git a/garnet/examples/media/use_media_decoder/use_video_decoder.cc b/garnet/examples/media/use_media_decoder/use_video_decoder.cc
index e3ffc75..c4d84c1 100644
--- a/garnet/examples/media/use_media_decoder/use_video_decoder.cc
+++ b/garnet/examples/media/use_media_decoder/use_video_decoder.cc
@@ -252,6 +252,7 @@
 
 static void use_video_decoder(
     async::Loop* main_loop, fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+    fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
     Format format, const std::string& input_file,
     const std::string& output_file, uint8_t md_out[SHA256_DIGEST_LENGTH],
     std::vector<std::pair<bool, uint64_t>>* timestamps_out, uint32_t* fourcc,
@@ -260,7 +261,7 @@
   FXL_DCHECK(!timestamps_out || timestamps_out->empty());
   memset(md_out, 0, SHA256_DIGEST_LENGTH);
   async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
-  loop.StartThread("use_h264_decoder_loop");
+  loop.StartThread("use_video_decoder_loop");
 
   // payload data for bear.h264 is 00 00 00 01 start code before each NAL, with
   // SPS / PPS NALs and also frame NALs.  We deliver to Codec NAL-by-NAL without
@@ -279,7 +280,7 @@
   // document in codec.fidl how that's to be handled.
 
   VLOGF("before CodecClient::CodecClient()...\n");
-  CodecClient codec_client(&loop);
+  CodecClient codec_client(&loop, std::move(sysmem));
 
   const char* mime_type;
   switch (format) {
@@ -372,14 +373,16 @@
         output_file.c_str());
     SHA256_CTX sha256_ctx;
     SHA256_Init(&sha256_ctx);
-    // We allow the server to send multiple output format updates if it wants;
-    // see implementation of BlockingGetEmittedOutput() which will hide
-    // multiple configs before the first packet from this code.
+    // We allow the server to send multiple output constraint updates if it
+    // wants; see implementation of BlockingGetEmittedOutput() which will hide
+    // multiple constraint updates before the first packet from this code.  In
+    // contrast we assert if the server sends multiple format updates with no
+    // packets in between since that's not compliant with the protocol rules.
     //
     // In this example, we only deal with one output format once we start seeing
     // stream output data show up, since our raw_video_writer is only really
     // meant to store one format per file.
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> stream_config;
+    std::shared_ptr<const fuchsia::media::StreamOutputFormat> stream_format;
     const fuchsia::media::VideoUncompressedFormat* raw = nullptr;
     while (true) {
       std::unique_ptr<CodecOutput> output =
@@ -418,8 +421,8 @@
             // set state command, so if that occurs, exit.
             codec_client.RecycleOutputPacket(std::move(packet_header));
           });
-      std::shared_ptr<const fuchsia::media::StreamOutputConfig> config =
-          output->config();
+      std::shared_ptr<const fuchsia::media::StreamOutputFormat> format =
+          output->format();
 
       if (!packet.has_buffer_index()) {
         // The server should not generate any empty packets.
@@ -432,14 +435,14 @@
           codec_client.GetOutputBufferByIndex(packet.buffer_index());
 
       ZX_ASSERT(
-          !stream_config ||
-          (stream_config->has_format_details() &&
-           stream_config->format_details().format_details_version_ordinal()));
-      if (stream_config &&
-          (!config->has_format_details() ||
-           !config->format_details().has_format_details_version_ordinal() ||
-           config->format_details().format_details_version_ordinal() !=
-               stream_config->format_details()
+          !stream_format ||
+          (stream_format->has_format_details() &&
+           stream_format->format_details().format_details_version_ordinal()));
+      if (stream_format &&
+          (!format->has_format_details() ||
+           !format->format_details().has_format_details_version_ordinal() ||
+           format->format_details().format_details_version_ordinal() !=
+               stream_format->format_details()
                     .format_details_version_ordinal())) {
         Exit(
             "codec server unexpectedly changed output format mid-stream - "
@@ -459,27 +462,27 @@
 
       // We have a non-empty packet of the stream.
 
-      if (!stream_config) {
-        // Every output has a config.  This happens exactly once.
-        stream_config = config;
+      if (!stream_format) {
+        // Every output has a format.  This happens exactly once.
+        stream_format = format;
 
-        ZX_ASSERT(config->format_details().has_domain());
+        ZX_ASSERT(format->format_details().has_domain());
 
-        if (!stream_config->has_format_details()) {
+        if (!stream_format->has_format_details()) {
           Exit("!format_details");
         }
 
-        const fuchsia::media::FormatDetails& format =
-            stream_config->format_details();
-        if (!format.has_domain()) {
+        const fuchsia::media::FormatDetails& format_details =
+            stream_format->format_details();
+        if (!format_details.has_domain()) {
           Exit("!format.domain");
         }
 
-        if (!format.domain().is_video()) {
+        if (!format_details.domain().is_video()) {
           Exit("!format.domain.is_video()");
         }
         const fuchsia::media::VideoFormat& video_format =
-            format.domain().video();
+            format_details.domain().video();
         if (!video_format.is_uncompressed()) {
           Exit("!video.is_uncompressed()");
         }
@@ -621,8 +624,8 @@
              &vmo = buffer.vmo(),
              vmo_offset = buffer.vmo_offset() + packet.start_offset() +
                           raw->primary_start_offset,
-             config, cleanup = std::move(cleanup)]() mutable {
-              frame_sink->PutFrame(image_id, vmo, vmo_offset, config,
+             format, cleanup = std::move(cleanup)]() mutable {
+              frame_sink->PutFrame(image_id, vmo, vmo_offset, format,
                                    [cleanup = std::move(cleanup)] {
                                      // The ~cleanup can run on any thread (the
                                      // current thread is main_loop's thread),
@@ -746,7 +749,7 @@
   loop.JoinThreads();
   VLOGF("after loop.JoinThreads()\n");
 
-  // Close the channel explicitly (just so we can more easily print messages
+  // Close the channels explicitly (just so we can more easily print messages
   // before and after vs. ~codec_client).
   VLOGF("before codec_client stop...\n");
   codec_client.Stop();
@@ -773,24 +776,26 @@
 
 void use_h264_decoder(async::Loop* main_loop,
                       fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
                       const std::string& input_file,
                       const std::string& output_file,
                       uint8_t md_out[SHA256_DIGEST_LENGTH],
                       std::vector<std::pair<bool, uint64_t>>* timestamps_out,
                       uint32_t* fourcc, FrameSink* frame_sink) {
-  use_video_decoder(main_loop, std::move(codec_factory), Format::kH264,
+  use_video_decoder(main_loop, std::move(codec_factory), std::move(sysmem), Format::kH264,
                     input_file, output_file, md_out, timestamps_out, fourcc,
                     frame_sink);
 }
 
 void use_vp9_decoder(async::Loop* main_loop,
                      fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
                      const std::string& input_file,
                      const std::string& output_file,
                      uint8_t md_out[SHA256_DIGEST_LENGTH],
                      std::vector<std::pair<bool, uint64_t>>* timestamps_out,
                      FrameSink* frame_sink) {
-  use_video_decoder(main_loop, std::move(codec_factory), Format::kVp9,
+  use_video_decoder(main_loop, std::move(codec_factory), std::move(sysmem), Format::kVp9,
                     input_file, output_file, md_out, timestamps_out, nullptr,
                     frame_sink);
 }
diff --git a/garnet/examples/media/use_media_decoder/use_video_decoder.h b/garnet/examples/media/use_media_decoder/use_video_decoder.h
index 9413a7f..4223136 100644
--- a/garnet/examples/media/use_media_decoder/use_video_decoder.h
+++ b/garnet/examples/media/use_media_decoder/use_video_decoder.h
@@ -43,6 +43,7 @@
 //     call back when the frame has been released by the sink.
 void use_h264_decoder(async::Loop* main_loop,
                       fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
                       const std::string& input_file,
                       const std::string& output_file,
                       uint8_t md_out[SHA256_DIGEST_LENGTH],
@@ -52,6 +53,7 @@
 // The same as use_h264_decoder, but for a VP9 file wrapped in an IVF container.
 void use_vp9_decoder(async::Loop* main_loop,
                      fuchsia::mediacodec::CodecFactoryPtr codec_factory,
+                      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
                      const std::string& input_file,
                      const std::string& output_file,
                      uint8_t md_out[SHA256_DIGEST_LENGTH],
diff --git a/garnet/examples/media/use_media_decoder/util.cc b/garnet/examples/media/use_media_decoder/util.cc
index ae6174e..535f5ba 100644
--- a/garnet/examples/media/use_media_decoder/util.cc
+++ b/garnet/examples/media/use_media_decoder/util.cc
@@ -117,8 +117,6 @@
   UpdateSha256(sha256_ctx, video.tertiary_start_offset);
   UpdateSha256(sha256_ctx, video.primary_pixel_stride);
   UpdateSha256(sha256_ctx, video.secondary_pixel_stride);
-  UpdateSha256(sha256_ctx, video.special_formats.is_temp_field_todo_remove());
-  UpdateSha256(sha256_ctx, video.special_formats.temp_field_todo_remove());
 }
 
 void SHA256_Update_VideoPlane(SHA256_CTX* sha256_ctx, uint8_t* start,
diff --git a/garnet/lib/media/codec_impl/codec_impl.cc b/garnet/lib/media/codec_impl/codec_impl.cc
index 0ea822f..58af372 100644
--- a/garnet/lib/media/codec_impl/codec_impl.cc
+++ b/garnet/lib/media/codec_impl/codec_impl.cc
@@ -62,8 +62,10 @@
 constexpr uint32_t kInputDefaultPacketCountForCodec =
     kInputPacketCountForCodecRecommended;
 
+constexpr uint32_t kInputPacketCountForClientMin = 1;
 constexpr uint32_t kInputPacketCountForClientMax =
     std::numeric_limits<uint32_t>::max();
+
 // This is fairly arbitrary, but rough speaking, 1 to be filling, 1 to be in
 // flight toward the codec, and 1 to be in flight from the codec.  This doesn't
 // intend to be large enough to ride out any hypothetical decoder performance
@@ -124,43 +126,30 @@
   DISALLOW_COPY_ASSIGN_AND_MOVE(ScopedRelock);
 };
 
-uint32_t PacketCountFromPortSettings(
-    const fuchsia::media::StreamBufferSettings& settings) {
-  ZX_DEBUG_ASSERT(settings.has_packet_count_for_server());
-  ZX_DEBUG_ASSERT(settings.has_packet_count_for_client());
-  return settings.packet_count_for_server() +
-         settings.packet_count_for_client();
-}
-
-uint32_t BufferCountFromPortSettings(
-    const fuchsia::media::StreamBufferSettings& settings) {
-  if (settings.has_single_buffer_mode() && settings.single_buffer_mode()) {
-    return 1;
-  }
-  return PacketCountFromPortSettings(settings);
-}
-
 }  // namespace
 
 CodecImpl::CodecImpl(
+    fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
     std::unique_ptr<CodecAdmission> codec_admission,
     async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
     std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
     fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request)
-    : CodecImpl(std::move(codec_admission), shared_fidl_dispatcher,
-                shared_fidl_thread, std::move(decoder_params), nullptr,
-                std::move(codec_request)) {}
+    : CodecImpl(std::move(sysmem), std::move(codec_admission),
+                shared_fidl_dispatcher, shared_fidl_thread,
+                std::move(decoder_params), nullptr, std::move(codec_request)) {}
 
 CodecImpl::CodecImpl(
+    fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
     std::unique_ptr<CodecAdmission> codec_admission,
     async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
     std::unique_ptr<fuchsia::mediacodec::CreateEncoder_Params> encoder_params,
     fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request)
-    : CodecImpl(std::move(codec_admission), shared_fidl_dispatcher,
-                shared_fidl_thread, nullptr, std::move(encoder_params),
-                std::move(codec_request)) {}
+    : CodecImpl(std::move(sysmem), std::move(codec_admission),
+                shared_fidl_dispatcher, shared_fidl_thread, nullptr,
+                std::move(encoder_params), std::move(codec_request)) {}
 
 CodecImpl::CodecImpl(
+    fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
     std::unique_ptr<CodecAdmission> codec_admission,
     async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
     std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
@@ -171,18 +160,31 @@
       codec_admission_(std::move(codec_admission)),
       shared_fidl_dispatcher_(shared_fidl_dispatcher),
       shared_fidl_thread_(shared_fidl_thread),
-      // TODO(dustingreen): Maybe have another parameter for encoder params, or
-      // maybe separate constructor.
       decoder_params_(std::move(decoder_params)),
       encoder_params_(std::move(encoder_params)),
+      tmp_sysmem_(std::move(sysmem)),
       tmp_interface_request_(std::move(codec_request)),
       binding_(this),
       stream_control_loop_(&kAsyncLoopConfigNoAttachToThread) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
   ZX_DEBUG_ASSERT(!!decoder_params_ ^ !!encoder_params_);
+  ZX_DEBUG_ASSERT(tmp_sysmem_);
   ZX_DEBUG_ASSERT(tmp_interface_request_);
+
+  // If the fuchsia::sysmem::Allocator connection dies, so does this CodecImpl.
+  sysmem_.set_error_handler([this](zx_status_t status) {
+    // This handler can't run until after sysmem_ is bound.
+    ZX_DEBUG_ASSERT(was_logically_bound_);
+    this->Fail("CodecImpl sysmem_ channel failed");
+  });
+
   // This is the binding_'s error handler, not the owner_error_handler_ which
   // is related but separate.
-  binding_.set_error_handler([this](zx_status_t status) { this->Unbind(); });
+  binding_.set_error_handler([this](zx_status_t status) {
+    // This handler can't run until after binding_ is bound.
+    ZX_DEBUG_ASSERT(was_logically_bound_);
+    this->Fail("CodecImpl binding_ channel failed");
+  });
   initial_input_format_details_ = decoder_params_
                                       ? &decoder_params_->input_details()
                                       : &encoder_params_->input_format();
@@ -270,9 +272,16 @@
     // of that dispatching would tend to land in FailLocked().  The concurrency
     // is just worth keeping in mind for the rest of the current lambda is all.
     PostToSharedFidl([this] {
-      zx_status_t bind_result = binding_.Bind(std::move(tmp_interface_request_),
+      zx_status_t status = sysmem_.Bind(std::move(tmp_sysmem_), shared_fidl_dispatcher_);
+      if (status != ZX_OK) {
+        Fail("sysmem_.Bind() failed");
+        return;
+      }
+      ZX_DEBUG_ASSERT(!tmp_sysmem_);
+
+      status = binding_.Bind(std::move(tmp_interface_request_),
                                               shared_fidl_dispatcher_);
-      if (bind_result != ZX_OK) {
+      if (status != ZX_OK) {
         Fail("binding_.Bind() failed");
         return;
       }
@@ -309,6 +318,8 @@
         kInputPacketCountForCodecRecommended);
     buffer_constraints.set_packet_count_for_server_max(
         kInputPacketCountForCodecMax);
+    buffer_constraints.set_packet_count_for_client_min(
+        kInputPacketCountForClientMin);
     buffer_constraints.set_packet_count_for_client_max(
         kInputPacketCountForClientMax);
     buffer_constraints.set_single_buffer_mode_allowed(
@@ -318,7 +329,7 @@
         std::make_unique<fuchsia::media::StreamBufferConstraints>(
             std::move(buffer_constraints));
 
-    // If/when this sends OnOutputConfig(), it posts to do so.
+    // If/when this sends OnOutputConstraints(), it posts to do so.
     onInputConstraintsReady();
 
     sent_buffer_constraints_version_ordinal_[kInputPort] =
@@ -354,23 +365,19 @@
     if (IsStoppingLocked()) {
       return;
     }
-    if (IsStreamActiveLocked()) {
-      Fail("client sent SetInputBufferSettings() with stream active");
-      return;
-    }
-    SetBufferSettingsCommon(lock, kInputPort, std::move(input_settings),
-                            *input_constraints_);
+    SetInputBufferSettingsCommon(lock, &input_settings, nullptr);
   }  // ~lock
 }
 
 void CodecImpl::AddInputBuffer(fuchsia::media::StreamBuffer buffer) {
   ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
   PostToStreamControl([this, buffer = std::move(buffer)]() mutable {
-    AddInputBuffer_StreamControl(std::move(buffer));
+    AddInputBuffer_StreamControl(true, std::move(buffer));
   });
 }
 
 void CodecImpl::AddInputBuffer_StreamControl(
+    bool is_client,
     fuchsia::media::StreamBuffer buffer) {
   ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
   if (IsStopping()) {
@@ -378,65 +385,177 @@
   }
   // We must check, because __WARN_UNUSED_RESULT, and it's worth it for the
   // enforcement and consistency.
-  if (!AddBufferCommon(kInputPort, std::move(buffer))) {
+  if (!AddBufferCommon(is_client, kInputPort, std::move(buffer))) {
     return;
   }
 }
 
+void CodecImpl::SetInputBufferPartialSettings(
+    fuchsia::media::StreamBufferPartialSettings input_settings) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+  PostToStreamControl([this, input_settings = std::move(input_settings)]() mutable {
+    SetInputBufferPartialSettings_StreamControl(std::move(input_settings));
+  });
+}
+
+void CodecImpl::SetInputBufferPartialSettings_StreamControl(
+    fuchsia::media::StreamBufferPartialSettings input_partial_settings) {
+  ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
+  {  // scope lock
+    std::unique_lock<std::mutex> lock(lock_);
+    if (!sysmem_) {
+      FailLocked("client sent SetInputBufferPartialSettings() to a CodecImpl that lacks sysmem_");
+      return;
+    }
+    SetInputBufferSettingsCommon(lock, nullptr, &input_partial_settings);
+  }  // ~lock
+}
+
+void CodecImpl::SetInputBufferSettingsCommon(
+    std::unique_lock<std::mutex>& lock,
+    fuchsia::media::StreamBufferSettings* input_settings,
+    fuchsia::media::StreamBufferPartialSettings* input_partial_settings) {
+  if (IsStoppingLocked()) {
+    return;
+  }
+  if (IsStreamActiveLocked()) {
+    FailLocked("client sent SetInputBuffer*Settings() with stream active");
+    return;
+  }
+  SetBufferSettingsCommon(lock, kInputPort, input_settings,
+                          input_partial_settings, *input_constraints_);
+}
+
 void CodecImpl::SetOutputBufferSettings(
     fuchsia::media::StreamBufferSettings output_settings) {
   ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
-
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
-
-    if (!output_config_) {
-      // invalid client behavior
-      //
-      // client must have received at least the initial OnOutputConfig() first
-      // before sending SetOutputBufferSettings().
-      FailLocked(
-          "client sent SetOutputBufferSettings() when no output_config_");
-      return;
-    }
-
-    // For a mid-stream output format change, this also enforces that the client
-    // can only catch up to the mid-stream format change once.  In other words,
-    // if the client has already caught up to the mid-stream config change, the
-    // client no longer has an excuse to re-configure again with a stream
-    // active.
-    //
-    // There's a check in SetBufferSettingsCommonLocked() that ignores this
-    // message if the client's buffer_constraints_version_ordinal is behind
-    // last_required_buffer_constraints_version_ordinal_, which gets updated
-    // under the same lock hold interval as the server's de-configuring of
-    // output buffers.
-    //
-    // There's a check in SetBufferSettingsCommonLocked() that closes the
-    // channel if the client is sending a buffer_constraints_version_ordinal
-    // that's newer than the last sent_buffer_constraints_version_ordinal_.
-    if (IsOutputConfiguredLocked() && IsStreamActiveLocked()) {
-      FailLocked(
-          "client sent SetOutputBufferSettings() with IsStreamActiveLocked() + "
-          "already-configured output");
-      return;
-    }
-
-    SetBufferSettingsCommon(lock, kOutputPort, std::move(output_settings),
-                            output_config_->buffer_constraints());
+    SetOutputBufferSettingsCommon(lock, &output_settings, nullptr);
   }  // ~lock
 }
 
+void CodecImpl::SetOutputBufferSettingsCommon(
+    std::unique_lock<std::mutex>& lock,
+    fuchsia::media::StreamBufferSettings* output_settings,
+    fuchsia::media::StreamBufferPartialSettings* output_partial_settings) {
+  if (!output_constraints_) {
+    // invalid client behavior
+    //
+    // client must have received at least the initial OnOutputConstraints() first
+    // before sending SetOutputBufferSettings().
+    FailLocked(
+        "client sent SetOutputBufferSettings()/SetOutputBufferPartialSettings()"
+        " when no output_constraints_");
+    return;
+  }
+
+  // For a mid-stream output format change, this also enforces that the client
+  // can only catch up to the mid-stream format change once.  In other words,
+  // if the client has already caught up to the mid-stream config change, the
+  // client no longer has an excuse to re-configure again with a stream
+  // active.
+  //
+  // There's a check in SetBufferSettingsCommonLocked() that ignores this
+  // message if the client's buffer_constraints_version_ordinal is behind
+  // last_required_buffer_constraints_version_ordinal_, which gets updated
+  // under the same lock hold interval as the server's de-configuring of
+  // output buffers.
+  //
+  // There's a check in SetBufferSettingsCommonLocked() that closes the
+  // channel if the client is sending a buffer_constraints_version_ordinal
+  // that's newer than the last sent_buffer_constraints_version_ordinal_.
+  if (IsStreamActiveLocked() && IsOutputConfiguredLocked()) {
+    FailLocked(
+        "client sent SetOutputBufferSettings()/SetOutputBufferPartialSettings()"
+        " with IsStreamActiveLocked() + already-fully-configured output");
+    return;
+  }
+
+  SetBufferSettingsCommon(lock, kOutputPort, output_settings,
+                          output_partial_settings,
+                          output_constraints_->buffer_constraints());
+}
+
 void CodecImpl::AddOutputBuffer(fuchsia::media::StreamBuffer buffer) {
   ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
-  bool output_done_configuring =
-      AddBufferCommon(kOutputPort, std::move(buffer));
-  if (output_done_configuring) {
+  AddOutputBufferInternal(true, std::move(buffer));
+}
+
+void CodecImpl::AddOutputBufferInternal(bool is_client, fuchsia::media::StreamBuffer buffer) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+  bool output_buffers_done_configuring =
+      AddBufferCommon(is_client, kOutputPort, std::move(buffer));
+  if (output_buffers_done_configuring) {
     // The StreamControl domain _might_ be waiting for output to be configured.
     wake_stream_control_condition_.notify_all();
   }
 }
 
+void CodecImpl::SetOutputBufferPartialSettings(
+  fuchsia::media::StreamBufferPartialSettings output_partial_settings) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+  {  // scope lock
+    std::unique_lock<std::mutex> lock(lock_);
+    if (!sysmem_) {
+      FailLocked("client sent SetOutputBufferPartialSettings() to a CodecImpl "
+                 "that lacks a sysmem_");
+      return;
+    }
+    SetOutputBufferSettingsCommon(lock, nullptr, &output_partial_settings);
+  }  // ~lock
+}
+
+void CodecImpl::CompleteOutputBufferPartialSettings(
+    uint64_t buffer_lifetime_ordinal) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+  {  // scope lock
+    std::unique_lock<std::mutex> lock(lock_);
+
+    if (buffer_lifetime_ordinal % 2 == 0) {
+      FailLocked(
+          "CompleteOutputBufferPartialSettings client sent even "
+          "buffer_lifetime_ordinal, but must be odd");
+      return;
+    }
+
+    if (buffer_lifetime_ordinal !=
+        protocol_buffer_lifetime_ordinal_[kOutputPort]) {
+      FailLocked(
+          "CompleteOutputBufferPartialSettings bad buffer_lifetime_ordinal");
+      return;
+    }
+
+    // If the server is not interested in the client's buffer_lifetime_ordinal,
+    // the client's buffer_lifetime_ordinal won't match the server's
+    // buffer_lifetime_ordinal_.  The client will probably later catch up.
+    if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[kOutputPort]) {
+      // The case that ends up here is when a client's output configuration
+      // (whole or last part) is being ignored because it's not yet caught up
+      // with last_required_buffer_constraints_version_ordinal_.
+
+      // Ignore the client's message.  The client will probably catch up later.
+      return;
+    }
+
+    if (!IsPortBuffersAtLeastPartiallyConfiguredLocked(kOutputPort)) {
+      FailLocked("CompleteOutputBufferPartialSettings seen without prior "
+                 "SetOutputBufferPartialSettings");
+      return;
+    }
+
+    if (port_settings_[kOutputPort]->is_complete_seen_output()) {
+      FailLocked("CompleteOutputBufferPartialSettings permitted exactly once "
+                 "after each SetOutputBufferPartialSettings");
+      return;
+    }
+
+    // This will cause IsOutputConfiguredLocked() to start returning true.
+    port_settings_[kOutputPort]->SetCompleteSeenOutput();
+  }  // ~lock
+  wake_stream_control_condition_.notify_all();
+}
+
 void CodecImpl::FlushEndOfStreamAndCloseStream(
     uint64_t stream_lifetime_ordinal) {
   ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
@@ -482,7 +601,7 @@
     // At this point we know that the stream is not discarded, and not already
     // flushed previously (because flush will discard the stream as there's
     // nothing more that the stream is permitted to do).
-    ZX_DEBUG_ASSERT(stream_);
+    ZX_DEBUG_ASSERT(IsStreamActiveLocked());
     ZX_DEBUG_ASSERT(stream_->stream_lifetime_ordinal() ==
                     stream_lifetime_ordinal);
     if (!stream_->input_end_of_stream()) {
@@ -493,7 +612,7 @@
     }
     while (!stream_->output_end_of_stream()) {
       // While waiting, we'll continue to send OnOutputPacket(),
-      // OnOutputConfig(), and continue to process RecycleOutputPacket(), until
+      // OnOutputConstraints(), and continue to process RecycleOutputPacket(), until
       // the client catches up to the latest config (as needed) and we've
       // started the send of output end_of_stream packet to the client.
       //
@@ -572,7 +691,14 @@
     // closed soon instead, and the client has to tolerate that.
     return;
   }
-  callback();
+  // We post back to FIDL thread to respond to ensure we're not racing with
+  // channel close which could lead to attempting to send to handle value 0
+  // which can cause process termination.  Also, because this fences
+  // BufferAllocation clean close which itself is done async from StreamControl
+  // to FIDL in some cases.
+  PostToSharedFidl([callback = std::move(callback)]{
+    callback();
+  });
 }
 
 void CodecImpl::RecycleOutputPacket(
@@ -635,7 +761,7 @@
     // Before handing the packet to the core codec, clear some fields that the
     // core codec is expected to set (or optionally set in the case of
     // timestamp_ish).  In addition to these parameters, a core codec can emit
-    // output config changes via onCoreCodecMidStreamOutputConfigChange().
+    // output config changes via onCoreCodecMidStreamOutputConstraintsChange().
     packet = all_packets_[kOutputPort][packet_index].get();
     packet->ClearStartOffset();
     packet->ClearValidLengthBytes();
@@ -689,6 +815,12 @@
   if (!CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal)) {
     return;
   }
+
+  if (!CheckWaitEnsureInputConfigured(lock)) {
+    ZX_DEBUG_ASSERT(IsStoppingLocked() || !stream_ || stream_->future_discarded());
+    return;
+  }
+
   ZX_DEBUG_ASSERT(stream_lifetime_ordinal >= stream_lifetime_ordinal_);
   if (stream_lifetime_ordinal > stream_lifetime_ordinal_) {
     if (!StartNewStream(lock, stream_lifetime_ordinal)) {
@@ -788,12 +920,26 @@
       return;
     }
 
+    if (!packet.has_stream_lifetime_ordinal()) {
+      FailLocked(
+          "client QueueInputPacket() without packet stream_lifetime_ordinal.");
+      return;
+    }
+    if (!CheckStreamLifetimeOrdinalLocked(packet.stream_lifetime_ordinal())) {
+      return;
+    }
+
+    if (!CheckWaitEnsureInputConfigured(lock)) {
+      ZX_DEBUG_ASSERT(IsStoppingLocked() || (stream_ && stream_->future_discarded()));
+      return;
+    }
+
     // For input, mid-stream config changes are not a thing and input buffers
     // are never unilaterally de-configured by the Codec server.
     ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kInputPort] ==
                     port_settings_[kInputPort]->buffer_lifetime_ordinal());
-    // For this message we're extra-strict re. buffer_lifetime_ordinal, at least
-    // for now.
+
+    // For this message we're strict re. buffer_lifetime_ordinal.
     //
     // In contrast to output, the server doesn't use even values to track config
     // changes that the client doesn't know about yet, since the server can't
@@ -810,15 +956,6 @@
       return;
     }
 
-    if (!packet.has_stream_lifetime_ordinal()) {
-      FailLocked(
-          "client QueueInputPacket() without packet stream_lifetime_ordinal.");
-      return;
-    }
-
-    if (!CheckStreamLifetimeOrdinalLocked(packet.stream_lifetime_ordinal())) {
-      return;
-    }
     ZX_DEBUG_ASSERT(packet.stream_lifetime_ordinal() >=
                     stream_lifetime_ordinal_);
 
@@ -836,16 +973,12 @@
     ZX_DEBUG_ASSERT(packet.stream_lifetime_ordinal() ==
                     stream_lifetime_ordinal_);
 
-    if (!IsInputConfiguredLocked()) {
-      FailLocked("client QueueInputPacket() with input buffers not configured");
-      return;
-    }
     if (!packet.header().has_packet_index()) {
       FailLocked("client QueueInputPacket() with packet has no packet index");
       return;
     }
     if (packet.header().packet_index() >= all_packets_[kInputPort].size()) {
-      FailLocked("client QueueInputPacket() with packet_index out of range");
+      FailLocked("client QueueInputPacket() with packet_index out of range - packet_index: %u size: %u", packet.header().packet_index(), all_packets_[kInputPort].size());
       return;
     }
     if (!packet.has_buffer_index()) {
@@ -952,6 +1085,12 @@
     if (!CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal)) {
       return;
     }
+
+    if (!CheckWaitEnsureInputConfigured(lock)) {
+      ZX_DEBUG_ASSERT(IsStoppingLocked() || (stream_ && stream_->future_discarded()));
+      return;
+    }
+
     ZX_DEBUG_ASSERT(stream_lifetime_ordinal >= stream_lifetime_ordinal_);
     if (stream_lifetime_ordinal > stream_lifetime_ordinal_) {
       // We start a new stream given an end-of-stream for a stream we've not
@@ -969,11 +1108,11 @@
     stream_->SetInputEndOfStream();
 
     if (stream_->future_discarded()) {
-      // Don't queue to OMX.  The stream_ may have never fully started, or may
-      // have been future-discarded since.  Either way, skip queueing to OMX. We
-      // only really must do this because the stream may not have ever fully
-      // started, in the case where the client moves on to a new stream before
-      // catching up to latest output config.
+      // Don't queue to core codec.  The stream_ may have never fully started,
+      // or may have been future-discarded since. Either way, skip queueing to
+      // core codec. We only really must do this because the stream may not have
+      // ever fully started, in the case where the client moves on to a new
+      // stream before catching up to latest output config.
       return;
     }
   }  // ~lock
@@ -981,6 +1120,74 @@
   CoreCodecQueueInputEndOfStream();
 }
 
+bool CodecImpl::CheckWaitEnsureInputConfigured(std::unique_lock<std::mutex>& lock) {
+  // Ensure/finish input configuration.
+  if (!IsPortBuffersAtLeastPartiallyConfiguredLocked(kInputPort)) {
+    FailLocked("client QueueInput*() with input buffers not at least partially configured");
+    return false;
+  }
+  ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kInputPort] % 2 == 1);
+  // The client is required to know that sysmem is in fact done allocating the
+  // BufferCollection successfully before the client sends
+  // QueueInput...StreamControl.  We can't trust a client to necessarily get
+  // that right however, so rather than just getting stuck indefinitely in that
+  // case, we detect by asking sysmem to verify that it has allocated the
+  // BufferCollection successfully.  This verification happens async, but will
+  // shortly cause WaitEnsureSysmemReadyOnInput() to return and
+  // IsStoppingLocked() to return true if verification fails.
+  if (!IsInputConfiguredLocked()) {
+    PostToSharedFidl([this, buffer_lifetime_ordinal = buffer_lifetime_ordinal_[kInputPort]]{
+      std::unique_lock<std::mutex> lock(lock_);
+      if (IsStoppingLocked()) {
+        return;
+      }
+      if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[kInputPort]) {
+        // stale; no problem; old buffers were allocated fine and client already
+        // moved on after that.
+        return;
+      }
+      // Else previous buffer_lifetime_ordinal check would have noticed.
+      ZX_DEBUG_ASSERT(port_settings_[kInputPort]);
+      // paranoid check - assert above believed to be valid
+      if (!port_settings_[kInputPort]) {
+        return;
+      }
+      // Else IsStoppingLocked() check above would have returned.
+      ZX_DEBUG_ASSERT(port_settings_[kInputPort]->buffer_collection().is_bound());
+      // paranoid check - assert above believed to be valid
+      if (!port_settings_[kInputPort]->buffer_collection().is_bound()) {
+        return;
+      }
+      port_settings_[kInputPort]->buffer_collection()->CheckBuffersAllocated([this, buffer_lifetime_ordinal](zx_status_t status){
+        std::unique_lock<std::mutex> lock(lock_);
+        if (IsStoppingLocked()) {
+          return;
+        }
+        if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[kInputPort]) {
+          // stale; no problem; old buffers were allocated fine and client
+          // already moved on after that.
+          return;
+        }
+        if (status != ZX_OK) {
+          // This will cause any in-progress WaitEnsureSysmemReadyOnInput() to
+          // return shortly and IsStoppingLocked() will be true.
+          FailLocked("Probably client did QueueInput* before the client determined that sysmem was done successfully allocating buffers after most recent SetInputBufferPartialSettings()");
+          return;
+        }
+      });
+    });
+    if (!WaitEnsureSysmemReadyOnInput(lock)) {
+      ZX_DEBUG_ASSERT(IsStoppingLocked());
+      return false;
+    }
+  }
+  if (!IsInputConfiguredLocked()) {
+    FailLocked("client QueueInput*() with input buffers not configured");
+    return false;
+  }
+  return true;
+}
+
 void CodecImpl::onInputConstraintsReady() {
   ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
   if (!IsCoreCodecRequiringOutputConfigForFormatDetection()) {
@@ -988,7 +1195,7 @@
   }
   std::unique_lock<std::mutex> lock(lock_);
   StartIgnoringClientOldOutputConfig(lock);
-  GenerateAndSendNewOutputConfig(lock, true);
+  GenerateAndSendNewOutputConstraints(lock, true);
 }
 
 void CodecImpl::UnbindLocked() {
@@ -1102,6 +1309,22 @@
       // being safe to just delete.
       stream_control_loop_.Shutdown();
 
+      {  // scope lock
+        std::lock_guard<std::mutex> lock(lock_);
+        // This unbinds any BufferCollection bindings.  This is effectively part
+        // of the overall unbind of anything generating activity on FIDL thread
+        // based on incoming channel messages.
+        if (port_settings_[kInputPort] && port_settings_[kInputPort]->is_partial_settings()) {
+          port_settings_[kInputPort]->UnbindBufferCollection();
+        }
+        if (port_settings_[kOutputPort] && port_settings_[kOutputPort]->is_partial_settings()) {
+          port_settings_[kOutputPort]->UnbindBufferCollection();
+        }
+        // Unbind the sysmem_ fuchsia::sysmem::Allocator connection - this also
+        // ensures that any in-flight requests' completions will not run.
+        sysmem_.Unbind();
+      }  // ~lock
+
       // Before calling the owner_error_handler_, we declare that unbind is
       // done so that during the destructor we can check that unbind is done.
       was_unbind_completed_ = true;
@@ -1135,7 +1358,10 @@
       // fidl_thread() re. this CodecImpl to not re-post to the fidl_thread().
       // In contrast, it is ok if a FIDL dispatch (on FIDL thread) re-posts to
       // the fidl_thread(); this is ok because we've already stopped any more
-      // FIDL dispatching by binding_.Unbind() above.
+      // FIDL dispatching by binding_.Unbind() above.  It's also ok if a posted
+      // lambda starts a FIDL request that will complete async on the FIDL
+      // thread since the unbind above will prevent the completion from
+      // running.
       PostSerial(shared_fidl_dispatcher_,
                  [client_error_handler = std::move(owner_error_handler_)] {
                    // This call deletes the CodecImpl.
@@ -1159,13 +1385,15 @@
 }
 
 bool CodecImpl::IsStreamActiveLocked() {
-  return stream_lifetime_ordinal_ % 2 == 1;
+  ZX_DEBUG_ASSERT(!!stream_ == (stream_lifetime_ordinal_ % 2 == 1));
+  return !!stream_;
 }
 
 void CodecImpl::SetBufferSettingsCommon(
     std::unique_lock<std::mutex>& lock, CodecPort port,
-    fuchsia::media::StreamBufferSettings settings,
-    const fuchsia::media::StreamBufferConstraints& constraints) {
+    fuchsia::media::StreamBufferSettings* settings,
+    fuchsia::media::StreamBufferPartialSettings* partial_settings,
+    const fuchsia::media::StreamBufferConstraints& stream_constraints) {
   ZX_DEBUG_ASSERT(port == kInputPort &&
                       thrd_current() == stream_control_thread_ ||
                   port == kOutputPort && thrd_current() == fidl_thread());
@@ -1177,41 +1405,91 @@
   // is either the last accepted from the client or one more than that as a way
   // of cleanly permitting the server to unilaterally de-configure output
   // buffers.
-  if (!settings.has_buffer_lifetime_ordinal()) {
-    FailLocked("settings do not have buffer lifetime ordinal");
-    return;
+  if (settings) {
+    if (!settings->has_buffer_lifetime_ordinal()) {
+      FailLocked("settings missing buffer lifetime ordinal");
+      return;
+    }
+    if (!settings->has_buffer_constraints_version_ordinal()) {
+      FailLocked("settings missing buffer constraints version ordinal");
+      return;
+    }
+    if (!settings->has_packet_count_for_server()) {
+      FailLocked("settings missing packet_count_for_server");
+      return;
+    }
+    if (!settings->has_packet_count_for_client()) {
+      FailLocked("settings missing packet_count_for_client");
+      return;
+    }
+    if (!settings->has_per_packet_buffer_bytes()) {
+      FailLocked("settings missing per_packet_buffer_bytes");
+      return;
+    }
+  } else {
+    if (!partial_settings->has_buffer_lifetime_ordinal()) {
+      FailLocked("partial_settings do not have buffer lifetime ordinal");
+      return;
+    }
+    if (!partial_settings->has_buffer_constraints_version_ordinal()) {
+      FailLocked(
+          "partial_settings do not have buffer constraints version ordinal");
+      return;
+    }
+    if (!partial_settings->has_packet_count_for_server()) {
+      FailLocked("partial_settings missing packet_count_for_server");
+      return;
+    }
+    if (!partial_settings->has_packet_count_for_client()) {
+      FailLocked("partial_settings missing packet_count_for_client");
+      return;
+    }
+    if (!partial_settings->has_sysmem_token() || !partial_settings->sysmem_token().is_valid()) {
+      FailLocked("partial_settings missing valid sysmem_token");
+      return;
+    }
   }
 
   ZX_DEBUG_ASSERT(
-      (!port_settings_[port && buffer_lifetime_ordinal_[port] == 0]) ||
+      !port_settings_[port] ||
       (buffer_lifetime_ordinal_[port] >=
            port_settings_[port]->buffer_lifetime_ordinal() &&
        buffer_lifetime_ordinal_[port] <=
            port_settings_[port]->buffer_lifetime_ordinal() + 1));
-  if (settings.buffer_lifetime_ordinal() <=
+
+  // The caller may be providing StreamBufferSettings or
+  // StreamBufferPartialSettings, but not both.
+  ZX_DEBUG_ASSERT(!!settings ^ !!partial_settings);
+
+  // Extract buffer_lifetime_ordinal and buffer_constraints_version_ordinal from
+  // whichever of StreamBufferSettings or StreamBufferPartialSettings the caller
+  // is providing.
+  uint64_t buffer_lifetime_ordinal = settings ?
+      settings->buffer_lifetime_ordinal() :
+      partial_settings->buffer_lifetime_ordinal();
+
+  uint64_t buffer_constraints_version_ordinal = settings ?
+      settings->buffer_constraints_version_ordinal() :
+      partial_settings->buffer_constraints_version_ordinal();
+
+  if (buffer_lifetime_ordinal <=
       protocol_buffer_lifetime_ordinal_[port]) {
     FailLocked(
-        "settings.buffer_lifetime_ordinal <= "
+        "buffer_lifetime_ordinal <= "
         "protocol_buffer_lifetime_ordinal_[port] - port: %d",
         port);
     return;
   }
-  protocol_buffer_lifetime_ordinal_[port] = settings.buffer_lifetime_ordinal();
-
-  if (settings.buffer_lifetime_ordinal() % 2 == 0) {
+  if (buffer_lifetime_ordinal % 2 == 0) {
     FailLocked(
         "Only odd values for buffer_lifetime_ordinal are permitted - port: %d "
         "value %lu",
-        port, settings.buffer_lifetime_ordinal());
+        port, buffer_lifetime_ordinal);
     return;
   }
+  protocol_buffer_lifetime_ordinal_[port] = buffer_lifetime_ordinal;
 
-  if (!settings.has_buffer_constraints_version_ordinal()) {
-    FailLocked("settings do not have buffer constraints version ordinal");
-    return;
-  }
-
-  if (settings.buffer_constraints_version_ordinal() >
+  if (buffer_constraints_version_ordinal >
       sent_buffer_constraints_version_ordinal_[port]) {
     FailLocked(
         "Client sent too-new buffer_constraints_version_ordinal - port: %d",
@@ -1219,37 +1497,292 @@
     return;
   }
 
-  if (settings.buffer_constraints_version_ordinal() <
+  if (buffer_constraints_version_ordinal <
       last_required_buffer_constraints_version_ordinal_[port]) {
     // ignore - client will probably catch up later
     return;
   }
 
   // We've peeled off too new and too old above.
-  ZX_DEBUG_ASSERT(settings.buffer_constraints_version_ordinal() >=
+  ZX_DEBUG_ASSERT(buffer_constraints_version_ordinal >=
                       last_required_buffer_constraints_version_ordinal_[port] &&
-                  settings.buffer_constraints_version_ordinal() <=
+                  buffer_constraints_version_ordinal <=
                       sent_buffer_constraints_version_ordinal_[port]);
 
   // We've already checked above that the buffer_lifetime_ordinal is in
   // sequence.
-  ZX_DEBUG_ASSERT(!port_settings_[port] || settings.buffer_lifetime_ordinal() >
+  ZX_DEBUG_ASSERT(!port_settings_[port] || buffer_lifetime_ordinal >
                                                buffer_lifetime_ordinal_[port]);
 
-  if (!ValidateBufferSettingsVsConstraintsLocked(port, settings, constraints)) {
-    // This assert is safe only because this thread still holds lock_.
-    ZX_DEBUG_ASSERT(IsStoppingLocked());
-    return;
+  if (settings) {
+    if (!ValidateBufferSettingsVsConstraintsLocked(port, *settings, stream_constraints)) {
+      // This assert is safe only because this thread still holds lock_.  This
+      // is asserting that ValidateBufferSettingsVsConstraintsLocked() already
+      // called FailLocked().
+      ZX_DEBUG_ASSERT(IsStoppingLocked());
+      return;
+    }
+  } else {
+    if (!ValidatePartialBufferSettingsVsConstraintsLocked(port, *partial_settings, stream_constraints)) {
+      // This assert is safe only because this thread still holds lock_.  This
+      // is asserting that ValidateBufferSettingsVsConstraintsLocked() already
+      // called FailLocked().
+      ZX_DEBUG_ASSERT(IsStoppingLocked());
+      return;
+    }
   }
 
   // Little if any reason to do this outside the lock.
   EnsureBuffersNotConfigured(lock, port);
 
   // This also starts the new buffer_lifetime_ordinal.
-  port_settings_[port] = std::make_unique<fuchsia::media::StreamBufferSettings>(
-      std::move(settings));
+  {  // scope port_settings, to enforce not using it after we've moved it out
+    std::unique_ptr<PortSettings> port_settings;
+    if (settings) {
+      port_settings = std::make_unique<PortSettings>(this, port,
+        std::move(*settings));
+    } else {
+      port_settings = std::make_unique<PortSettings>(this, port,
+        std::move(*partial_settings));
+    }
+    port_settings_[port] = std::move(port_settings);
+  }  // ~port_settings, which has been moved out, so we can't use it anyway
   buffer_lifetime_ordinal_[port] =
       port_settings_[port]->buffer_lifetime_ordinal();
+
+  // If partial_settings_param, the client won't be doing any AddInputBuffer()
+  // or AddOutputBuffer().  Instead, CodecImpl uses the token in
+  // partial_settings to get a fuchsia.sysmem.BufferCollection view,
+  // SetConstraints() on that BufferCollection, and then get allocated buffers
+  // from sysmem directly.
+
+  if (port_settings_[port]->is_partial_settings()) {
+    fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token =
+        port_settings_[port]->TakeToken();
+    // We intentionally don't want to hand the sysmem token directly to the core
+    // codec, at least for now (maybe later it'll be necessary).
+    ZX_DEBUG_ASSERT(!port_settings_[port]->partial_settings().has_sysmem_token());
+    fuchsia::sysmem::BufferCollectionConstraints buffer_collection_constraints = [this, port, &lock, &stream_constraints](){
+      // port_settings_[port] can only change on this thread so are safe to read
+      // outside the lock.
+      ScopedUnlock unlock(lock);
+      return CoreCodecGetBufferCollectionConstraints(
+          port, stream_constraints, port_settings_[port]->partial_settings());
+    }();
+    // The core codec doesn't fill out usage directly.  Instead we fill it out
+    // here.
+    if (!FixupBufferCollectionConstraints(
+        port, port_settings_[port]->partial_settings(),
+        &buffer_collection_constraints)) {
+      // FixupBufferCollectionConstraints() already called Fail().
+      ZX_DEBUG_ASSERT(IsStopping());
+      return;
+    }
+    // For output, the only reason we re-post here is to share the lock
+    // acquisition code with input.  Because we're presently on a FIDL handler
+    // it's ok to re-post back to the FIDL thread, so for output we pass true
+    // for promise_not_on_previously_posted_fidl_thread_lambda.
+    PostToSharedFidl([this, port, buffer_lifetime_ordinal = buffer_lifetime_ordinal_[port], token = std::move(token), buffer_collection_constraints = std::move(buffer_collection_constraints)]() mutable {
+      std::lock_guard<std::mutex> lock(lock_);
+      if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[port]) {
+        return;
+      }
+      if (!sysmem_) {
+        return;
+      }
+      if (IsStoppingLocked()) {
+        return;
+      }
+      sysmem_->BindSharedCollection(std::move(token), port_settings_[port]->NewBufferCollectionRequest(shared_fidl_dispatcher_));
+      port_settings_[port]->buffer_collection().set_error_handler(
+        [this, port, buffer_lifetime_ordinal](zx_status_t status){
+        std::lock_guard<std::mutex> lock(lock_);
+        if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[port]) {
+          // It's fine if a BufferCollection fails after we're already done
+          // using it.
+          return;
+        }
+        // We're intentionally picky about the BufferCollection failing too
+        // soon, as all clean closes should use Close(), which will avoid
+        // causing this.  If we find a case where a client legitimately needs to
+        // try one way then if that fails try another way, we should see if we
+        // can avoid the need to do that by expressing in sysmem constraints, or
+        // more likely just accept that such a client will need to start with a
+        // new codec instance for the 2nd try.
+        FailLocked("CodecImpl's BufferCollection failed - status: %d", status);
+      });
+      port_settings_[port]->buffer_collection()->SetConstraints(
+          true, std::move(buffer_collection_constraints));
+      port_settings_[port]->buffer_collection()->WaitForBuffersAllocated([this, port, buffer_lifetime_ordinal](zx_status_t status, fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) mutable {
+        OnBufferCollectionInfo(port, buffer_lifetime_ordinal, status, std::move(buffer_collection_info));
+      });
+    }, port == kOutputPort);
+  } else {
+    // This path probably won't stick around for very long, and involves a
+    // substantial amount of simulation of the new way based on old settings. To
+    // the degree that it's more indirect than usual, it's to try and
+    // use/simulate the new way as much as possible despite the client using the
+    // old way.
+
+    const fuchsia::media::StreamBufferSettings& settings =
+        port_settings_[port]->settings();
+
+    // If !port_settings_[port]->is_partial_settings(), we'll simulate the new
+    // way based on the old-style settings.  This way we can get the core codec
+    // to fill out image_format_constraints, and can inform the core codec of
+    // BufferCollectionInfo_2, even if we're not actually using sysmem (in this
+    // path for now).
+    fuchsia::media::StreamBufferPartialSettings fake_partial_settings;
+    fake_partial_settings.set_buffer_lifetime_ordinal(settings.buffer_lifetime_ordinal());
+    fake_partial_settings.set_buffer_constraints_version_ordinal(settings.buffer_constraints_version_ordinal());
+    fake_partial_settings.set_packet_count_for_server(settings.packet_count_for_server());
+    fake_partial_settings.set_packet_count_for_client(settings.packet_count_for_client());
+    fake_partial_settings.set_single_buffer_mode(settings.has_single_buffer_mode() && settings.single_buffer_mode());
+    ZX_DEBUG_ASSERT(!fake_partial_settings.has_sysmem_token());
+
+    fuchsia::sysmem::BufferCollectionConstraints buffer_collection_constraints = [this, port, &lock, &stream_constraints, &fake_partial_settings]{
+        ScopedUnlock unlock(lock);
+        return CoreCodecGetBufferCollectionConstraints(port, stream_constraints, fake_partial_settings);
+    }();
+
+    // The core codec doesn't fill out usage directly.  Instead we fill it out
+    // here.
+    if (!FixupBufferCollectionConstraints(
+        port, fake_partial_settings,
+        &buffer_collection_constraints)) {
+      // FixupBufferCollectionConstraints() already called Fail().
+      ZX_DEBUG_ASSERT(IsStopping());
+      return;
+    }
+
+    fuchsia::sysmem::BufferCollectionInfo_2 fake;
+    fake.buffer_count = fake_partial_settings.single_buffer_mode() ? 1 : fake_partial_settings.packet_count_for_server() + fake_partial_settings.packet_count_for_client();
+    fake.settings.buffer_settings.size_bytes = fake_partial_settings.single_buffer_mode() ? settings.per_packet_buffer_bytes() * fake.buffer_count : settings.per_packet_buffer_bytes();
+    fake.settings.buffer_settings.is_physically_contiguous = buffer_collection_constraints.has_buffer_memory_constraints && buffer_collection_constraints.buffer_memory_constraints.physically_contiguous_required;
+    // The client should use sysmem (and SetInputBufferPartialSettings /
+    // SetOutputBufferPartialSettings) if secure is required.
+    fake.settings.buffer_settings.is_secure = false;
+    if (buffer_collection_constraints.image_format_constraints_count != 0) {
+      fake.settings.has_image_format_constraints = true;
+      // pick option 0 arbitrarily (essentially picking PixelFormat 0 among possibly multiple PixelFormat(s))
+      fake.settings.image_format_constraints = fidl::Clone(buffer_collection_constraints.image_format_constraints[0]);
+    } else {
+      fake.settings.has_image_format_constraints = false;
+    }
+    CoreCodecSetBufferCollectionInfo(port, fake);
+  }
+}
+
+void CodecImpl::OnBufferCollectionInfo(
+    CodecPort port, uint64_t buffer_lifetime_ordinal, zx_status_t status,
+    fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+
+  if (port == kInputPort) {
+    PostSysmemCompletion([this, port, buffer_lifetime_ordinal, status, buffer_collection_info = std::move(buffer_collection_info)]() mutable {
+      OnBufferCollectionInfoInternal(port, buffer_lifetime_ordinal, status, std::move(buffer_collection_info));
+    });
+  } else {
+    ZX_DEBUG_ASSERT(port == kOutputPort);
+    OnBufferCollectionInfoInternal(port, buffer_lifetime_ordinal, status, std::move(buffer_collection_info));
+  }
+}
+
+void CodecImpl::OnBufferCollectionInfoInternal(CodecPort port, uint64_t buffer_lifetime_ordinal, zx_status_t allocate_status, fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) {
+  ZX_DEBUG_ASSERT(port == kInputPort && thrd_current() == stream_control_thread_ ||
+                  port == kOutputPort && thrd_current() == fidl_thread());
+
+  {  // scope lock
+    std::lock_guard<std::mutex> lock(lock_);
+    if (IsStoppingLocked()) {
+      return;
+    }
+
+    // The buffer_lifetime_ordinal_[port] can only change on the current thread.
+    if (buffer_lifetime_ordinal != buffer_lifetime_ordinal_[port]) {
+      // stale response
+      return;
+    }
+    if (allocate_status != ZX_OK) {
+      Fail("OnBufferCollectionInfoLocked() sees failure - port: %d allocate_status: %d", port, allocate_status);
+      return;
+    }
+  }  // ~lock
+
+  uint32_t buffer_count = buffer_collection_info.buffer_count;
+
+  // This code trusts sysmem to really be sysmem and to behave correctly, but
+  // doesn't hurt to double-check some things in debug build.
+  ZX_DEBUG_ASSERT(buffer_count >= 1);
+  ZX_DEBUG_ASSERT(buffer_count <= countof(buffer_collection_info.buffers));
+  // Spot check that the boundary between valid and invalid handles is where it
+  // should be.
+  ZX_DEBUG_ASSERT(buffer_collection_info.buffers[buffer_count-1].vmo.is_valid());
+  ZX_DEBUG_ASSERT(buffer_count == countof(buffer_collection_info.buffers) ||
+      !buffer_collection_info.buffers[buffer_count].vmo.is_valid());
+
+  // Let's move the VMO handles out first, so that the BufferCollectionInfo_2 we
+  // send to the core codec doesn't have the VMO handles.  We want the core
+  // codec to get its VMO handles via the CodecBuffer*(s) we'll provide shortly
+  // below.
+  zx::vmo vmos[countof(buffer_collection_info.buffers)];
+  for (uint32_t i = 0; i < buffer_count; ++i) {
+    vmos[i] = std::move(buffer_collection_info.buffers[i].vmo);
+    ZX_DEBUG_ASSERT(!buffer_collection_info.buffers[i].vmo.is_valid());
+  }
+
+  // Now we can tell the core codec about the collection info.  The core codec
+  // can clone the FIDL struct if it wants, or can just copy out any info it
+  // wants from specific fields.
+  CoreCodecSetBufferCollectionInfo(port, buffer_collection_info);
+
+  {  // scope lock
+    std::lock_guard<std::mutex> lock(lock_);
+
+    ZX_DEBUG_ASSERT(
+        buffer_lifetime_ordinal == buffer_lifetime_ordinal_[port]);
+
+    // The only way port_settings_[port] gets cleared is if
+    // buffer_lifetime_ordinal changes.
+    ZX_DEBUG_ASSERT(port_settings_[port]);
+
+    // This completes the settings, analogous to having completed
+    // SetInputBufferSettings()/SetOutputBufferSettings().
+    port_settings_[port]->SetBufferCollectionInfo(
+        std::move(buffer_collection_info));
+  }
+
+  // We convert the buffer_collection_info into AddInputBuffer_StreamControl()
+  // and AddOutputBufferInternal() calls, almost as if the client were adding
+  // the buffers itself (but without the check that the client isn't adding
+  // buffers itself while using sysmem).
+  for (uint32_t i = 0; i < buffer_count; i++) {
+    // While under the lock we'll move out the stuff we need into codec_buffer.
+    fuchsia::media::StreamBuffer stream_buffer;
+    {  // scope lock
+      std::lock_guard<std::mutex> lock(lock_);
+
+      ZX_DEBUG_ASSERT(
+          buffer_lifetime_ordinal == buffer_lifetime_ordinal_[port]);
+      ZX_DEBUG_ASSERT(port_settings_[port]);
+
+      stream_buffer.set_buffer_lifetime_ordinal(buffer_lifetime_ordinal);
+      stream_buffer.set_buffer_index(i);
+      fuchsia::media::StreamBufferDataVmo data_vmo;
+      data_vmo.set_vmo_handle(std::move(vmos[i]));
+      data_vmo.set_vmo_usable_start(port_settings_[port]->vmo_usable_start(i));
+      data_vmo.set_vmo_usable_size(port_settings_[port]->vmo_usable_size());
+      fuchsia::media::StreamBufferData data;
+      data.set_vmo(std::move(data_vmo));
+      stream_buffer.set_data(std::move(data));
+    }  // ~lock
+    if (port == kInputPort) {
+      AddInputBuffer_StreamControl(false, std::move(stream_buffer));
+    } else {
+      ZX_DEBUG_ASSERT(port == kOutputPort);
+      AddOutputBufferInternal(false, std::move(stream_buffer));
+    }
+  }
 }
 
 void CodecImpl::EnsureBuffersNotConfigured(std::unique_lock<std::mutex>& lock,
@@ -1261,17 +1794,29 @@
   //
   // On input, this can only be called on stream_control_thread_.
   //
-  // On output, this can be called on stream_control_thread_ or output_thread_.
-  ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_ ||
-                  (port == kOutputPort && (thrd_current() == fidl_thread())));
+  // On output, this can be called on stream_control_thread_ or fidl_thread().
+  ZX_DEBUG_ASSERT(port == kInputPort && thrd_current() == stream_control_thread_ ||
+                  port == kOutputPort && (thrd_current() == stream_control_thread_ || thrd_current() == fidl_thread()));
 
-  is_port_configured_[port] = false;
+  is_port_buffers_configured_[port] = false;
+  if (buffer_lifetime_ordinal_[port] % 2 == 1) {
+    buffer_lifetime_ordinal_[port]++;
+  }
+  if (port_settings_[port]) {
+    // This will close the BufferCollection async cleanly, without causing the
+    // LogicalBufferCollection to fail.  Mainly we care so we can more easily
+    // tell during debugging whether a LogicalBufferCollection was cleanly
+    // closed by all participants, vs. potentially getting failed by a
+    // participant exiting or non-cleanly closing.  A Sync() by the client is
+    // sufficient to ensure this async close is done.
+    port_settings_[port].reset();
+  }
 
   // Ensure that buffers aren't with the core codec.
   {  // scope unlock
     ScopedUnlock unlock(lock);
     CoreCodecEnsureBuffersNotConfigured(port);
-  }
+  }  // ~unlock
 
   // For mid-stream output config change, the caller is responsible for ensuring
   // that buffers are not with the HW first.
@@ -1306,12 +1851,15 @@
     FailLocked("packet_count_for_server > packet_count_for_server_max");
     return false;
   }
-
   if (!settings.has_packet_count_for_client()) {
     FailLocked("settings do not have packet count for client");
     return false;
   }
-
+  if (settings.packet_count_for_client() <
+      constraints.packet_count_for_client_min()) {
+    FailLocked("packet_count_for_client < packet_count_for_client_min - port: %d %d < %d", port, settings.packet_count_for_client(), constraints.packet_count_for_client_min());
+    return false;
+  }
   if (settings.packet_count_for_client() >
       constraints.packet_count_for_client_max()) {
     FailLocked("packet_count_for_client > packet_count_for_client_max");
@@ -1343,17 +1891,75 @@
   return true;
 }
 
-bool CodecImpl::AddBufferCommon(CodecPort port,
+bool CodecImpl::ValidatePartialBufferSettingsVsConstraintsLocked(
+    CodecPort port, const fuchsia::media::StreamBufferPartialSettings& partial_settings,
+    const fuchsia::media::StreamBufferConstraints& constraints) {
+  // Most of the constraints will be handled by telling sysmem about them, not
+  // via the client, so there's not a ton to validate here.
+  if ((partial_settings.has_single_buffer_mode() && partial_settings.single_buffer_mode()) && !constraints.single_buffer_mode_allowed()) {
+    FailLocked("single_buffer_mode && !single_buffer_mode_allowed");
+    return false;
+  }
+  bool packet_count_needed = partial_settings.has_single_buffer_mode() && partial_settings.single_buffer_mode();
+  ZX_DEBUG_ASSERT(partial_settings.sysmem_token().is_valid());
+  if (packet_count_needed) {
+    if (!partial_settings.has_packet_count_for_server()) {
+      FailLocked("missing packet_count_for_server");
+      return false;
+    }
+    if (!partial_settings.has_packet_count_for_client()) {
+      FailLocked("missing packet_count_for_client");
+      return false;
+    }
+  } else {
+    // If packet count isn't required, it can still be provided, but if it's
+    // provided, it should be provided in two parts like usual.
+    if (partial_settings.has_packet_count_for_server() !=
+        partial_settings.has_packet_count_for_client()) {
+      FailLocked("has_packet_count_for_server != has_packet_count_for_client");
+      return false;
+    }
+  }
+  // if needed or provided anyway
+  if (partial_settings.has_packet_count_for_server()) {
+    if (partial_settings.packet_count_for_server() <
+        constraints.packet_count_for_server_min()) {
+      FailLocked("packet_count_for_server < packet_count_for_server_min");
+      return false;
+    }
+    if (partial_settings.packet_count_for_server() >
+        constraints.packet_count_for_server_max()) {
+      FailLocked("packet_count_for_server > packet_count_for_server_max");
+      return false;
+    }
+  }
+  // if needed or provided anyway
+  if (partial_settings.has_packet_count_for_client()) {
+    if (partial_settings.packet_count_for_client() <
+        constraints.packet_count_for_client_min()) {
+      FailLocked("packet_count_for_client > packet_count_for_client_max");
+      return false;
+    }
+    if (partial_settings.packet_count_for_client() >
+        constraints.packet_count_for_client_max()) {
+      FailLocked("packet_count_for_client > packet_count_for_client_max");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool CodecImpl::AddBufferCommon(bool is_client, CodecPort port,
                                 fuchsia::media::StreamBuffer buffer) {
   ZX_DEBUG_ASSERT(port == kInputPort &&
                       (thrd_current() == stream_control_thread_) ||
                   port == kOutputPort && (thrd_current() == fidl_thread()));
-  bool done_configuring = false;
+  bool buffers_done_configuring = false;
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
 
     if (!buffer.has_buffer_lifetime_ordinal()) {
-      FailFatalLocked("Client send a buffer without a buffer_lifetime_ordinal");
+      FailLocked("Client send a buffer without a buffer_lifetime_ordinal");
       return false;
     }
 
@@ -1392,6 +1998,11 @@
       return false;
     }
 
+    if (is_client && port_settings_[port]->is_partial_settings()) {
+      FailLocked(
+          "AddInputBuffer()/AddOutputBuffer() not permitted when using sysmem");
+      return false;
+    }
     if (!buffer.has_buffer_index()) {
       FailLocked("client sent buffer without index");
       return false;
@@ -1404,8 +2015,7 @@
       return false;
     }
 
-    uint32_t required_buffer_count =
-        BufferCountFromPortSettings(*port_settings_[port]);
+    uint32_t required_buffer_count = port_settings_[port]->buffer_count();
     if (buffer.buffer_index() >= required_buffer_count) {
       FailLocked("AddOutputBuffer()/AddInputBuffer() extra buffer - port: %d",
                  port);
@@ -1441,15 +2051,13 @@
           port_settings_[port]->buffer_constraints_version_ordinal();
       // Now we allocate all_packets_[port].
       ZX_DEBUG_ASSERT(all_packets_[port].empty());
-      uint32_t packet_count =
-          PacketCountFromPortSettings(*port_settings_[port]);
+      uint32_t packet_count = port_settings_[port]->packet_count();
       for (uint32_t i = 0; i < packet_count; i++) {
         // Private constructor to prevent core codec maybe creating its own
         // Packet instances (which isn't the intent) seems worth the hassle of
         // not using make_unique<>() here.
-        all_packets_[port].push_back(
-            std::unique_ptr<CodecPacket>(new CodecPacket(
-                port_settings_[port]->buffer_lifetime_ordinal(), i)));
+        all_packets_[port].push_back(std::unique_ptr<CodecPacket>(
+            new CodecPacket(port_settings_[port]->buffer_lifetime_ordinal(), i)));
       }
 
       {  // scope unlock
@@ -1470,20 +2078,19 @@
         }
       }  // ~unlock
 
-      // For OMX case, we tell OMX about the potentially-new buffer count
-      // separately later, just before moving from OMX loaded to OMX idle, or as
-      // part of mid-stream output config change.
+      is_port_buffers_configured_[port] = true;
+      buffers_done_configuring = true;
 
-      // For OMX case, we don't allocate OMX_BUFFERHEADERTYPE yet here by
-      // calling OMX UseBuffer() yet, because we can be in OMX_StateLoaded
-      // currently, and OMX UseBuffer() isn't valid until we're moving from
-      // OMX_StateLoaded to OMX_StateIdle.
-
-      is_port_configured_[port] = true;
-      done_configuring = true;
+      // For client-called AddOutputBuffer(), the last buffer being added is
+      // analogous to CompleteOutputBufferPartialSettings(); we handle that
+      // analogous-ness in IsOutputConfiguredLocked() (not by pretending we got
+      // a CompleteOutputBufferPartialSettings() here), so
+      // is_port_buffers_configured_[port] = true above is enough to make
+      // IsOutputConfiguredLocked() return true if this is a client-driven
+      // AddOutputBuffer().
     }
   }
-  return done_configuring;
+  return buffers_done_configuring;
 }
 
 bool CodecImpl::CheckOldBufferLifetimeOrdinalLocked(
@@ -1539,10 +2146,7 @@
   }
 
   EnsureStreamClosed(lock);
-
-  ZX_DEBUG_ASSERT((stream_lifetime_ordinal_ % 2 == 0) &&
-                  "expecting no current stream");
-  ZX_DEBUG_ASSERT(!stream_);
+  ZX_DEBUG_ASSERT(!IsStreamActiveLocked());
 
   // Now it's time to start the new stream.  We start the new stream at
   // Codec layer first then core codec layer.
@@ -1630,8 +2234,7 @@
     // detection.
     is_new_config_needed = true;
   } else if (IsOutputConfiguredLocked() &&
-             port_settings_[kOutputPort]
-                     ->buffer_constraints_version_ordinal() <=
+             port_settings_[kOutputPort]->buffer_constraints_version_ordinal() <=
                  core_codec_meh_output_buffer_constraints_version_ordinal_) {
     // The core codec previously expressed "meh" regarding the current config's
     // buffer_constraints_version_ordinal, so to avoid mixing that with core
@@ -1652,13 +2255,13 @@
     // at the start of a stream - it's still while a stream is active, and still
     // prevents this stream from outputting any data to the Codec client until
     // the Codec client re-configures output while this stream is active.
-    GenerateAndSendNewOutputConfig(lock, true);
+    GenerateAndSendNewOutputConstraints(lock, true);
 
     // Now we can wait for the client to catch up to the current output config
     // or for the client to tell the server to discard the current stream.
     while (!IsStoppingLocked() && !stream_->future_discarded() &&
            !IsOutputConfiguredLocked()) {
-      wake_stream_control_condition_.wait(lock);
+      RunAnySysmemCompletionsOrWait(lock);
     }
 
     if (IsStoppingLocked()) {
@@ -1703,9 +2306,7 @@
   // Now close the old stream at the Codec layer.
   EnsureCodecStreamClosedLockedInternal();
 
-  ZX_DEBUG_ASSERT((stream_lifetime_ordinal_ % 2 == 0) &&
-                  "expecting no current stream");
-  ZX_DEBUG_ASSERT(!stream_);
+  ZX_DEBUG_ASSERT(!IsStreamActiveLocked());
 }
 
 // The only valid caller of this is EnsureStreamClosed().  We have this in a
@@ -1726,6 +2327,87 @@
   ZX_DEBUG_ASSERT(stream_lifetime_ordinal_ % 2 == 0);
 }
 
+bool CodecImpl::RunAnySysmemCompletions(std::unique_lock<std::mutex>& lock) {
+  ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
+  // Typically this loop will run once, but on return we want the queue to be
+  // empty even if more showed up while in this method, for condition_variable
+  // signalling reasons.
+  bool any_ran = false;
+  while (!sysmem_completion_queue_.empty()) {
+    // We'll run them all, so extract all the items and run them all.
+    std::queue<fit::closure> local_batch_to_run;
+    local_batch_to_run.swap(sysmem_completion_queue_);
+    // The unlock doesn't cause queue re-ordering, though so far none of these
+    // items care anyway.
+    ScopedUnlock unlock(lock);
+    while (!local_batch_to_run.empty()) {
+      any_ran = true;
+      fit::closure to_run = std::move(local_batch_to_run.front());
+      local_batch_to_run.pop();
+      to_run();
+    }
+  }
+  return any_ran;
+}
+
+void CodecImpl::PostSysmemCompletion(fit::closure to_run) {
+  ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
+
+  {  // scope lock
+    std::unique_lock<std::mutex> lock(lock_);
+    sysmem_completion_queue_.emplace(std::move(to_run));
+    // In case there is no WaitEnsureSysmemReadyOnInput(), we post to
+    // StreamControl to ensure that RunAnySysmemCompletions() runs soon.
+    // Don't let them accumulate though.
+    if (!is_sysmem_runner_pending_) {
+      is_sysmem_runner_pending_ = true;
+      PostToStreamControl([this]{
+        std::unique_lock<std::mutex> lock(lock_);
+        RunAnySysmemCompletions(lock);
+        ZX_DEBUG_ASSERT(sysmem_completion_queue_.empty());
+        is_sysmem_runner_pending_ = false;
+      });
+    }
+  }  // ~lock
+
+  // In case to_run needs to get run by a QueueInput...StreamControl() method
+  // via WaitEnsureSysmemReadyOnInput(), we wake the StreamControl thread.  We
+  // must do this even if is_sysmem_runner_pending_, in case that runner won't
+  // run for a while due to WaitEnsureSysmemReadyOnInput() blocking
+  // StreamControl.
+  wake_stream_control_condition_.notify_all();
+}
+
+bool CodecImpl::WaitEnsureSysmemReadyOnInput(std::unique_lock<std::mutex>& lock) {
+  ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
+  // Input buffer re-config is not permitted unless there's no current stream.
+  ZX_DEBUG_ASSERT(!IsStreamActiveLocked());
+  while (!IsInputConfiguredLocked()) {
+    RunAnySysmemCompletionsOrWait(lock);
+    // No need to check for stream switch since it's not permitted for a client
+    // to be sending any message that can cause a new stream until after the
+    // client is done configuring input buffers (enforced elsewhere).
+    if (IsStoppingLocked()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void CodecImpl::RunAnySysmemCompletionsOrWait(
+    std::unique_lock<std::mutex>& lock) {
+  // If any sysmem completions ran, we immediately return, so that conditions
+  // can be checked again in the caller immediately.
+  ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
+  bool any_completions_ran = RunAnySysmemCompletions(lock);
+  ZX_DEBUG_ASSERT(sysmem_completion_queue_.empty());
+  if (!any_completions_ran) {
+    // We know sysmem_completion_queue_.empty() and the lock is held just before
+    // this wait().
+    wake_stream_control_condition_.wait(lock);
+  }
+}
+
 // This is called on Output ordering domain (FIDL thread) any time a message is
 // received which would be able to start a new stream.
 //
@@ -1836,10 +2518,10 @@
 }
 
 // This method is only called when buffer_constraints_action_required will be
-// true in an OnOutputConfig() message sent shortly after this method call.
+// true in an OnOutputConstraints() message sent shortly after this method call.
 //
 // Even if the client is switching streams rapidly without configuring output,
-// this method and GenerateAndSendNewOutputConfig() with
+// this method and GenerateAndSendNewOutputConstraints() with
 // buffer_constraints_action_required true always run in pairs.
 //
 // This is what starts the interval during which
@@ -1863,12 +2545,11 @@
     buffer_lifetime_ordinal_[kOutputPort]++;
     ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] % 2 == 0);
     ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] ==
-                    port_settings_[kOutputPort]->buffer_lifetime_ordinal() +
-                        1);
+                    port_settings_[kOutputPort]->buffer_lifetime_ordinal() + 1);
   }
 
   // When buffer_constraints_action_required true, we can assert in
-  // GenerateAndSendNewOutputConfig() that this value is still the
+  // GenerateAndSendNewOutputConstraints() that this value is still the
   // next_output_buffer_constraints_version_ordinal_ in that method.
   last_required_buffer_constraints_version_ordinal_[kOutputPort] =
       next_output_buffer_constraints_version_ordinal_;
@@ -1900,7 +2581,7 @@
   ZX_DEBUG_ASSERT(is_output_ordering_domain_done_with_recycle_output_packet);
 }
 
-void CodecImpl::GenerateAndSendNewOutputConfig(
+void CodecImpl::GenerateAndSendNewOutputConstraints(
     std::unique_lock<std::mutex>& lock,
     bool buffer_constraints_action_required) {
   // When client action is required, this can only happen on the StreamControl
@@ -1914,8 +2595,6 @@
   uint64_t current_stream_lifetime_ordinal = stream_lifetime_ordinal_;
   uint64_t new_output_buffer_constraints_version_ordinal =
       next_output_buffer_constraints_version_ordinal_++;
-  uint64_t new_output_format_details_version_ordinal =
-      next_output_format_details_version_ordinal_++;
 
   // If buffer_constraints_action_required true, the caller bumped the
   // last_required_buffer_constraints_version_ordinal_[kOutputPort] before
@@ -1928,67 +2607,87 @@
       (last_required_buffer_constraints_version_ordinal_[kOutputPort] ==
        new_output_buffer_constraints_version_ordinal));
 
-  // printf("GenerateAndSendNewOutputConfig
+  // printf("GenerateAndSendNewOutputConstraints
   // new_output_buffer_constraints_version_ordinal: %lu
   // buffer_constraints_action_required: %d\n",
   // new_output_buffer_constraints_version_ordinal,
   // buffer_constraints_action_required);
 
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig> output_config;
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+      output_constraints;
+  uint64_t next_output_format_details_version_ordinal =
+      next_output_format_details_version_ordinal_;
+  fuchsia::media::StreamOutputConfig output_config;
   {  // scope unlock
     ScopedUnlock unlock(lock);
+
     // Don't call the core codec under the lock_, because we can avoid doing so,
     // and to allow the core codec to use this thread to call back into
     // CodecImpl using this stack if needed.  So far we don't have any actual
     // known examples of a core codec using this thread to call back into
     // CodecImpl using this stack.
-    output_config = CoreCodecBuildNewOutputConfig(
+    output_constraints = CoreCodecBuildNewOutputConstraints(
         current_stream_lifetime_ordinal,
         new_output_buffer_constraints_version_ordinal,
-        new_output_format_details_version_ordinal,
         buffer_constraints_action_required);
+
+    // Temporarily still generate config also by converting from constraints and
+    // getting format also - this only works for now because core codecs happen
+    // to be able to provide format this early because they can only generate 1
+    // for now etc - but for now it can work during the transition.
+    fuchsia::media::StreamOutputFormat output_format = CoreCodecGetOutputFormat(
+        current_stream_lifetime_ordinal,
+        next_output_format_details_version_ordinal);
+    output_config.set_stream_lifetime_ordinal(
+        output_constraints->stream_lifetime_ordinal());
+    output_config.set_buffer_constraints_action_required(
+        output_constraints->buffer_constraints_action_required());
+    output_config.set_buffer_constraints(
+        fidl::Clone(output_constraints->buffer_constraints()));
+    output_config.set_format_details(
+        fidl::Clone(output_format.format_details()));
   }  // ~unlock
-  // We only call GenerateAndSendNewOutputConfig() from contexts that won't be
+
+  // We only call GenerateAndSendNewOutputConstraints() from contexts that won't be
   // changing the stream_lifetime_ordinal_, so the fact that we released the
   // lock above doesn't mean the stream_lifetime_ordinal_ could have changed, so
   // we can assert here that it's still the same as above.
   ZX_DEBUG_ASSERT(current_stream_lifetime_ordinal == stream_lifetime_ordinal_);
 
-  output_config_ = std::move(output_config);
+  output_constraints_ = std::move(output_constraints);
 
-  // Stay under lock after setting output_config_, to get proper ordering of
+  // Stay under lock after setting output_constraints_, to get proper ordering of
   // sent messages even if a hostile client deduces the content of this message
   // before we've sent it and manages to get the server to send another
-  // subsequent OnOutputConfig().
+  // subsequent OnOutputConstraints().
 
   ZX_DEBUG_ASSERT(sent_buffer_constraints_version_ordinal_[kOutputPort] + 1 ==
                   new_output_buffer_constraints_version_ordinal);
-  ZX_DEBUG_ASSERT(sent_format_details_version_ordinal_[kOutputPort] + 1 ==
-                  new_output_format_details_version_ordinal);
 
   // Setting this within same lock hold interval as we queue the message to be
-  // sent in order vs. other OnOutputConfig() messages.  This way we can verify
+  // sent in order vs. other OnOutputConstraints() messages.  This way we can verify
   // that the client's incoming messages are not trying to configure with
   // respect to a buffer_constraints_version_ordinal that is newer than we've
   // actually sent the client.
   sent_buffer_constraints_version_ordinal_[kOutputPort] =
       new_output_buffer_constraints_version_ordinal;
-  sent_format_details_version_ordinal_[kOutputPort] =
-      new_output_format_details_version_ordinal;
 
-  // Intentional copy of fuchsia::media::OutputConfig output_config_ here,
-  // as we want output_config_ to remain valid (at least for debugging reasons
-  // for now).
+  // Intentional copy of fuchsia::media::OutputConfig output_constraints_ here,
+  // as we want output_constraints_ to remain valid (at least for debugging
+  // reasons for now).
   PostToSharedFidl(
-      [this, output_config = fidl::Clone(*output_config_)]() mutable {
+      [this, output_constraints = fidl::Clone(*output_constraints_),
+       output_config = std::move(output_config)]() mutable {
         // See "is_bound_checks" comment up top.
         if (binding_.is_bound()) {
+          binding_.events().OnOutputConstraints(std::move(output_constraints));
+          // DEPRECATED - keep sending this temporarily.
           binding_.events().OnOutputConfig(std::move(output_config));
         }
       });
 }
 
-void CodecImpl::MidStreamOutputConfigChange(uint64_t stream_lifetime_ordinal) {
+void CodecImpl::MidStreamOutputConstraintsChange(uint64_t stream_lifetime_ordinal) {
   ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
@@ -2020,13 +2719,13 @@
 
     EnsureBuffersNotConfigured(lock, kOutputPort);
 
-    GenerateAndSendNewOutputConfig(lock, true);
+    GenerateAndSendNewOutputConstraints(lock, true);
 
     // Now we can wait for the client to catch up to the current output config
     // or for the client to tell the server to discard the current stream.
     while (!IsStoppingLocked() && !stream_->future_discarded() &&
            !IsOutputConfiguredLocked()) {
-      wake_stream_control_condition_.wait(lock);
+      RunAnySysmemCompletionsOrWait(lock);
     }
 
     if (IsStoppingLocked()) {
@@ -2040,6 +2739,9 @@
       // the start of the new stream.
       return;
     }
+
+    // For asserts.
+    stream_->ClearMidStreamOutputConstraintsChangeActive();
   }  // ~lock
 
   CoreCodecMidStreamOutputBufferReConfigFinish();
@@ -2047,6 +2749,109 @@
   VLOGF("Done with mid-stream format change.\n");
 }
 
+bool CodecImpl::FixupBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings,
+    fuchsia::sysmem::BufferCollectionConstraints* buffer_collection_constraints) {
+  fuchsia::sysmem::BufferUsage& usage = buffer_collection_constraints->usage;
+
+  if (IsCoreCodecMappedBufferNeeded(port)) {
+    // Not surprisingly, both decoders and encoders read from input and write to
+    // output.
+    if (port == kInputPort) {
+      if (usage.cpu & ~(fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageReadOften)) {
+        Fail("Core codec set disallowed CPU usage bits (input port).");
+        return false;
+      }
+      usage.cpu |=
+          fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageReadOften;
+    } else {
+      if (usage.cpu & ~(fuchsia::sysmem::cpuUsageWrite | fuchsia::sysmem::cpuUsageWriteOften)) {
+        Fail("Core codec set disallowed CPU usage bit(s) (output port).");
+        return false;
+      }
+      usage.cpu |=
+          fuchsia::sysmem::cpuUsageWrite | fuchsia::sysmem::cpuUsageWriteOften;
+    }
+  } else {
+    if (usage.cpu) {
+      Fail("Core codec set usage.cpu despite !IsCoreCodecMappedBufferNeeded()");
+      return false;
+    }
+    // The CPU won't touch the buffers at all.
+    usage.cpu = 0;
+  }
+  if (usage.vulkan) {
+    Fail("Core codec set usage.vulkan bits");
+    return false;
+  }
+  ZX_DEBUG_ASSERT(!usage.vulkan);
+  if (usage.display) {
+    Fail("Core codec set usage.display bits");
+    return false;
+  }
+  ZX_DEBUG_ASSERT(!usage.display);
+  if (IsCoreCodecHwBased()) {
+    // Let's see if we can deprecate videoUsageHwProtected, since it's redundant
+    // with secure_required and secure_permitted.
+    if (usage.video & fuchsia::sysmem::videoUsageHwProtected) {
+      Fail("Core codec set deprecated videoUsageHwProtected - disallow");
+      return false;
+    }
+    uint32_t allowed_video_usage_bits = IsDecoder() ? fuchsia::sysmem::videoUsageHwDecoder : fuchsia::sysmem::videoUsageHwEncoder;
+    if (usage.video & ~allowed_video_usage_bits) {
+      Fail("Core codec set disallowed video usage bit(s) - port: %d, usage: 0x%08x, allowed: 0x%08x", port, usage.video, allowed_video_usage_bits);
+      return false;
+    }
+    if (IsDecoder()) {
+      usage.video |= fuchsia::sysmem::videoUsageHwDecoder;
+    } else {
+      usage.video |= fuchsia::sysmem::videoUsageHwEncoder;
+    }
+  } else {
+    // Despite being a video decoder or encoder, a SW decoder or encoder doesn't
+    // count as videoUsageHwDecoder or videoUsageHwEncoder.  And definitely not
+    // videoUsageHwProtected.
+    usage.video = 0;
+  }
+
+  bool is_single_buffer_mode = partial_settings.has_single_buffer_mode() && partial_settings.single_buffer_mode();
+
+  uint32_t required_min_buffer_count_for_camping;
+  if (is_single_buffer_mode) {
+    required_min_buffer_count_for_camping =
+        static_cast<uint32_t>(partial_settings.packet_count_for_server() != 0);
+  } else {
+    required_min_buffer_count_for_camping =
+        partial_settings.packet_count_for_server();
+  }
+  if (buffer_collection_constraints->min_buffer_count_for_camping <
+      required_min_buffer_count_for_camping) {
+    Fail("Core codec set min_buffer_count_for_camping too low - min_buffer_count_for_camping: %lu required_min_buffer_count_for_camping: %lu", buffer_collection_constraints->min_buffer_count_for_camping, required_min_buffer_count_for_camping);
+    return false;
+  }
+
+  if (is_single_buffer_mode) {
+    if (buffer_collection_constraints->min_buffer_count_for_camping > 1) {
+      Fail("Core codec set min_buffer_count_for_camping too high for single_buffer_mode - min_buffer_count_for_camping: %lu", buffer_collection_constraints->min_buffer_count_for_camping);
+      return false;
+    }
+    if (buffer_collection_constraints->min_buffer_count_for_dedicated_slack != 0 ||
+        buffer_collection_constraints->min_buffer_count_for_shared_slack != 0) {
+      Fail("Core codec set slack with single_buffer_mode - min_buffer_count_for_dedicated_slack: %lu min_buffer_count_for_shared_slack: %lu", buffer_collection_constraints->min_buffer_count_for_dedicated_slack, buffer_collection_constraints->min_buffer_count_for_shared_slack);
+      return false;
+    }
+    if (buffer_collection_constraints->max_buffer_count != 1) {
+      Fail("CoreCodec must specify max_buffer_count 1 when single_buffer_mode");
+      return false;
+    }
+  }
+
+  // The rest of the constraints are entirely up to the core codec.
+
+  return true;
+}
+
 thrd_t CodecImpl::fidl_thread() { return shared_fidl_thread_; }
 
 void CodecImpl::SendFreeInputPacketLocked(fuchsia::media::PacketHeader header) {
@@ -2067,22 +2872,46 @@
 }
 
 bool CodecImpl::IsInputConfiguredLocked() {
-  return IsPortConfiguredCommonLocked(kInputPort);
+  return IsPortBuffersConfiguredCommonLocked(kInputPort);
 }
 
 bool CodecImpl::IsOutputConfiguredLocked() {
-  return IsPortConfiguredCommonLocked(kOutputPort);
+  if (!IsPortBuffersConfiguredCommonLocked(kOutputPort)) {
+    return false;
+  }
+  ZX_DEBUG_ASSERT(port_settings_[kOutputPort]);
+  if (!port_settings_[kOutputPort]->is_partial_settings()) {
+    return true;
+  }
+  if (!port_settings_[kOutputPort]->is_complete_seen_output()) {
+    return false;
+  }
+  return true;
 }
 
-bool CodecImpl::IsPortConfiguredCommonLocked(CodecPort port) {
+bool CodecImpl::IsPortBuffersConfiguredCommonLocked(CodecPort port) {
   // In addition to what we're able to assert here, when
-  // is_port_configured_[port], the core codec also has the port
+  // is_port_buffers_configured_[port], the core codec also has the port
   // configured.
-  ZX_DEBUG_ASSERT(!is_port_configured_[port] ||
+  ZX_DEBUG_ASSERT(!is_port_buffers_configured_[port] ||
                   port_settings_[port] &&
-                      all_buffers_[port].size() ==
-                          BufferCountFromPortSettings(*port_settings_[port]));
-  return is_port_configured_[port];
+                      all_buffers_[port].size() == port_settings_[port]->buffer_count());
+  return is_port_buffers_configured_[port];
+}
+
+bool CodecImpl::IsPortBuffersAtLeastPartiallyConfiguredLocked(CodecPort port) {
+  if (IsPortBuffersConfiguredCommonLocked(port)) {
+    return true;
+  }
+  if (!port_settings_[port]) {
+    return false;
+  }
+  if (!port_settings_[port]->is_partial_settings()) {
+    return false;
+  }
+  ZX_DEBUG_ASSERT(port_settings_[port] && port_settings_[port]->is_partial_settings());
+  ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[port] % 2 == 1);
+  return true;
 }
 
 void CodecImpl::Fail(const char* format, ...) {
@@ -2118,7 +2947,7 @@
 void CodecImpl::vFail(bool is_fatal, const char* format, va_list args) {
   {  // scope lock
     std::lock_guard<std::mutex> lock(lock_);
-    vFailLocked(false, format, args);
+    vFailLocked(is_fatal, format, args);
   }  // ~lock
 }
 
@@ -2202,6 +3031,16 @@
   return IsStoppingLocked();
 }
 
+bool CodecImpl::IsDecoder() {
+  ZX_DEBUG_ASSERT(!!decoder_params_ ^ !!encoder_params_);
+  return !!decoder_params_;
+}
+
+bool CodecImpl::IsEncoder() {
+  ZX_DEBUG_ASSERT(!!decoder_params_ ^ !!encoder_params_);
+  return !!encoder_params_;
+}
+
 // true - maybe it's the core codec thread.
 // false - it's definitely not the core codec thread.
 bool CodecImpl::IsPotentiallyCoreCodecThread() {
@@ -2291,27 +3130,29 @@
   }  // ~lock
 }
 
-void CodecImpl::onCoreCodecMidStreamOutputConfigChange(
+void CodecImpl::onCoreCodecMidStreamOutputConstraintsChange(
     bool output_re_config_required) {
+  VLOGF("CodecImpl::onCoreCodecMidStreamOutputConstraintsChange(): %d\n",
+        output_re_config_required);
   // For now, the core codec thread is the only thread this gets called from.
   ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread());
   // For a OMX_EventPortSettingsChanged that doesn't demand output buffer
   // re-config before more output data, this translates to an ordered emit
-  // of a no-action-required OnOutputConfig() that just updates to the new
+  // of a no-action-required OnOutputConstraints() that just updates to the new
   // format, without demanding output buffer re-config.  HDR info can be
   // conveyed this way, ordered with respect to output frames.  OMX
   // requires that we use this thread to collect OMX format info during
   // EventHandler().
   if (!output_re_config_required) {
     std::unique_lock<std::mutex> lock(lock_);
-    GenerateAndSendNewOutputConfig(
+    GenerateAndSendNewOutputConstraints(
         lock,
         false);  // buffer_constraints_action_required
     return;
   }
 
-  // We have an OMX_EventPortSettingsChanged that does demand output
-  // buffer re-config before more output data.
+  // We have an output constraints change that does demand output buffer
+  // re-config before more output data.
   ZX_DEBUG_ASSERT(output_re_config_required);
 
   // We post over to StreamControl domain because we need to synchronize
@@ -2319,7 +3160,7 @@
   // When we get over there to StreamControl, we'll check if we're still
   // talking about the same stream_lifetime_ordinal, and if not, we ignore
   // the event, because a new stream may or may not have the same output
-  // settings, and we'll be re-generating an OnOutputConfig() as needed
+  // settings, and we'll be re-generating an OnOutputConstraints() as needed
   // from current/later OMX output config anyway.  Here are the
   // possibilities:
   //   * Prior to the client moving to a new stream, we process this event
@@ -2332,16 +3173,29 @@
   //     StreamControl.  In this case we ignore the event on StreamControl
   //     domain since its stale by that point, but instead we use
   //     omx_meh_output_buffer_constraints_version_ordinal_ to cause the
-  //     client's next stream to start with a new OnOutputConfig() that
+  //     client's next stream to start with a new OnOutputConstraints() that
   //     the client must catch up to before the stream can fully start.
   //     This way we know we're not ignoring a potential change to
   //     nBufferCountMin or anything like that.
   uint64_t local_stream_lifetime_ordinal;
   {  // scope lock
     std::lock_guard<std::mutex> lock(lock_);
+
+    // The core codec is only allowed to call this mehtod while there's an
+    // active stream.
+    ZX_DEBUG_ASSERT(IsStreamActiveLocked());
+
+    // The client is allowed to essentially forget what the format is on any
+    // mid-stream buffer config change, so remember to re-send the format to the
+    // client before the next output packet of this stream.
+    stream_->SetOutputFormatPending();
+
+    // For asserts.
+    stream_->SetMidStreamOutputConstraintsChangeActive();
+
     // This part is not speculative.  OMX has indicated that it's at least
     // meh about the current output config, so ensure we do a required
-    // OnOutputConfig() before the next stream starts, even if the client
+    // OnOutputConstraints() before the next stream starts, even if the client
     // moves on to a new stream such that the speculative part below becomes
     // stale.
     core_codec_meh_output_buffer_constraints_version_ordinal_ =
@@ -2356,10 +3210,23 @@
   }  // ~lock
   PostToStreamControl(
       [this, stream_lifetime_ordinal = local_stream_lifetime_ordinal] {
-        MidStreamOutputConfigChange(stream_lifetime_ordinal);
+        MidStreamOutputConstraintsChange(stream_lifetime_ordinal);
       });
 }
 
+void CodecImpl::onCoreCodecOutputFormatChange() {
+  ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread());
+  std::lock_guard<std::mutex> lock(lock_);
+  ZX_DEBUG_ASSERT(IsStreamActiveLocked());
+  // In future we could relax this requirement, but for now we don't allow
+  // output format changes, output packets, or EOS while mid-stream constraints
+  // change is active.
+  ZX_DEBUG_ASSERT(!stream_->is_mid_stream_output_constraints_change_active());
+  // Next time the core codec asks to output a packet, we'll send the format
+  // first.
+  stream_->SetOutputFormatPending();
+}
+
 void CodecImpl::onCoreCodecInputPacketDone(CodecPacket* packet) {
   // Free/busy coherency from Codec interface to OMX doesn't involve trusting
   // the client, so assert we're doing it right server-side.
@@ -2389,6 +3256,56 @@
 void CodecImpl::onCoreCodecOutputPacket(CodecPacket* packet,
                                         bool error_detected_before,
                                         bool error_detected_during) {
+  ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread());
+
+  {  // scope lock
+    std::unique_lock<std::mutex> lock(lock_);
+
+    // The core codec shouldn't output a packet until after
+    // CoreCodecStartStream() and input data availability in the case that
+    // output buffer config was already suitable, or until after
+    // CoreCodecMidStreamOutputBufferReConfigFinish() in the case that output
+    // buffer config wasn't suitable (not configured or not suitable) or
+    // changed mid-stream.  See also comments in codec_adapter.h.
+    ZX_DEBUG_ASSERT(IsOutputConfiguredLocked());
+
+    // Before we send the packet, we check whether the stream has output format
+    // pending, which means we need to send the output format before the output
+    // packet (and clear the pending state).
+    ZX_DEBUG_ASSERT(IsStreamActiveLocked());
+
+    ZX_DEBUG_ASSERT(!stream_->is_mid_stream_output_constraints_change_active());
+
+    if (stream_->output_format_pending()) {
+      stream_->ClearOutputFormatPending();
+      uint64_t stream_lifetime_ordinal = stream_lifetime_ordinal_;
+      uint64_t new_output_format_details_version_ordinal =
+          next_output_format_details_version_ordinal_++;
+      fuchsia::media::StreamOutputFormat output_format;
+      {  // scope unlock
+        ScopedUnlock unlock(lock);
+        output_format = CoreCodecGetOutputFormat(
+              stream_lifetime_ordinal,
+              new_output_format_details_version_ordinal);
+      }  // ~unlock
+      // Stream change while unlocked above won't happen because we're on
+      // InputData domain which is fenced as part of stream switch.
+      ZX_DEBUG_ASSERT(stream_lifetime_ordinal == stream_lifetime_ordinal_);
+      ZX_DEBUG_ASSERT(new_output_format_details_version_ordinal ==
+                      next_output_format_details_version_ordinal_ - 1);
+      ZX_DEBUG_ASSERT(sent_format_details_version_ordinal_[kOutputPort] + 1 ==
+                      new_output_format_details_version_ordinal);
+      sent_format_details_version_ordinal_[kOutputPort] =
+          new_output_format_details_version_ordinal;
+      PostToSharedFidl([this, output_format = std::move(output_format)]() mutable {
+        // See "is_bound_checks" comment up top.
+        if (binding_.is_bound()) {
+          binding_.events().OnOutputFormat(std::move(output_format));
+        }
+      });
+    }
+  }
+
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
     // This helps verify that packet lifetimes are coherent, but we can't do the
@@ -2434,11 +3351,13 @@
 }
 
 void CodecImpl::onCoreCodecOutputEndOfStream(bool error_detected_before) {
+  VLOGF("CodecImpl::onCoreCodecOutputEndOfStream()\n");
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
+    ZX_DEBUG_ASSERT(IsStreamActiveLocked());
+    ZX_DEBUG_ASSERT(!stream_->is_mid_stream_output_constraints_change_active());
     stream_->SetOutputEndOfStream();
     output_end_of_stream_seen_.notify_all();
-    VLOGF("sending OnOutputEndOfStream()\n");
     PostSerial(shared_fidl_dispatcher_,
                [this, stream_lifetime_ordinal = stream_lifetime_ordinal_,
                 error_detected_before] {
@@ -2520,6 +3439,190 @@
 
 bool CodecImpl::Stream::failure_seen() { return failure_seen_; }
 
+void CodecImpl::Stream::SetOutputFormatPending() {
+  output_format_pending_ = true;
+}
+
+void CodecImpl::Stream::ClearOutputFormatPending() {
+  output_format_pending_ = false;
+}
+
+bool CodecImpl::Stream::output_format_pending() {
+  return output_format_pending_;
+}
+
+void CodecImpl::Stream::SetMidStreamOutputConstraintsChangeActive() {
+  ZX_DEBUG_ASSERT(!is_mid_stream_output_constraints_change_active_);
+  is_mid_stream_output_constraints_change_active_ = true;
+}
+
+void CodecImpl::Stream::ClearMidStreamOutputConstraintsChangeActive() {
+  ZX_DEBUG_ASSERT(is_mid_stream_output_constraints_change_active_);
+  is_mid_stream_output_constraints_change_active_ = false;
+}
+
+bool CodecImpl::Stream::is_mid_stream_output_constraints_change_active() {
+  return is_mid_stream_output_constraints_change_active_;
+}
+
+CodecImpl::PortSettings::PortSettings(CodecImpl* parent, CodecPort port, fuchsia::media::StreamBufferSettings settings)
+  : parent_(parent), port_(port), settings_(std::make_unique<fuchsia::media::StreamBufferSettings>(std::move(settings))) {
+  // nothing else to do here
+}
+
+CodecImpl::PortSettings::PortSettings(CodecImpl* parent, CodecPort port,
+    fuchsia::media::StreamBufferPartialSettings partial_settings)
+  : parent_(parent), port_(port), partial_settings_(
+      std::make_unique<fuchsia::media::StreamBufferPartialSettings>(
+        std::move(partial_settings))) {
+  // nothing else to do here
+}
+
+CodecImpl::PortSettings::~PortSettings() {
+  // To be safe, the unbind needs to occur on the FIDL thread.  In addition, we
+  // want to send a clean Close() to avoid causing the LogicalBufferCollection
+  // to fail.  Since we're not a crashing process, this is a clean close by
+  // definition.
+  if (buffer_collection_ && thrd_current() != parent_->fidl_thread()) {
+    parent_->PostToSharedFidl([buffer_collection = std::move(buffer_collection_)]{
+      // Sysmem will notice the Close() before the PEER_CLOSED.
+      buffer_collection->Close();
+      // ~buffer_collection on FIDL thread
+    });
+  }
+}
+
+void CodecImpl::PortSettings::SetBufferCollectionInfo(fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) {
+  ZX_DEBUG_ASSERT(!buffer_collection_info_);
+  buffer_collection_info_ = std::make_unique<fuchsia::sysmem::BufferCollectionInfo_2>(std::move(buffer_collection_info));
+}
+
+const fuchsia::sysmem::BufferCollectionInfo_2& CodecImpl::PortSettings::buffer_collection_info() {
+  ZX_DEBUG_ASSERT(buffer_collection_info_);
+  return *buffer_collection_info_;
+}
+
+uint64_t CodecImpl::PortSettings::buffer_lifetime_ordinal() {
+  if (is_partial_settings()) {
+    return partial_settings_->buffer_lifetime_ordinal();
+  } else {
+    return settings_->buffer_lifetime_ordinal();
+  }
+}
+
+uint64_t CodecImpl::PortSettings::buffer_constraints_version_ordinal() {
+  if (is_partial_settings()) {
+    return partial_settings_->buffer_constraints_version_ordinal();
+  } else {
+    return settings_->buffer_constraints_version_ordinal();
+  }
+}
+
+uint32_t CodecImpl::PortSettings::packet_count() {
+  if (is_partial_settings()) {
+    // Asking before we have buffer_collection_info_ would potentially get the
+    // wrong answer.
+    ZX_DEBUG_ASSERT(buffer_collection_info_);
+    return std::max(
+      partial_settings_->packet_count_for_server() +
+          partial_settings_->packet_count_for_client(),
+      buffer_collection_info_->buffer_count);
+  } else {
+    return settings_->packet_count_for_server() + settings_->packet_count_for_client();
+  }
+}
+
+uint32_t CodecImpl::PortSettings::buffer_count() {
+  if (is_partial_settings()) {
+    ZX_DEBUG_ASSERT(buffer_collection_info_);
+    return buffer_collection_info_->buffer_count;
+  } else {
+    if (settings_->single_buffer_mode()) {
+      return 1;
+    }
+    return packet_count();
+  }
+}
+
+bool CodecImpl::PortSettings::is_partial_settings() {
+  // Exactly one of settings_ or partial_settings_ is set.
+  ZX_DEBUG_ASSERT(!!settings_ ^ !!partial_settings_);
+  return !!partial_settings_;
+}
+
+const fuchsia::media::StreamBufferPartialSettings& CodecImpl::PortSettings::partial_settings() {
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  return *partial_settings_;
+}
+
+const fuchsia::media::StreamBufferSettings& CodecImpl::PortSettings::settings() {
+  ZX_DEBUG_ASSERT(!is_partial_settings());
+  return *settings_;
+}
+
+fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> CodecImpl::PortSettings::TakeToken() {
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(partial_settings_->has_sysmem_token());
+  fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token =
+      std::move(*partial_settings_->mutable_sysmem_token());
+  partial_settings_->clear_sysmem_token();
+  return token;
+}
+
+zx::vmo CodecImpl::PortSettings::TakeVmo(uint32_t buffer_index) {
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(buffer_collection_info_);
+  ZX_DEBUG_ASSERT(buffer_index < buffer_collection_info_->buffer_count);
+  return std::move(buffer_collection_info_->buffers[buffer_index].vmo);
+}
+
+fidl::InterfaceRequest<fuchsia::sysmem::BufferCollection> CodecImpl::PortSettings::NewBufferCollectionRequest(async_dispatcher_t* dispatcher) {
+  ZX_DEBUG_ASSERT(thrd_current() == parent_->fidl_thread());
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(!buffer_collection_);
+  return buffer_collection_.NewRequest(dispatcher);
+}
+
+fuchsia::sysmem::BufferCollectionPtr& CodecImpl::PortSettings::buffer_collection() {
+  ZX_DEBUG_ASSERT(thrd_current() == parent_->fidl_thread());
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  return buffer_collection_;
+}
+
+void CodecImpl::PortSettings::UnbindBufferCollection() {
+  ZX_DEBUG_ASSERT(thrd_current() == parent_->fidl_thread());
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  // return value intentionally ignored and deleted
+  buffer_collection_.Unbind();
+}
+
+bool CodecImpl::PortSettings::is_complete_seen_output() {
+  ZX_DEBUG_ASSERT(port_ == kOutputPort);
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  return is_complete_seen_output_;
+}
+
+void CodecImpl::PortSettings::SetCompleteSeenOutput() {
+  ZX_DEBUG_ASSERT(port_ == kOutputPort);
+  ZX_DEBUG_ASSERT(thrd_current() == parent_->fidl_thread());
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(!is_complete_seen_output_);
+  is_complete_seen_output_ = true;
+}
+
+uint64_t CodecImpl::PortSettings::vmo_usable_start(uint32_t buffer_index) {
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(buffer_collection_info_);
+  ZX_DEBUG_ASSERT(buffer_index < buffer_collection_info_->buffer_count);
+  return buffer_collection_info_->buffers[buffer_index].vmo_usable_start;
+}
+
+uint64_t CodecImpl::PortSettings::vmo_usable_size() {
+  ZX_DEBUG_ASSERT(is_partial_settings());
+  ZX_DEBUG_ASSERT(buffer_collection_info_);
+  return buffer_collection_info_->settings.buffer_settings.size_bytes;
+}
+
 //
 // CoreCodec wrappers, for the asserts.  These asserts, and the way we ensure
 // at compile time that this class has a method for every method of
@@ -2535,6 +3638,31 @@
   codec_adapter_->CoreCodecInit(initial_input_format_details);
 }
 
+fuchsia::sysmem::BufferCollectionConstraints
+CodecImpl::CoreCodecGetBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
+  ZX_DEBUG_ASSERT(port == kInputPort &&
+                      thrd_current() == stream_control_thread_ ||
+                  port == kOutputPort && thrd_current() == fidl_thread());
+  // We don't intend to send the sysmem token to the core codec directly, just
+  // because it doesn't really need to participate directly that way, and this
+  // lets us keep direct interaction with sysmem in CodecImpl instead of each
+  // core codec.
+  ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token());
+  return codec_adapter_->CoreCodecGetBufferCollectionConstraints(port, stream_buffer_constraints, partial_settings);
+}
+
+void CodecImpl::CoreCodecSetBufferCollectionInfo(
+    CodecPort port,
+    const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) {
+  ZX_DEBUG_ASSERT(port == kInputPort &&
+                      thrd_current() == stream_control_thread_ ||
+                  port == kOutputPort && thrd_current() == fidl_thread());
+  codec_adapter_->CoreCodecSetBufferCollectionInfo(port, buffer_collection_info);
+}
+
 void CodecImpl::CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) {
   ZX_DEBUG_ASSERT(port == kInputPort &&
                       thrd_current() == stream_control_thread_ ||
@@ -2591,22 +3719,57 @@
   return codec_adapter_->IsCoreCodecRequiringOutputConfigForFormatDetection();
 }
 
+bool CodecImpl::IsCoreCodecMappedBufferNeeded(CodecPort port) {
+  ZX_DEBUG_ASSERT(port == kInputPort && thrd_current() == stream_control_thread_ ||
+                  port == kOutputPort && thrd_current() == fidl_thread());
+  return codec_adapter_->IsCoreCodecMappedBufferNeeded(port);
+}
+
+bool CodecImpl::IsCoreCodecHwBased() {
+  return codec_adapter_->IsCoreCodecHwBased();
+}
+
 // Caller must ensure that this is called only on one thread at a time, only
 // during setup, during a core codec initiated mid-stream format change, or
 // during stream start before any input data has been delivered for the new
 // stream.
-std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-CodecImpl::CoreCodecBuildNewOutputConfig(
+std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+CodecImpl::CoreCodecBuildNewOutputConstraints(
     uint64_t stream_lifetime_ordinal,
     uint64_t new_output_buffer_constraints_version_ordinal,
-    uint64_t new_output_format_details_version_ordinal,
     bool buffer_constraints_action_required) {
   ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread() ||
                   thrd_current() == stream_control_thread_);
-  return codec_adapter_->CoreCodecBuildNewOutputConfig(
-      stream_lifetime_ordinal, new_output_buffer_constraints_version_ordinal,
-      new_output_format_details_version_ordinal,
-      buffer_constraints_action_required);
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints> constraints =
+      codec_adapter_->CoreCodecBuildNewOutputConstraints(
+          stream_lifetime_ordinal,
+          new_output_buffer_constraints_version_ordinal,
+          buffer_constraints_action_required);
+  ZX_DEBUG_ASSERT(constraints);
+  ZX_DEBUG_ASSERT(constraints->has_stream_lifetime_ordinal());
+  ZX_DEBUG_ASSERT(constraints->stream_lifetime_ordinal() == stream_lifetime_ordinal);
+  ZX_DEBUG_ASSERT(constraints->has_buffer_constraints());
+  ZX_DEBUG_ASSERT(constraints->buffer_constraints().has_buffer_constraints_version_ordinal());
+  ZX_DEBUG_ASSERT(constraints->buffer_constraints().buffer_constraints_version_ordinal() == new_output_buffer_constraints_version_ordinal);
+  ZX_DEBUG_ASSERT(constraints->has_buffer_constraints_action_required());
+  ZX_DEBUG_ASSERT(constraints->buffer_constraints_action_required() == buffer_constraints_action_required);
+  return constraints;
+}
+
+fuchsia::media::StreamOutputFormat CodecImpl::CoreCodecGetOutputFormat(
+    uint64_t stream_lifetime_ordinal,
+    uint64_t new_output_format_details_version_ordinal) {
+  // TODO(dustingreen): Remove the 2nd part of this assert condition along with
+  // removal of SetOutputBufferSettings() and OnOutputConfig() messages.
+  ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread() || thrd_current() == stream_control_thread_);
+  fuchsia::media::StreamOutputFormat format =
+      codec_adapter_->CoreCodecGetOutputFormat(stream_lifetime_ordinal, new_output_format_details_version_ordinal);
+  ZX_DEBUG_ASSERT(format.has_stream_lifetime_ordinal());
+  ZX_DEBUG_ASSERT(format.stream_lifetime_ordinal() == stream_lifetime_ordinal);
+  ZX_DEBUG_ASSERT(format.has_format_details());
+  ZX_DEBUG_ASSERT(format.format_details().has_format_details_version_ordinal());
+  ZX_DEBUG_ASSERT(format.format_details().format_details_version_ordinal() == new_output_format_details_version_ordinal);
+  return format;
 }
 
 void CodecImpl::CoreCodecMidStreamOutputBufferReConfigPrepare() {
diff --git a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter.h b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter.h
index 5230ab4..37d591e 100644
--- a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter.h
+++ b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter.h
@@ -59,6 +59,15 @@
   // or may require an output config (false).
   virtual bool IsCoreCodecRequiringOutputConfigForFormatDetection() = 0;
 
+  // If true, the codec requires that the buffer VMOs be mappable for direct
+  // access by the CPU.
+  virtual bool IsCoreCodecMappedBufferNeeded(CodecPort port) = 0;
+
+  // If true, the codec is HW-based, in the sense that at least some of the
+  // processing is performed by specialized processing HW running separately
+  // from any CPU execution context.
+  virtual bool IsCoreCodecHwBased() = 0;
+
   // The initial input format details and later input format details will
   // _often_ remain the same overall format, and only differ in ways that are
   // reasonable on a format-specific basis.  However, not always.  A core codec
@@ -88,6 +97,78 @@
   virtual void CoreCodecInit(
       const fuchsia::media::FormatDetails& initial_input_format_details) = 0;
 
+  // All codecs must implement this for both ports.  The returned structure will
+  // be sent to sysmem in a SetConstraints() call.  This method can be called
+  // on the FIDL thread or the StreamControl domain (thread for now).
+  //
+  // Input:
+  //
+  // For now, a core codec has no way to trigger being asked for new input
+  // constraints, so the input constraints (for now) need to be generally
+  // applicable to any potential setting/property of the input.
+  //
+  // A decoder should permit a fairly wide range of buffer space, without
+  // worrying whether the min is enough to efficiently handle a high bitrate.
+  // The CodecImpl will own bumping up the min based on approximate bitrate
+  // provided in the initial decoder creation parameters and/or per-stream input
+  // format details (this logic is shared because it can reasonably be shared).
+  // A core codec that has special requirements for extra input buffer space
+  // given a particular bitrate can take it upon itself to set the input min
+  // buffer space, but the idea is that typically it won't be necessary for a
+  // decoder to increase the min based on input bitrate beyond what CodecImpl
+  // does, so needing to do this should be fairly rare.
+  //
+  // A video encoder which needs to vary it's input BufferCollectionConstraints
+  // based on encoder settings can do so, using the data provided to
+  // CoreCodecInit().  If later use of QueueInputFormatDetails() (per-stream)
+  // results in an input packet that conforms to the old
+  // BufferCollectionConstraints but does not conform to the effective new
+  // BufferCollectionConstraints, the core codec can use
+  // CodecAdapterEvents::onCoreCodecFailCodec() (the core codec should fail the
+  // codec instance in this case rather than attempt to handle input data that
+  // is outside the bounds that would have been indicated by the core codec had
+  // the current input format details been used as the initial format details).
+  // Clients that change the input format details on the fly should be willing
+  // to re-request a new codec instance at least once starting with the new
+  // input format details via the CodecFactory.  This is true for additional
+  // reasons beyond this paragraph involving the possibility of accelerated but
+  // partial codec implementations.  If a client needs to change input format
+  // details but doesn't want to concern itself with tracking whether the
+  // current codec was created with the current input format details, a client
+  // can instead choose to always create a new codec via CodecFactory on any
+  // change to the input format details.
+  //
+  // Output:
+  //
+  // A core codec can trigger this method to get called again by indicating an
+  // output format detection/change with action_required true via
+  // CoreCodecEvents::onCoreCodecMidStreamOutputConstraintsChange().
+  //
+  // Filling out the usage bits is optional.  If the usage bits are not filled
+  // out (all still 0), the caller will fill them out based on
+  // IsCoreCodecMappedBufferNeeded() and IsCoreCodecHwBased().  The core codec
+  // must either leave usage set to all 0, or completely fill them out.
+  virtual fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) = 0;
+
+  // There are no VMO handles in the buffer_collection_info.  Those are instead
+  // provided via calls to CoreCodecAddBuffer(), as CodecImpl handles allocation
+  // of CodecBuffer instances (each of which has a VMO).
+  //
+  // This method allows a core codec to know things like buffer_count, whether
+  // sysmem selected CPU domain or RAM domain for sharing of buffers, whether
+  // protected buffers were allocated, etc.
+  //
+  // This call occurs regardless of whether "settings" or "partial settings" are
+  // set (regardless of whether the client is using sysmem), after the client
+  // sets input or output settings, and before the first buffer is added.
+  virtual void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) = 0;
+
   // Stream lifetime:
   //
   // The CoreCodecStartStream() and CoreCodecStopStream() calls bracket the
@@ -148,6 +229,12 @@
 
   // Add input or output buffer.
   //
+  // The buffers added via this method correspond to the buffers of the buffer
+  // collection - these are buffers of the collection most recently indicated via
+  // a call to CoreCodecSetBufferCollectionInfo().  While the VMOs are
+  // intentionally not included in that call, the VMOs are indicated here (this
+  // lets the CodecImpl own allocation of CodecBuffer instances).
+  //
   // A core codec may be able to fully configure a buffer during this call and
   // later ignore CoreCodecConfigureBuffers(), or a core codec may use
   // CoreCodecConfigureBuffers() to finish configuring buffers.
@@ -183,7 +270,7 @@
   // be called while there's no active stream, or after a stream is started but
   // before any input data is queued, or during processing shortly after the
   // core codec calling
-  // onCoreCodecMidStreamOutputConfigChange(true), after
+  // onCoreCodecMidStreamOutputConstraintsChange(true), after
   // CoreCodecMidStreamOutputBufferReConfigPrepare() and before
   // CoreCodecMidStreamOutputBufferReConfigFinish().
   //
@@ -207,22 +294,34 @@
   // output_re_config_required false:
   //
   // This is called on the same thread and same stack as
-  // onCoreCodecMidStreamOutputConfigChange() (and with same stream still
+  // onCoreCodecMidStreamOutputConstraintsChange() (and with same stream still
   // active).
-  virtual std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  virtual std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) = 0;
 
+  // This will be called on the InputData domain, during the core codec's call
+  // to onCoreCodecOutputPacket(), so that the format will be delivered at most
+  // once before any packet which needs a new format to be indicated.  The core
+  // codec can trigger this to occur during the next onCoreCodecOutputPacket()
+  // by calling onCoreCodecOutputFormatChange().  The tracking of pending
+  // output format is per-stream, and all streams start with a pending output
+  // format, so a core codec need not call onCoreCodecOutputFormatChange()
+  // unless the format change is mid-stream (but calling before the first packet
+  // is allowed and not harmful).
+  virtual fuchsia::media::StreamOutputFormat CoreCodecGetOutputFormat(
+      uint64_t stream_lifetime_ordinal,
+      uint64_t new_output_format_details_version_ordinal) = 0;
+
   // CoreCodecMidStreamOutputBufferReConfigPrepare()
   //
   // For a mid-stream format change where output buffer re-configuration is
   // needed (as initiated async by the core codec calling
-  // CodecAdapterEvents::onCoreCodecMidStreamOutputConfigChange(true)), this
+  // CodecAdapterEvents::onCoreCodecMidStreamOutputConstraintsChange(true)), this
   // method is called on the StreamControl thread before the client is notified
-  // of the need for output buffer re-config (via OnOutputConfig() with
+  // of the need for output buffer re-config (via OnOutputConstraints() with
   // buffer_constraints_action_required true).
   //
   // The core codec should do whatever is necessary to ensure that output
@@ -261,6 +360,16 @@
   //
   // The core codec should do whatever is necessary to get back into normal
   // steady-state operation in this method.
+  //
+  // The core codec must not onCoreCodecOutputPacket() or
+  // onCoreCodecOutputEndOfStream() until this method has been called, or until
+  // CoreCodecStartStream() is called and some input is available, should the
+  // current stream be stopped before completing mid-stream output buffer
+  // re-config.  This works partly because the CodecImpl guarantees that if a
+  // mid-stream re-config didn't finish, there will be a complete output
+  // re-config before the CoreCodecStartStream() - in other words this re-config
+  // is abandoned and a new one takes its place and is fully complete prior to
+  // the new stream starting.
   virtual void CoreCodecMidStreamOutputBufferReConfigFinish() = 0;
 
  protected:
diff --git a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter_events.h b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter_events.h
index b826256..e45f732 100644
--- a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter_events.h
+++ b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_adapter_events.h
@@ -32,13 +32,23 @@
   // call is propertly ordered with respect to onCoreCodecOutputPacket() and
   // onCoreCodecOutputEndOfStream() calls.
   //
-  // A call to onCoreCodecMidStreamOutputConfigChange(true) must not be
+  // A call to onCoreCodecMidStreamOutputConstraintsChange(true) must not be
   // followed by any more output (including EndOfStream) until the associated
   // output re-config is completed by a call to
   // CoreCodecMidStreamOutputBufferReConfigFinish().
-  virtual void onCoreCodecMidStreamOutputConfigChange(
+  virtual void onCoreCodecMidStreamOutputConstraintsChange(
       bool output_re_config_required) = 0;
 
+  // When the core codec calls this method, the CodecImpl will note that the
+  // format has changed, and on next onCoreCodecOutputPacket(), the CodecImpl
+  // will ask the core codec for the format and generate and send an
+  // OnOutputformat() message before that output packet.  This way, the core
+  // codec is free to call onCoreCodecOutputFormat() repeatedly without any
+  // packet in between, with CodecImpl collapsing these into one
+  // OnOutputFormat() to avoid the extra message (so it doesn't have to be sent
+  // and doesn't have to be handled by clients).
+  virtual void onCoreCodecOutputFormatChange() = 0;
+
   virtual void onCoreCodecInputPacketDone(CodecPacket* packet) = 0;
 
   virtual void onCoreCodecOutputPacket(CodecPacket* packet,
diff --git a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_impl.h b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_impl.h
index 457ea2f..ad55ff9 100644
--- a/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_impl.h
+++ b/garnet/lib/media/codec_impl/include/lib/media/codec_impl/codec_impl.h
@@ -19,6 +19,7 @@
 #include <zircon/compiler.h>
 
 #include <list>
+#include <queue>
 
 // The CodecImpl class can be used for both SW and HW codecs.
 //
@@ -62,12 +63,21 @@
                   public CodecAdapterEvents,
                   private CodecAdapter {
  public:
+  // The CodecImpl will take care of doing set_error_handler() on the sysmem
+  // connection.  The sysmem connection should be set up to use the
+  // shared_fidl_dispatcher.
   CodecImpl(
+      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
       std::unique_ptr<CodecAdmission> codec_admission,
       async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
       std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
       fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request);
+
+  // The CodecImpl will take care of doing set_error_handler() on the sysmem
+  // connection.  The sysmem connection should be set up to use the
+  // shared_fidl_dispatcher.
   CodecImpl(
+      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
       std::unique_ptr<CodecAdmission> codec_admission,
       async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
       std::unique_ptr<fuchsia::mediacodec::CreateEncoder_Params> encoder_params,
@@ -126,9 +136,15 @@
   void SetInputBufferSettings(
       fuchsia::media::StreamBufferSettings input_settings) override;
   void AddInputBuffer(fuchsia::media::StreamBuffer buffer) override;
+  void SetInputBufferPartialSettings(
+      fuchsia::media::StreamBufferPartialSettings input_settings) override;
   void SetOutputBufferSettings(
       fuchsia::media::StreamBufferSettings output_settings) override;
   void AddOutputBuffer(fuchsia::media::StreamBuffer buffer) override;
+  void SetOutputBufferPartialSettings(
+      fuchsia::media::StreamBufferPartialSettings output_settings) override;
+  void CompleteOutputBufferPartialSettings(
+      uint64_t buffer_lifetime_ordinal) override;
   void FlushEndOfStreamAndCloseStream(
       uint64_t stream_lifetime_ordinal) override;
   void CloseCurrentStream(uint64_t stream_lifetime_ordinal,
@@ -145,6 +161,7 @@
 
  private:
   CodecImpl(
+      fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem,
       std::unique_ptr<CodecAdmission> codec_admission,
       async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
       std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
@@ -160,7 +177,7 @@
   // We keep a queue of Stream objects rather than just a single current stream
   // object, so we can track which streams are future-discarded and which are
   // not yet known to be future-discarded.  This difference matters because
-  // clients are not required to process OnOutputConfig() with
+  // clients are not required to process OnOutputConstraints() with
   // stream_lifetime_ordinal of a stream that the client has since told the
   // server to discard, so we don't want StreamControl ordering domain getting
   // stuck waiting on a client to catch up to an output config that the client
@@ -224,6 +241,16 @@
     void SetFailureSeen();
     __WARN_UNUSED_RESULT bool failure_seen();
 
+    // These methods are called on the core codec processing domain.  See also
+    // comments on output_format_pending_.
+    void SetOutputFormatPending();
+    void ClearOutputFormatPending();
+    __WARN_UNUSED_RESULT bool output_format_pending();
+
+    void SetMidStreamOutputConstraintsChangeActive();
+    void ClearMidStreamOutputConstraintsChangeActive();
+    __WARN_UNUSED_RESULT bool is_mid_stream_output_constraints_change_active();
+
    private:
     const uint64_t stream_lifetime_ordinal_ = 0;
     bool future_discarded_ = false;
@@ -238,6 +265,107 @@
     bool input_end_of_stream_ = false;
     bool output_end_of_stream_ = false;
     bool failure_seen_ = false;
+
+    // This defaults to _true_, so that we send OnOutputFormat() before the
+    // first OnOutputFormat() of a stream.  We also set this back to true any
+    // time the core codec indicates onOutputFormat(), and any time the core
+    // codec indicates onCoreCodecMidStreamOutputConstraintsChange() with action
+    // required true.
+    bool output_format_pending_ = true;
+
+    // It's not permitted for the core codec to emit output while a mid-stream
+    // output constraints change is active.
+    bool is_mid_stream_output_constraints_change_active_ = false;
+  };
+
+  // PortSettings
+  //
+  // The PortSettings wraps/homogenizes the port settings regardless of whether
+  // the settings are specified by the client using StreamBufferSettings or
+  // StreamBufferPartialSettings.  In addition, in the case of
+  // StreamBufferPartialSettings, this class tracks the settings that arrive
+  // later from sysmem (whether we've received them yet, and if so, what the
+  // values are).
+  class PortSettings {
+    public:
+      PortSettings(CodecImpl* parent, CodecPort port, fuchsia::media::StreamBufferSettings settings);
+      PortSettings(CodecImpl* parent, CodecPort port,
+          fuchsia::media::StreamBufferPartialSettings partial_settings);
+      ~PortSettings();
+
+      uint64_t buffer_lifetime_ordinal();
+
+      uint64_t buffer_constraints_version_ordinal();
+
+      uint32_t packet_count();
+      uint32_t buffer_count();
+
+      // If is_partial_settings(), the PortSettings are initially partial, with
+      // sysmem used to complete the settings.  Along the way the PortSettings
+      // transiently also have the zx::vmo handles.  In contrast, if
+      // !is_partial_settings(), the settings are complete from the start (aside
+      // from vmo handles which are never owned by PortSettings in this case).
+      bool is_partial_settings();
+
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings();
+
+      const fuchsia::media::StreamBufferSettings& settings();
+
+      fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> TakeToken();
+
+      // The caller should std::move() in the buffer_collection_info.  This call
+      // is only valid if this instance was created from
+      // StreamBufferPartialSettings, and this method hasn't been called before
+      // on this instance.
+      void SetBufferCollectionInfo(
+          fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info);
+
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info();
+
+      // We use SetBufferCollectionInfo(), but then take the VMOs back.  This
+      // just happens to be more convenient than taking the VMOs before doing
+      // SetBufferCollectionInfo().
+      zx::vmo TakeVmo(uint32_t buffer_index);
+
+      uint64_t vmo_usable_start(uint32_t buffer_index);
+      uint64_t vmo_usable_size();
+
+      // Only call from FIDL thread.
+      fidl::InterfaceRequest<fuchsia::sysmem::BufferCollection> NewBufferCollectionRequest(async_dispatcher_t* dispatcher);
+
+      // Only call from FIDL thread.
+      fuchsia::sysmem::BufferCollectionPtr& buffer_collection();
+
+      // Only call from FIDL thread.
+      void UnbindBufferCollection();
+
+      // This condition is necessary (but not sufficient) for
+      // IsOutputConfiguredLocked() to return true.
+      bool is_complete_seen_output();
+      void SetCompleteSeenOutput();
+
+    private:
+      CodecImpl* parent_ = nullptr;
+
+      CodecPort port_ = kInvalidPort;
+
+      // Only one or the other of settings_ or partial_settings_ is set.
+      std::unique_ptr<fuchsia::media::StreamBufferSettings> settings_;
+
+      // Only needed/set for the partial_settings_ case.
+      std::unique_ptr<const fuchsia::media::StreamBufferConstraints>
+          constraints_;
+      std::unique_ptr<fuchsia::media::StreamBufferPartialSettings> partial_settings_;
+
+      fuchsia::sysmem::BufferCollectionPtr buffer_collection_;
+
+      // In the case of partial_settings_, the remainder of the settings arrive
+      // from sysmem in a BufferCollectionInfo_2.  When that arrives from
+      // sysmem, we move the VMOs into CodecBuffer(s), and the remainder of the
+      // settings get stored here.
+      std::unique_ptr<fuchsia::sysmem::BufferCollectionInfo_2> buffer_collection_info_;
+
+      bool is_complete_seen_output_ = false;
   };
 
   // While we list this first in the member variables to hint that this gets
@@ -252,19 +380,36 @@
   // previous Codec channel, when there's a concurrency cap of 1 (for example).
   std::unique_ptr<CodecAdmission> codec_admission_;
 
+  fuchsia::sysmem::AllocatorPtr sysmem_;
+
   async_dispatcher_t* shared_fidl_dispatcher_;
   thrd_t shared_fidl_thread_;
 
   // Parts of CodecImpl are accessed from shared_fidl_thread(),
   // stream_control_thread_, and decoder thread(s) such as interrupt handling
   // thread(s).
+  //
+  // FXL_GUARDED_BY() is not directly usable in this class because this class
+  // takes advantage of for example being able to read outside the lock from
+  // something that can only be modified on the current thread.  Also, which
+  // thread is relevant can vary by port, while FXL_GUARDED_BY() doesn't have
+  // any way to tag indexes of an array differently.
+  //
+  // TODO(dustingreen): Implement some lock-like contexts including reader vs.
+  // writer aspects so we can use FXL_GUARDED_BY() (just not with the lock
+  // directly).
+  //
+  // TODO(dustingreen): Switch to fbl::Mutex and fbl::ConditionVariable, because
+  // they complain instead of blocking if repeated acquisition is attempted, and
+  // because one can check whether the current thread holds the lock (for assert
+  // purposes).
   std::mutex lock_;
 
   //
   // Setup/teardown aspects.
   //
 
-  // Will send an initial Codec.OnOutputConfig() if the codec can't tolerate
+  // Will send an initial Codec.OnOutputConstraints() if the codec can't tolerate
   // null output config during format detection.
   void onInputConstraintsReady();
 
@@ -332,6 +477,10 @@
 
   // Held here temporarily until DeviceFidl is ready to handle errors so we can
   // bind.
+  fidl::InterfaceHandle<fuchsia::sysmem::Allocator> tmp_sysmem_;
+
+  // Held here temporarily until DeviceFidl is ready to handle errors so we can
+  // bind.
   fidl::InterfaceRequest<fuchsia::media::StreamProcessor>
       tmp_interface_request_;
 
@@ -368,7 +517,10 @@
   // StreamControl thread.
   void SetInputBufferSettings_StreamControl(
       fuchsia::media::StreamBufferSettings input_settings);
-  void AddInputBuffer_StreamControl(fuchsia::media::StreamBuffer buffer);
+  void AddInputBuffer_StreamControl(
+      bool is_client, fuchsia::media::StreamBuffer buffer);
+  void SetInputBufferPartialSettings_StreamControl(
+    fuchsia::media::StreamBufferPartialSettings input_partial_settings);
   void FlushEndOfStreamAndCloseStream_StreamControl(
       uint64_t stream_lifetime_ordinal);
   void CloseCurrentStream_StreamControl(uint64_t stream_lifetime_ordinal,
@@ -380,11 +532,27 @@
       fuchsia::media::FormatDetails format_details);
   void QueueInputPacket_StreamControl(fuchsia::media::Packet packet);
   void QueueInputEndOfStream_StreamControl(uint64_t stream_lifetime_ordinal);
+  // This method returns false if input buffers aren't configured enough so far,
+  // or if sysmem-based buffers can't be confirmed to be allocated.  On
+  // returning false, IsStoppingLocked() will already be true.
+  bool CheckWaitEnsureInputConfigured(std::unique_lock<std::mutex>& lock);
 
   __WARN_UNUSED_RESULT bool IsStreamActiveLocked();
+
+  void SetInputBufferSettingsCommon(
+      std::unique_lock<std::mutex>& lock,
+      fuchsia::media::StreamBufferSettings* input_settings,
+      fuchsia::media::StreamBufferPartialSettings* input_partial_settings);
+
+  void SetOutputBufferSettingsCommon(
+    std::unique_lock<std::mutex>& lock,
+    fuchsia::media::StreamBufferSettings* output_settings,
+    fuchsia::media::StreamBufferPartialSettings* output_partial_settings);
+
   void SetBufferSettingsCommon(
       std::unique_lock<std::mutex>& lock, CodecPort port,
-      fuchsia::media::StreamBufferSettings settings,
+      fuchsia::media::StreamBufferSettings* settings,
+      fuchsia::media::StreamBufferPartialSettings* partial_settings,
       const fuchsia::media::StreamBufferConstraints& constraints);
   void EnsureBuffersNotConfigured(std::unique_lock<std::mutex>& lock,
                                   CodecPort port);
@@ -395,12 +563,26 @@
       CodecPort port, const fuchsia::media::StreamBufferSettings& settings,
       const fuchsia::media::StreamBufferConstraints& constraints);
 
+  // This is just validating that the _partial_ settings set by the client are
+  // valid with respect to the constraints indicated to the client, without any
+  // involvement of sysmem yet (but soon), so there's not a ton to validate
+  // here.
+  __WARN_UNUSED_RESULT bool ValidatePartialBufferSettingsVsConstraintsLocked(
+      CodecPort port, const fuchsia::media::StreamBufferPartialSettings& partial_settings,
+      const fuchsia::media::StreamBufferConstraints& constraints);
+
+  void AddInputBufferInternal(
+      bool is_client, fuchsia::media::StreamBuffer buffer);
+
+  void AddOutputBufferInternal(
+      bool is_client, fuchsia::media::StreamBuffer buffer);
+
   // Returns true if the port is done configuring (last buffer was added).
   // Returns false if the port is not done configuring or if Fail() was called;
   // currently the caller doesn't need to tell the difference between these two
   // very different cases.
   __WARN_UNUSED_RESULT bool AddBufferCommon(
-      CodecPort port, fuchsia::media::StreamBuffer buffer);
+      bool is_client, CodecPort port, fuchsia::media::StreamBuffer buffer);
 
   // Return value of false means FailLocked() has already been called.
   __WARN_UNUSED_RESULT bool CheckOldBufferLifetimeOrdinalLocked(
@@ -416,6 +598,20 @@
   void EnsureStreamClosed(std::unique_lock<std::mutex>& lock);
   void EnsureCodecStreamClosedLockedInternal();
 
+  // Run all items in the sysmem_completion_queue_.  The item itself is run
+  // outside the lock.  Returns true if any completions ran.
+  bool RunAnySysmemCompletions(std::unique_lock<std::mutex>& lock);
+
+  // Only sysmem completions get posted this way.  These essentially cut in line
+  // before most of the body of all QueueInput...StreamControl methods when
+  // those are blocked waiting for sysmem completion.
+  void PostSysmemCompletion(fit::closure to_run);
+  // Returns false if IsStoppingLocked() is already true - just to save the
+  // caller the hassle of checking itself.
+  bool WaitEnsureSysmemReadyOnInput(std::unique_lock<std::mutex>& lock);
+  void RunAnySysmemCompletionsOrWait(
+      std::unique_lock<std::mutex>& lock);
+
   bool is_on_stream_failed_enabled_ = false;
 
   // This is the stream_lifetime_ordinal of the current stream as viewed from
@@ -433,9 +629,9 @@
   // this queue.  This queue is how the StreamControl ordering domain knows
   // whether a stream is discarded or not.  If a stream isn't discarded then the
   // StreamControl domain can keep waiting for the client to process
-  // OnOutputConfig() for that stream.  If the stream has been discarded, then
+  // OnOutputConstraints() for that stream.  If the stream has been discarded, then
   // StreamControl ordering domain cannot expect the client to ever process
-  // OnOutputConfig() for the stream, and the StreamControl ordering domain can
+  // OnOutputConstraints() for the stream, and the StreamControl ordering domain can
   // instead move on to the next stream.
   //
   // In addition, this can allow the StreamControl ordering domain to skip past
@@ -449,11 +645,15 @@
   std::unique_ptr<const fuchsia::media::StreamBufferConstraints>
       input_constraints_;
 
-  // This is the most recent settings received from the client and accepted,
-  // received via SetInputBufferSettings() or SetOutputBufferSettings().  The
-  // settings are as-received from the client.
-  std::unique_ptr<const fuchsia::media::StreamBufferSettings>
-      port_settings_[kPortCount];
+  // This holds the most recent settings received from the client and accepted,
+  // received via SetInputBufferSettings()/SetInputBufferPartialSettings() or
+  // SetOutputBufferSettings()/SetOutputBufferPartialSettings(). The settings
+  // are retained as-received from the client.  In the case of the client
+  // sending StreamBufferPartialSettings, we discover some of the settings via
+  // sysmem (instead of from the client) and store those in port_settings_, to
+  // homogenize how we handle the settigns between StreamBufferSettings and
+  // StreamBufferPartialSettings.
+  std::unique_ptr<PortSettings> port_settings_[kPortCount];
 
   // The most recent fully-configured input or output buffers had this
   // buffer_constraints_version_ordinal.  Even when !port_settings_[port], this
@@ -461,10 +661,10 @@
   // last_required_buffer_constraints_version_ordinal_[port].
   uint64_t last_provided_buffer_constraints_version_ordinal_[kPortCount] = {};
 
-  // For CodecImpl, the initial StreamOutputConfig can be the first sent
-  // message. If sent that early, the StreamOutputConfig is likely to change
+  // For CodecImpl, the initial StreamOutputConstraints can be the first sent
+  // message. If sent that early, the StreamOutputConstraints is likely to change
   // again before any output data is emitted, but it _may not_.
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig> output_config_;
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints> output_constraints_;
 
   // The core codec indicated that it didn't like an output config that had this
   // buffer_constraints_version_ordinal set.  Normally this would lead to
@@ -527,6 +727,31 @@
   // This is set when stream_.output_end_of_stream is set.
   std::condition_variable output_end_of_stream_seen_;
 
+  // This is a queue of lambdas that are to be run on the StreamControl domain
+  // before any further QueueInput... processing on StreamControl.  Even before
+  // the sysmem completion is on this queue, QueueInput...StreamControl() will
+  // be blocked waiting for sysmem completion to be done, and helping run any
+  // items that show up on this queue.
+  //
+  // This line-cutting queue avoids forcing a round-trip to ensure the client
+  // isn't sending any input until after the codec knows about the allocated
+  // buffers.  This also avoids un-binding the client's channel while we wait
+  // for sysmem allocation to be complete - this is worth avoiding because if we
+  // unbind then we also don't find out about PEER_CLOSED which would be at
+  // least somewhat problematic if the client didn't also cause sysmem
+  // allocation to fail.
+  //
+  // We use wake_stream_control_condition_ to wake any
+  // QueueInput...StreamControl waiter that's blocked and helping run items on
+  // this queue, since we of course also have to give up on the wait if we're
+  // shutting down, which is an aspect in common with other StreamControl waits
+  // so it's convenient to share the condition var.
+  std::queue<fit::closure> sysmem_completion_queue_;
+
+  // Avoid re-posting to StreamControl to run sysmem_completion_queue_ items if
+  // there's already a posted runner lambda that'll notice a newly-added item.
+  bool is_sysmem_runner_pending_ = false;
+
   //
   // Adapter-related
   //
@@ -573,17 +798,31 @@
 
   void StartIgnoringClientOldOutputConfig(std::unique_lock<std::mutex>& lock);
 
-  void GenerateAndSendNewOutputConfig(std::unique_lock<std::mutex>& lock,
+  void GenerateAndSendNewOutputConstraints(std::unique_lock<std::mutex>& lock,
                                       bool buffer_constraints_action_required);
 
-  void MidStreamOutputConfigChange(uint64_t stream_lifetime_ordinal);
+  void MidStreamOutputConstraintsChange(uint64_t stream_lifetime_ordinal);
+
+  bool FixupBufferCollectionConstraints(
+    CodecPort port,
+    const fuchsia::media::StreamBufferPartialSettings& partial_settings,
+    fuchsia::sysmem::BufferCollectionConstraints* buffer_collection_constraints);
+
+  void OnBufferCollectionInfo(
+      CodecPort port, uint64_t buffer_lifetime_ordinal, zx_status_t status,
+      fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info);
+
+  // When this method is called we know we're already on the correct thread per
+  // the port.
+  void OnBufferCollectionInfoInternal(
+      CodecPort port, uint64_t buffer_lifetime_ordinal, zx_status_t allocate_status, fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info);
 
   // These are 1:1 with logical CodecBuffer(s).
   std::vector<std::unique_ptr<CodecBuffer>> all_buffers_[kPortCount];
 
   // For this bool to be true, there must be enough buffers in all_buffers_ and
   // the core codec must also be fully configured with regard to those buffers.
-  bool is_port_configured_[kPortCount] = {};
+  bool is_port_buffers_configured_[kPortCount] = {};
 
   // This vector owns these buffers.
   //
@@ -609,7 +848,13 @@
 
   __WARN_UNUSED_RESULT bool IsInputConfiguredLocked();
   __WARN_UNUSED_RESULT bool IsOutputConfiguredLocked();
-  __WARN_UNUSED_RESULT bool IsPortConfiguredCommonLocked(CodecPort port);
+  __WARN_UNUSED_RESULT bool IsPortBuffersConfiguredCommonLocked(CodecPort port);
+
+  // Either completely configured one way or another, or at least partially
+  // configured using sysmem-style port settings.  Else the client isn't
+  // behaving properly.
+  __WARN_UNUSED_RESULT bool IsPortBuffersAtLeastPartiallyConfiguredLocked(
+      CodecPort port);
 
   // Complain sync, then Unbind() async.  Even if more than one caller
   // complains, the async Unbind() work will only run once (but in such cases it
@@ -651,6 +896,9 @@
   __WARN_UNUSED_RESULT bool IsStoppingLocked();
   __WARN_UNUSED_RESULT bool IsStopping();
 
+  __WARN_UNUSED_RESULT bool IsDecoder();
+  __WARN_UNUSED_RESULT bool IsEncoder();
+
   //
   // Core codec interfacing.
   //
@@ -686,13 +934,15 @@
   // call is properly ordered with respect to onCoreCodecOutputPacket() and
   // onCoreCodecOutputEndOfStream() calls.
   //
-  // A call to onCoreCodecMidStreamOutputConfigChange(true) must not be
+  // A call to onCoreCodecMidStreamOutputConstraintsChange(true) must not be
   // followed by any more output (including EndOfStream) until the associated
   // output re-config is completed by a call to
   // CoreCodecMidStreamOutputBufferReConfigFinish().
-  void onCoreCodecMidStreamOutputConfigChange(
+  void onCoreCodecMidStreamOutputConstraintsChange(
       bool output_re_config_required) override;
 
+  void onCoreCodecOutputFormatChange() override;
+
   void onCoreCodecInputPacketDone(CodecPacket* packet) override;
 
   void onCoreCodecOutputPacket(CodecPacket* packet, bool error_detected_before,
@@ -710,9 +960,28 @@
   __WARN_UNUSED_RESULT bool IsCoreCodecRequiringOutputConfigForFormatDetection()
       override;
 
+  __WARN_UNUSED_RESULT bool IsCoreCodecMappedBufferNeeded(CodecPort port)
+      override;
+
+  __WARN_UNUSED_RESULT bool IsCoreCodecHwBased() override;
+
   void CoreCodecInit(const fuchsia::media::FormatDetails&
                          initial_input_format_details) override;
 
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings) override;
+
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info) override;
+
+  fuchsia::media::StreamOutputFormat CoreCodecGetOutputFormat(
+      uint64_t stream_lifetime_ordinal,
+      uint64_t new_output_format_details_version_ordinal) override;
+
   void CoreCodecStartStream() override;
 
   void CoreCodecQueueInputFormatDetails(
@@ -736,11 +1005,10 @@
   void CoreCodecEnsureBuffersNotConfigured(CodecPort port) override;
 
   __WARN_UNUSED_RESULT
-  std::unique_ptr<const fuchsia::media::StreamOutputConfig>
-  CoreCodecBuildNewOutputConfig(
+  std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
+  CoreCodecBuildNewOutputConstraints(
       uint64_t stream_lifetime_ordinal,
       uint64_t new_output_buffer_constraints_version_ordinal,
-      uint64_t new_output_format_details_version_ordinal,
       bool buffer_constraints_action_required) override;
 
   void CoreCodecMidStreamOutputBufferReConfigPrepare() override;
diff --git a/garnet/lib/media/test/codec_buffer.cc b/garnet/lib/media/test/codec_buffer.cc
index 9b83a14..f5fa91e 100644
--- a/garnet/lib/media/test/codec_buffer.cc
+++ b/garnet/lib/media/test/codec_buffer.cc
@@ -24,7 +24,7 @@
   FXL_CHECK(status == ZX_OK);
 }
 
-bool CodecBuffer::Init() {
+bool CodecBuffer::AllocateInternal() {
   zx::vmo local_vmo;
   zx_status_t res;
 
@@ -104,7 +104,37 @@
     result->SetPhysicallyContiguousRequired(
         (constraints.very_temp_kludge_bti_handle()));
   }
-  if (!result->Init()) {
+  if (!result->AllocateInternal()) {
+    return nullptr;
+  }
+  return result;
+}
+
+bool CodecBuffer::CreateFromVmoInternal(zx::vmo vmo, uint32_t vmo_usable_start, uint32_t vmo_usable_size, bool need_write) {
+  zx_vm_option_t options = ZX_VM_PERM_READ;
+  if (need_write) {
+    options |= ZX_VM_PERM_WRITE;
+  }
+  uintptr_t tmp;
+  zx_status_t status = zx::vmar::root_self()->map(0, vmo, vmo_usable_start, vmo_usable_size, options, &tmp);
+  if (status != ZX_OK) {
+    FXL_LOG(WARNING) << "CodecBuffer::CreateFromVmoInternal failed to map VMO - status: " << status;
+    return false;
+  }
+  base_ = reinterpret_cast<uint8_t*>(tmp);
+  vmo_ = std::move(vmo);
+  return true;
+}
+
+std::unique_ptr<CodecBuffer> CodecBuffer::CreateFromVmo(
+    uint32_t buffer_index,
+    zx::vmo vmo,
+    uint32_t vmo_usable_start,
+    uint32_t vmo_usable_size,
+    bool need_write) {
+  std::unique_ptr<CodecBuffer> result(new CodecBuffer(
+    buffer_index, vmo_usable_size));
+  if (!result->CreateFromVmoInternal(std::move(vmo), vmo_usable_start, vmo_usable_size, need_write)) {
     return nullptr;
   }
   return result;
diff --git a/garnet/lib/media/test/codec_client.cc b/garnet/lib/media/test/codec_client.cc
index 3a5b9d2b..4a77af9 100644
--- a/garnet/lib/media/test/codec_client.cc
+++ b/garnet/lib/media/test/codec_client.cc
@@ -28,7 +28,7 @@
 
 }  // namespace
 
-CodecClient::CodecClient(async::Loop* loop)
+CodecClient::CodecClient(async::Loop* loop, fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem)
     : loop_(loop), dispatcher_(loop_->dispatcher()) {
   // We haven't created a channel yet, but that's fine, and we want the error
   // handler set up before any error can possibly be generated by the channel so
@@ -56,12 +56,21 @@
       fit::bind_member(this, &CodecClient::OnInputConstraints);
   codec_.events().OnFreeInputPacket =
       fit::bind_member(this, &CodecClient::OnFreeInputPacket);
-  codec_.events().OnOutputConfig =
-      fit::bind_member(this, &CodecClient::OnOutputConfig);
+  codec_.events().OnOutputConstraints =
+      fit::bind_member(this, &CodecClient::OnOutputConstraints);
+  codec_.events().OnOutputFormat =
+      fit::bind_member(this, &CodecClient::OnOutputFormat);
   codec_.events().OnOutputPacket =
       fit::bind_member(this, &CodecClient::OnOutputPacket);
   codec_.events().OnOutputEndOfStream =
       fit::bind_member(this, &CodecClient::OnOutputEndOfStream);
+
+  // Bind sysmem_ using FIDL thread.  This is ok because all communication with
+  // sysmem also happens via FIDL thread so will queue after this posted lambda.
+  PostToFidlThread([this, sysmem = std::move(sysmem)]() mutable {
+    zx_status_t bind_status = sysmem_.Bind(std::move(sysmem), dispatcher_);
+    ZX_ASSERT(bind_status == ZX_OK);
+  });
 }
 
 CodecClient::~CodecClient() { Stop(); }
@@ -115,77 +124,153 @@
 
   FXL_CHECK(input_constraints_->has_packet_count_for_server_recommended());
   FXL_CHECK(input_constraints_->has_packet_count_for_server_max());
-  uint32_t packet_count_for_client = kMinExtraInputPacketsForClient;
+  uint32_t packet_count_for_client = std::max(
+    kMinExtraInputPacketsForClient,
+    input_constraints_->packet_count_for_client_min());
   uint32_t packet_count_for_server =
       input_constraints_->packet_count_for_server_recommended();
-  uint32_t input_packet_count =
-      packet_count_for_client + packet_count_for_server;
-  if (input_packet_count < packet_count_for_server ||
-      input_packet_count > input_constraints_->packet_count_for_server_max()) {
-    FXL_LOG(FATAL) << "server can't easily accomodate "
+  if (packet_count_for_client > input_constraints_->packet_count_for_client_max()) {
+    FXL_LOG(FATAL) << "server can't accomodate "
                       "kMinExtraInputPacketsForClient - not "
                       "using server - exiting";
   }
-  {  // scope input_settings, just for clarity
-    fuchsia::media::StreamBufferSettings input_settings;
-    input_settings.set_buffer_lifetime_ordinal(kInputBufferLifetimeOrdinal);
-    input_settings.set_buffer_constraints_version_ordinal(
-        input_constraints_->buffer_constraints_version_ordinal());
-    input_settings.set_packet_count_for_server(packet_count_for_server);
-    input_settings.set_packet_count_for_client(packet_count_for_client);
-    input_settings.set_per_packet_buffer_bytes(
-        input_constraints_->per_packet_buffer_bytes_recommended());
-    input_settings.set_single_buffer_mode(false);
-    async::PostTask(
-        dispatcher_,
-        [this, input_settings = std::move(input_settings)]() mutable {
-          codec_->SetInputBufferSettings(std::move(input_settings));
-        });
+
+  uint32_t input_packet_count;
+  fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info;
+  if (!ConfigurePortBufferCollection(
+      false, kInputBufferLifetimeOrdinal,
+      input_constraints_->buffer_constraints_version_ordinal(),
+      packet_count_for_server, packet_count_for_client, &input_packet_count,
+      &input_buffer_collection_, &buffer_collection_info)) {
+    FXL_LOG(FATAL) << "ConfigurePortBufferCollection failed (input)";
   }
+
   ZX_ASSERT(input_free_bits_.empty());
   input_free_bits_.resize(input_packet_count, true);
   all_input_buffers_.reserve(input_packet_count);
   for (uint32_t i = 0; i < input_packet_count; i++) {
     std::unique_ptr<CodecBuffer> local_buffer =
-        CodecBuffer::Allocate(i, *input_constraints_);
+      CodecBuffer::CreateFromVmo(i, std::move(buffer_collection_info.buffers[i].vmo), buffer_collection_info.buffers[i].vmo_usable_start, buffer_collection_info.settings.buffer_settings.size_bytes, true);
     if (!local_buffer) {
-      FXL_LOG(FATAL) << "CodecBuffer::Allocate() failed";
-    }
-    zx::vmo dup_vmo;
-    if (!local_buffer->GetDupVmo(false, &dup_vmo)) {
-      FXL_LOG(FATAL) << "GetDupVmo() failed";
+      FXL_LOG(FATAL) << "CodecBuffer::CreateFromVmo() failed";
     }
     ZX_ASSERT(all_input_buffers_.size() == i);
     all_input_buffers_.push_back(std::move(local_buffer));
-
-    // May as well tell the Codec server about these incrementally.
-    {
-      fuchsia::media::StreamBuffer codec_buffer;
-      codec_buffer.set_buffer_lifetime_ordinal(kInputBufferLifetimeOrdinal);
-      codec_buffer.set_buffer_index(i);
-      codec_buffer.mutable_data()->vmo().set_vmo_handle(std::move(dup_vmo));
-      codec_buffer.mutable_data()->vmo().set_vmo_usable_start(0);
-      codec_buffer.mutable_data()->vmo().set_vmo_usable_size(
-          input_constraints_->per_packet_buffer_bytes_recommended());
-      async::PostTask(dispatcher_,
-                      [this, codec_buffer = std::move(codec_buffer)]() mutable {
-                        codec_->AddInputBuffer(std::move(codec_buffer));
-                      });
-    }
   }
-  // Now that the codec has all the input buffers, we effectively have just
-  // allocated all the input packets.  They all start as free with the Codec
-  // client, per protocol.
+
+  // Now that we've SetInputBufferPartialSettings(), the codec will get the
+  // input buffers from sysmem.  The input packets all start as free with the
+  // Codec client, per protocol.  Same goes for input buffers - this client
+  // happens to track in terms of packets and have buffer_index == packet_index.
+  //
+  // TODO(dustingreen): Have CodecClient scramble the order of packets vs.
+  // buffers to check that CodecImpl is handling that correctly for input
+  // packets.
   input_free_list_.reserve(input_packet_count);
   for (uint32_t i = 0; i < input_packet_count; i++) {
     input_free_list_.push_back(i);
   }
 }
 
+bool CodecClient::CreateAndSyncBufferCollection(
+    fuchsia::sysmem::BufferCollectionSyncPtr* out_buffer_collection,
+    fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>*
+        out_codec_sysmem_token) {
+  fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
+  fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> codec_sysmem_token;
+
+  // Create client_token which will get converted into out_buffer_collection.
+  fuchsia::sysmem::BufferCollectionTokenSyncPtr client_token;
+  fidl::InterfaceRequest<fuchsia::sysmem::BufferCollectionToken>
+      client_token_request = client_token.NewRequest();
+
+  // Create codec_sysmem_token that'll get returned via out_codec_sysmem_token.
+  client_token->Duplicate(
+      std::numeric_limits<uint32_t>::max(), codec_sysmem_token.NewRequest());
+
+  // client_token gets converted into a buffer_collection.
+  //
+  // Start client_token connection and start converting it into a BufferCollection,
+  // so we can Sync() the previous Duplicate().
+  PostToFidlThread(
+      [this, client_token_request = std::move(client_token_request),
+       client_token = client_token.Unbind(),
+       buffer_collection_request = buffer_collection.NewRequest()]() mutable {
+    if (!sysmem_) {
+      return;
+    }
+    sysmem_->AllocateSharedCollection(std::move(client_token_request));
+    // codec_sysmem_token will be known to sysmem by the time client_token
+    // closure is seen by sysmem, which in turn is before
+    // buffer_collection_request will be hooked up, which is why
+    // buffer_collection->Sync() completion below is enough to prove that sysmem
+    // knows about codec_sysmem_token before codec_sysmem_token is sent to the
+    // codec.
+    sysmem_->BindSharedCollection(std::move(client_token), std::move(buffer_collection_request));
+  });
+
+  // After Sync() completes its round trip, we know that sysmem knows about
+  // codec_sysmem_token (causally), which is important because we'll shortly
+  // send codec_sysmem_token to the codec which will use codec_sysmem_token via
+  // a different sysmem channel.
+  zx_status_t sync_status = buffer_collection->Sync();
+  if (sync_status != ZX_OK) {
+    FXL_LOG(FATAL) << "buffer_collection->Sync() failed - status: " << sync_status;
+  }
+
+  *out_buffer_collection = std::move(buffer_collection);
+  *out_codec_sysmem_token = std::move(codec_sysmem_token);
+  return true;
+}
+
+bool CodecClient::WaitForSysmemBuffersAllocated(
+    fuchsia::sysmem::BufferCollectionSyncPtr* buffer_collection_param,
+    fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info) {
+  // The style guide doesn't like non-const &, but the code in this method is
+  // easier to read with a non-const &, so treat it that way within this method.
+  fuchsia::sysmem::BufferCollectionSyncPtr& buffer_collection =
+      *buffer_collection_param;
+  fuchsia::sysmem::BufferCollectionInfo_2 result_buffer_collection_info;
+
+  // It's not permitted to send input data until the client knows that sysmem
+  // is done allocating.  It's not required that the client know that the
+  // codec knows that sysmem is done allocating though - the server will
+  // verify that sysmem is done by communicating with sysmem directly as
+  // needed.
+  zx_status_t allocate_status;
+  zx_status_t call_status = buffer_collection->WaitForBuffersAllocated(
+      &allocate_status, &result_buffer_collection_info);
+  if (call_status != ZX_OK) {
+    FXL_LOG(ERROR) << "WaitForBuffersAllocated returned failure - status: " << call_status;
+    return false;
+  }
+  if (allocate_status != ZX_OK) {
+    FXL_LOG(ERROR) << "WaitForBuffersAllocated allocation failed - status: " << allocate_status;
+    return false;
+  }
+
+  *out_buffer_collection_info = std::move(result_buffer_collection_info);
+  return true;
+}
+
 void CodecClient::Stop() {
   if (codec_.is_bound()) {
     codec_.Unbind();
   }
+  if (sysmem_.is_bound()) {
+    sysmem_.Unbind();
+  }
+  if (input_buffer_collection_.is_bound()) {
+    input_buffer_collection_.Unbind();
+  }
+  if (output_buffer_collection_.is_bound()) {
+    output_buffer_collection_.Unbind();
+  }
+}
+
+void CodecClient::PostToFidlThread(fit::closure to_run) {
+  zx_status_t post_status = async::PostTask(dispatcher_, std::move(to_run));
+  ZX_ASSERT(post_status == ZX_OK);
 }
 
 void CodecClient::CallSyncAndWaitForResponse() {
@@ -224,6 +309,22 @@
   ZX_ASSERT(is_sync_complete);
 }
 
+void CodecClient::TrackOutputStreamLifetimeOrdinal(
+    uint64_t output_stream_lifetime_ordinal) {
+  // must be odd
+  ZX_ASSERT(output_stream_lifetime_ordinal % 2 == 1);
+  ZX_ASSERT(output_stream_lifetime_ordinal >= output_stream_lifetime_ordinal_);
+  if (output_stream_lifetime_ordinal > output_stream_lifetime_ordinal_) {
+    // We're allowed to forget format any time there's a stream change, so we
+    // do.  This isn't critical for this test code, but it's closer to how a
+    // real client will likely track the output format on a per-stream basis.
+    ZX_ASSERT(!last_output_format_ || last_output_format_->stream_lifetime_ordinal() == output_stream_lifetime_ordinal_);
+    output_stream_lifetime_ordinal_ = output_stream_lifetime_ordinal;
+    last_output_format_ = nullptr;
+    // We intentionally don't reset is_packet_since_last_format_.
+  }
+}
+
 void CodecClient::OnInputConstraints(
     fuchsia::media::StreamBufferConstraints input_constraints) {
   if (input_constraints_) {
@@ -312,7 +413,15 @@
 void CodecClient::QueueInputPacket(
     std::unique_ptr<fuchsia::media::Packet> packet) {
   ZX_ASSERT(packet->has_header());
+  ZX_ASSERT(packet->header().has_buffer_lifetime_ordinal());
   ZX_ASSERT(packet->header().has_packet_index());
+  ZX_ASSERT(packet->has_buffer_index());
+  ZX_ASSERT(packet->has_stream_lifetime_ordinal());
+  ZX_ASSERT(packet->has_start_offset());
+  ZX_ASSERT(packet->has_valid_length_bytes());
+  // timestamp_ish field is optional
+  // start_access_unit field is optional
+  // known_end_access_unit is optional
   fuchsia::media::Packet local_packet = fidl::Clone(*packet);
   {  // scope lock
     // This packet is already not on the free list, but is still considered free
@@ -343,14 +452,14 @@
 
 std::unique_ptr<CodecOutput> CodecClient::BlockingGetEmittedOutput() {
   while (true) {
-    // The rule is that a required pending config won't be followed by any more
-    // output packets until it's no longer pending (in the sense that the output
-    // buffers have been suitably re-configured).  We verify the server is
-    // following that rule elsewhere, which means we know here that when both
-    // packets are pending and config is pending, the packets were delivered to
-    // the client first.  So we drain the packets first.
+    // The rule is that a required pending constraints won't be followed by any
+    // more output packets until it's no longer pending (in the sense that the
+    // output buffers have been suitably re-configured).  We verify the server
+    // is following that rule elsewhere, which means we know here that when both
+    // packets are pending and constraints is pending, the packets were
+    // delivered to the client first. So we drain the packets first.
     std::unique_ptr<CodecOutput> packet;
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> config;
+    std::shared_ptr<const fuchsia::media::StreamOutputConstraints> constraints;
     {  // scope lock
       std::unique_lock<std::mutex> lock(lock_);
       while (!output_pending_) {
@@ -365,11 +474,11 @@
           output_pending_ = false;
         }
       } else {
-        ZX_ASSERT(output_config_action_pending_);
-        ZX_ASSERT(last_required_output_config_);
-        config = last_required_output_config_;
+        ZX_ASSERT(output_constraints_action_pending_);
+        ZX_ASSERT(last_required_output_constraints_);
+        constraints = last_required_output_constraints_;
       }
-    }
+    }  // ~lock
 
     // Now we own a packet or have a required config to deal with, but not both,
     // so it doesn't matter which order we check here, but for clarity we check
@@ -392,7 +501,7 @@
     // The main mechanism used to detect that the server isn't sending output
     // too soon is output_config_action_pending_.  In contrast, the client code
     // in this example permits itself to send RecycleOutputPacket() after the
-    // client has already seen OnOutputConfig() with action required true, even
+    // client has already seen OnOutputConstraints() with action required true, even
     // though the client could stop itself from doing so as a potential
     // optimization.  The client is allowed to send RecycleOutputPacket() up
     // until the implied ReleaseOutputBuffers() at the start of
@@ -403,24 +512,27 @@
     // Because of the client allowing itself to send RecycleOutputPacket() for a
     // while longer than fundamentally necessary, we delay upkeep on
     // output_free_bits_ until here.  This upkeep isn't really fundamentally
-    // necessary between OnOutputConfig() with action required true and the last
+    // necessary between OnOutputConstraints() with action required true and the last
     // AddOutputBuffer() as part of output re-configuration, but ... this
     // explicit delayed upkeep _may_ help illustrate how it's acceptable for a
     // client to let the completion end of output processing send
     // RecycleOutputPacket() as long as all those will be sent before
     // SetOutputSettings().
+    std::shared_ptr<const fuchsia::media::StreamOutputConstraints>
+        snapped_constraints;
+    uint64_t new_output_buffer_lifetime_ordinal;
     {  // scope lock
       std::unique_lock<std::mutex> lock(lock_);
 
-      // We know this because the previous OnOutputConfig() set this and because
-      // we're only here if it's set.
-      ZX_ASSERT(output_config_action_pending_);
+      // We know this because the previous OnOutputConstraints() set this and
+      // because we're only here if it's set.
+      ZX_ASSERT(output_constraints_action_pending_);
       // We know this because we reject additional output from the server when
       // output_config_action_pending_ is true, and because we've drained all
       // previous output by this point.
       ZX_ASSERT(emitted_output_.empty());
-      // We know this because we're only here if we have a pending config.
-      ZX_ASSERT(config);
+      // We know this because we're only here if we have pending constraints.
+      ZX_ASSERT(constraints);
 
       // Not really critical to do this, as we'll just end up setting these
       // back to true under the same lock hold interval as we set
@@ -431,143 +543,120 @@
       // Think of this assignment as slightly more than a comment in this
       // example, rather than any real need.
       output_free_bits_.resize(0);
-    }  // ~lock
 
-    // Free the old output buffers, if any.
-    while (!all_output_buffers_.empty()) {
-      std::unique_ptr<CodecBuffer> buffer =
-          std::move(all_output_buffers_.back());
-      all_output_buffers_.pop_back();
-    }
+      // Free the old output buffers, if any.
+      all_output_buffers_.clear();
 
-    // Here is where we snap which exact config version we'll actually use.
-    //
-    // For a client that's doing output buffer re-config on the FIDL thread
-    // during OnOutputConfig with action required true, this will always just be
-    // the config being presently received.  But this example shows how to drive
-    // the codec in a protocol-valid way without being forced to perform buffer
-    // re-configuration on the FIDL thread.
-
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> snapped_config;
-    uint64_t new_output_buffer_lifetime_ordinal;
-    {  // scope lock
-      std::unique_lock<std::mutex> lock(lock_);
-      ZX_ASSERT(output_config_action_pending_);
-      ZX_ASSERT(last_required_output_config_);
-      ZX_ASSERT(last_output_config_);
-      // We'll snap the last_output_config_, which is always at least as recent
-      // as the last_required_output_config_.
-      snapped_config = last_output_config_;
-      ZX_ASSERT(snapped_config);
+      // Here is where we snap which exact constraints version we'll actually use.
+      //
+      // For a client that's doing output buffer re-config on the FIDL thread
+      // during OnOutputConstraints with action required true, this will always
+      // just be the constraints being presently received.  But this example shows
+      // how to drive the codec in a protocol-valid way without being forced to
+      // perform buffer re-configuration on the FIDL thread.
+      ZX_ASSERT(output_constraints_action_pending_);
+      ZX_ASSERT(last_required_output_constraints_);
+      ZX_ASSERT(last_output_constraints_);
+      // We'll snap the last_output_constraints_, which is always at least as
+      // recent as the last_required_output_constraints_.
+      snapped_constraints = last_output_constraints_;
+      ZX_ASSERT(snapped_constraints);
       new_output_buffer_lifetime_ordinal = next_output_buffer_lifetime_ordinal_;
       next_output_buffer_lifetime_ordinal_ += 2;
     }  // ~lock
 
-    // Tell the server about output settings.
-
-    ZX_ASSERT(snapped_config->has_buffer_constraints());
-    const fuchsia::media::StreamBufferConstraints& constraints =
-        snapped_config->buffer_constraints();
-    ZX_ASSERT(constraints.has_packet_count_for_server_recommended());
-    uint32_t packet_count_for_server =
-        constraints.packet_count_for_server_recommended();
-    uint32_t packet_count_for_client = kMinExtraOutputPacketsForClient;
-    uint32_t packet_count = packet_count_for_server + packet_count_for_client;
-    // printf("Sending SetOutputBufferSettings - buffer_lifetime_ordinal: %lu
-    // buffer_constraints_version_ordinal: %lu\n",
-    // new_output_buffer_lifetime_ordinal,
-    // constraints.buffer_constraints_version_ordinal);
-    fuchsia::media::StreamBufferSettings settings;
-    settings.set_buffer_lifetime_ordinal(new_output_buffer_lifetime_ordinal);
-    settings.set_buffer_constraints_version_ordinal(
-        constraints.buffer_constraints_version_ordinal());
-    settings.set_packet_count_for_server(packet_count_for_server);
-    settings.set_packet_count_for_client(packet_count_for_client);
-    settings.set_per_packet_buffer_bytes(
-        constraints.per_packet_buffer_bytes_recommended());
-    settings.set_single_buffer_mode(false);
-    async::PostTask(dispatcher_,
-                    [this, settings = std::move(settings)]() mutable {
-                      codec_->SetOutputBufferSettings(std::move(settings));
-                    });
-
-    // Allocate new output buffers and tell the server about them.  Telling the
-    // server about the last buffer is significant in the protocol.  See details
-    // below.
     //
-    // This example doesn't try to skip creating and configuring the rest of the
-    // buffers if a new action-required config has arrived, but doing so would
-    // be legal behavior per the protocol.
+    // Tell the server about output settings.
+    //
 
-    all_output_buffers_.reserve(packet_count);
-    for (uint32_t i = 0; i < packet_count; i++) {
-      std::unique_ptr<CodecBuffer> buffer =
-          CodecBuffer::Allocate(i, constraints);
-      if (!buffer) {
-        FXL_LOG(FATAL) << "CodecBuffer::Allocate() failed (output)";
-      }
-      zx::vmo dup_vmo;
-      if (!buffer->GetDupVmo(true, &dup_vmo)) {
-        FXL_LOG(FATAL) << "GetDupVmo() failed (output)";
-      }
-      ZX_ASSERT(all_output_buffers_.size() == i);
-      all_output_buffers_.push_back(std::move(buffer));
-
-      // The last buffer being added is significant to the protocol.
-      if (i == packet_count - 1) {
-        std::unique_lock<std::mutex> lock(lock_);
-        // The last message will potentially result in OnOutputPacket(), so we
-        // need to be ready for that packet.
-        //
-        // This is non-harmful if output_config_action_pending_ will remain
-        // true.
-        output_free_bits_.resize(packet_count, true);
-      }
-
-      {  // scope codec_buffer for clarity
-        fuchsia::media::StreamBuffer codec_buffer;
-        codec_buffer.set_buffer_lifetime_ordinal(
-            new_output_buffer_lifetime_ordinal);
-        codec_buffer.set_buffer_index(i);
-        codec_buffer.mutable_data()->vmo().set_vmo_handle(std::move(dup_vmo));
-        codec_buffer.mutable_data()->vmo().set_vmo_usable_start(0);
-        codec_buffer.mutable_data()->vmo().set_vmo_usable_size(
-            constraints.per_packet_buffer_bytes_recommended());
-        // printf("Sending AddOutputBuffer - buffer_lifetime_ordinal: %lu\n",
-        // new_output_buffer_lifetime_ordinal);
-        async::PostTask(
-            dispatcher_,
-            [this, codec_buffer = std::move(codec_buffer)]() mutable {
-              codec_->AddOutputBuffer(std::move(codec_buffer));
-            });
-      }
+    ZX_ASSERT(snapped_constraints->has_buffer_constraints());
+    const fuchsia::media::StreamBufferConstraints& buffer_constraints =
+        snapped_constraints->buffer_constraints();
+    ZX_ASSERT(buffer_constraints.has_packet_count_for_server_recommended());
+    uint32_t packet_count_for_server =
+        buffer_constraints.packet_count_for_server_recommended();
+    uint32_t packet_count_for_client = std::max(
+        kMinExtraOutputPacketsForClient,
+        buffer_constraints.packet_count_for_client_min());
+    if (packet_count_for_client > buffer_constraints.packet_count_for_client_max()) {
+      FXL_LOG(FATAL) << "server can't accomodate "
+                        "kMinExtraOutputPacketsForClient - not "
+                        "using server - exiting";
     }
 
-    // So, now that we're done with that output re-config, it's time to see if
-    // that re-config was the last one we need to do, or if there's a newer
-    // config that's action-required.
+    uint32_t packet_count;
+    fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info;
+    if (!ConfigurePortBufferCollection(
+        true, new_output_buffer_lifetime_ordinal,
+        buffer_constraints.buffer_constraints_version_ordinal(),
+        packet_count_for_server, packet_count_for_client,
+        &packet_count, &output_buffer_collection_, &buffer_collection_info)) {
+      FXL_LOG(FATAL) << "ConfigurePortBufferCollection failed (output)";
+    }
+
+    // Configure tracking for output buffers.
+    {  // scope lock
+      std::lock_guard<std::mutex> lock(lock_);
+
+      all_output_buffers_.reserve(packet_count);
+      for (uint32_t i = 0; i < packet_count; i++) {
+        std::unique_ptr<CodecBuffer> buffer = CodecBuffer::CreateFromVmo(
+            i, std::move(buffer_collection_info.buffers[i].vmo),
+            buffer_collection_info.buffers[i].vmo_usable_start,
+            buffer_collection_info.settings.buffer_settings.size_bytes, false);
+        if (!buffer) {
+          FXL_LOG(FATAL) << "CodecBuffer::Allocate() failed (output)";
+        }
+        ZX_ASSERT(all_output_buffers_.size() == i);
+        all_output_buffers_.push_back(std::move(buffer));
+
+        if (i == packet_count - 1) {
+          output_free_bits_.resize(packet_count, true);
+        }
+      }
+
+      current_output_buffer_lifetime_ordinal_ =
+          new_output_buffer_lifetime_ordinal;
+    }  // ~lock
+
+    // We're ready to receive output.
+    PostToFidlThread([this, output_buffer_lifetime_ordinal = new_output_buffer_lifetime_ordinal]{
+      if (!codec_) {
+        return;
+      }
+      if (output_buffer_lifetime_ordinal != current_output_buffer_lifetime_ordinal_) {
+        return;
+      }
+      codec_->CompleteOutputBufferPartialSettings(
+          output_buffer_lifetime_ordinal);
+    });
 
     {  // scope lock
-      std::unique_lock<std::mutex> lock(lock_);
-      if (snapped_config->buffer_constraints()
+      std::lock_guard<std::mutex> lock(lock_);
+
+      // So, now that we're done with that output re-config, it's time to see if
+      // that re-config was the last one we need to do, or if there's a newer
+      // config that's action-required.
+
+      if (snapped_constraints->buffer_constraints()
                .buffer_constraints_version_ordinal() >=
-          last_required_output_config_->buffer_constraints()
+          last_required_output_constraints_->buffer_constraints()
                .buffer_constraints_version_ordinal()) {
         // Good.  The client is caught up.  The output_config_action_pending_
         // can become false here, but may very shortly become true again if
-        // another OnOutputConfig() is received after we release the lock
+        // another OnOutputConstraints() is received after we release the lock
         // (roughly speaking; see code).
         //
         // It's ok that we didn't set output_config_action_pending_ to false
         // before sending the last AddOutputBuffer() above, because
-        // OnOutputConfig() was still able to update
+        // OnOutputConstraints() was still able to update
         // last_required_output_config_ as needed, which it's been able to do
         // all along during most of this whole method.  If we had set to false
         // up there, it would probably be less obvious why it works vs. here,
         // but either can work.
         FXL_VLOG(3) << "output_config_action_pending_ = false, because client "
                        "caught up";
-        output_config_action_pending_ = false;
+        output_constraints_action_pending_ = false;
         // Because this was true for at least pending config reason which we
         // are only just clearing immediately above.
         ZX_ASSERT(output_pending_);
@@ -577,20 +666,127 @@
           output_pending_ = false;
         }
       } else {
-        // We've received and even more recent config that's action-required, so
-        // go around again without clearing output_config_action_pending_ or
-        // output_pending_.  Both remain true until we've caught up to a config
-        // that's at least as new as the last_required_output_config_.
+        // We've received and even more recent constraints that's
+        // action-required, so go around again without clearing
+        // output_constraints_action_pending_ or output_pending_. Both remain
+        // true until we've caught up to a config that's at least as new as the
+        // last_required_output_constraints_.
         FXL_VLOG(3)
-            << "output_config_action_pending_ remains true because server has "
-               "sent yet another action-required output config";
-        ZX_ASSERT(output_config_action_pending_);
+            << "output_constraints_action_pending_ remains true because server "
+               "has sent yet another action-required output constraints";
+        ZX_ASSERT(output_constraints_action_pending_);
         ZX_ASSERT(output_pending_);
       }
     }  // ~lock
   }
 }
 
+bool CodecClient::ConfigurePortBufferCollection(
+    bool is_output, uint64_t new_buffer_lifetime_ordinal,
+    uint64_t buffer_constraints_version_ordinal,
+    uint32_t packet_count_for_server, uint32_t packet_count_for_client,
+    uint32_t* out_packet_count,
+    fuchsia::sysmem::BufferCollectionPtr* out_buffer_collection,
+    fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info) {
+  uint32_t packet_count = packet_count_for_server + packet_count_for_client;
+
+  fuchsia::media::StreamBufferPartialSettings settings;
+  settings.set_buffer_lifetime_ordinal(new_buffer_lifetime_ordinal);
+  settings.set_buffer_constraints_version_ordinal(
+      buffer_constraints_version_ordinal);
+  settings.set_single_buffer_mode(false);
+  settings.set_packet_count_for_server(packet_count_for_server);
+  settings.set_packet_count_for_client(packet_count_for_client);
+
+  fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
+  fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> codec_sysmem_token;
+  if (!CreateAndSyncBufferCollection(&buffer_collection, &codec_sysmem_token)) {
+    FXL_LOG(FATAL) << "CreateAndSyncBufferCollection failed (output)";
+    return false;
+  }
+
+  settings.set_sysmem_token(std::move(codec_sysmem_token));
+
+  fuchsia::sysmem::BufferCollectionConstraints constraints;
+  constraints.usage.cpu = is_output ? fuchsia::sysmem::cpuUsageReadOften : fuchsia::sysmem::cpuUsageWriteOften;
+  // TODO(dustingreen): Make this more flexible once we're more flexible on
+  // frame_count on output of decoder.
+  constraints.min_buffer_count_for_camping = packet_count_for_client;
+  ZX_DEBUG_ASSERT(constraints.min_buffer_count_for_dedicated_slack == 0);
+  ZX_DEBUG_ASSERT(constraints.min_buffer_count_for_shared_slack == 0);
+
+  // 0 is treated as 0xFFFFFFFF.
+  ZX_DEBUG_ASSERT(constraints.max_buffer_count == 0);
+
+  constraints.has_buffer_memory_constraints = true;
+  // Sysmem has a built-in min_size_bytes of 1, so no need to really constrain
+  // min_size_bytes here.
+  ZX_DEBUG_ASSERT(constraints.buffer_memory_constraints.min_size_bytes == 0);
+  constraints.buffer_memory_constraints.max_size_bytes =
+      std::numeric_limits<uint32_t>::max();
+  constraints.buffer_memory_constraints.physically_contiguous_required = false;
+  constraints.buffer_memory_constraints.secure_required = false;
+  // This test client code has no way to produce or consume output data in
+  // protected memory.
+  constraints.buffer_memory_constraints.secure_permitted = false;
+
+  // Despite being a consumer of output uncompressed video frames (when
+  // decoding video and is_output), for now we intentionally don't constrain to
+  // the PixelFormatType(s) that we can consume, and instead fail later if we
+  // get something unexpected on output.  That's just easier than plumbing
+  // PixelFormatType(s) to here for now.
+  ZX_DEBUG_ASSERT(constraints.image_format_constraints_count == 0);
+
+  PostToFidlThread([this, is_output, settings = std::move(settings)]() mutable {
+    if (!codec_) {
+      return;
+    }
+    if (is_output) {
+      codec_->SetOutputBufferPartialSettings(std::move(settings));
+    } else {
+      codec_->SetInputBufferPartialSettings(std::move(settings));
+    }
+  });
+
+  buffer_collection->SetConstraints(true, std::move(constraints));
+
+  fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info;
+  // This borrows buffer_collection during the call.
+  if (!WaitForSysmemBuffersAllocated(&buffer_collection,
+                                     &buffer_collection_info)) {
+    FXL_LOG(FATAL) << "WaitForSysmemBuffersAllocated failed";
+    return false;
+  }
+
+  packet_count = std::max(
+    packet_count, buffer_collection_info.buffer_count);
+
+  fuchsia::sysmem::BufferCollectionPtr buffer_collection_ptr;
+
+  // For the Bind() we probably don't strictly need to be on FIDL thread, so
+  // do the Bind() here.  This does mean we need to set the error handler
+  // before the Bind() however.
+  buffer_collection_ptr.set_error_handler([is_output](zx_status_t status){
+    FXL_LOG(FATAL) << "BufferCollection failed - status: " << status << " is_output: " << is_output;
+  });
+
+  // This implicitly converts buffer_collection from
+  // fidl::SynchronousInterfacePtr<> to fidl::InterfaceHandle<>, which is
+  // what we want; we're moving handling of the BufferCollection from this
+  // thread to the FIDL thread.
+  zx_status_t bind_status = buffer_collection_ptr.Bind(
+      std::move(buffer_collection), dispatcher_);
+  if (bind_status != ZX_OK) {
+    FXL_LOG(FATAL) << "buffer_collection_ptr.Bind() failed - status: " << bind_status << " is_output: " << is_output;
+    return false;
+  }
+
+  *out_packet_count = packet_count;
+  *out_buffer_collection = std::move(buffer_collection_ptr);
+  *out_buffer_collection_info = std::move(buffer_collection_info);
+  return true;
+}
+
 void CodecClient::RecycleOutputPacket(
     fuchsia::media::PacketHeader free_packet) {
   ZX_ASSERT(free_packet.has_packet_index());
@@ -604,52 +800,63 @@
                   });
 }
 
-void CodecClient::OnOutputConfig(
-    fuchsia::media::StreamOutputConfig output_config) {
+void CodecClient::OnOutputConstraints(
+    fuchsia::media::StreamOutputConstraints output_constraints) {
   bool output_pending_notify_needed = false;
   // Not that the std::move() actaully helps here, but that's what we're doing.
-  std::shared_ptr<fuchsia::media::StreamOutputConfig> shared_config =
-      std::make_shared<fuchsia::media::StreamOutputConfig>(
-          std::move(output_config));
+  std::shared_ptr<fuchsia::media::StreamOutputConstraints> shared_constraints =
+      std::make_shared<fuchsia::media::StreamOutputConstraints>(
+          std::move(output_constraints));
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
-    ZX_ASSERT(!last_output_config_ ||
-              (last_output_config_->has_buffer_constraints() &&
-               last_output_config_->buffer_constraints()
+
+    if (!shared_constraints->has_stream_lifetime_ordinal()) {
+      FXL_LOG(FATAL) << "StreamOutputConstraints missing stream_lifetime_ordinal";
+    }
+    uint64_t stream_lifetime_ordinal =
+        shared_constraints->stream_lifetime_ordinal();
+    TrackOutputStreamLifetimeOrdinal(stream_lifetime_ordinal);
+
+    ZX_ASSERT(!last_output_constraints_ ||
+              (last_output_constraints_->has_buffer_constraints() &&
+               last_output_constraints_->buffer_constraints()
                    .has_buffer_constraints_version_ordinal()));
     uint64_t previous_buffer_constraints_version_ordinal =
-        last_output_config_ ? last_output_config_->buffer_constraints()
+        last_output_constraints_ ? last_output_constraints_->buffer_constraints()
                                    .buffer_constraints_version_ordinal()
                             : 0;
-    ZX_ASSERT(shared_config->has_buffer_constraints() &&
-              shared_config->buffer_constraints()
+    ZX_ASSERT(shared_constraints->has_buffer_constraints() &&
+              shared_constraints->buffer_constraints()
                   .has_buffer_constraints_version_ordinal());
-    if (shared_config->buffer_constraints()
+    if (shared_constraints->buffer_constraints()
              .buffer_constraints_version_ordinal() <
         previous_buffer_constraints_version_ordinal) {
       FXL_LOG(FATAL)
           << "broken server sent badly ordered buffer constraints ordinals";
     }
-    if ((shared_config->has_buffer_constraints_action_required() &&
-         shared_config->buffer_constraints_action_required()) &&
-        shared_config->buffer_constraints()
+    if ((shared_constraints->has_buffer_constraints_action_required() &&
+         shared_constraints->buffer_constraints_action_required()) &&
+        shared_constraints->buffer_constraints()
                 .buffer_constraints_version_ordinal() <=
             previous_buffer_constraints_version_ordinal) {
       FXL_LOG(FATAL)
           << "broken server sent buffer_constraints_action_required without "
              "increasingbuffer_constraints_version_ordinal";
     }
-    last_output_config_ = shared_config;
-    FXL_VLOG(3) << "OnOutputConfig buffer_constraints_version_ordinal: "
-                << shared_config->buffer_constraints()
+    last_output_constraints_ = shared_constraints;
+    FXL_VLOG(3) << "OnOutputConstraints buffer_constraints_version_ordinal: "
+                << shared_constraints->buffer_constraints()
                        .buffer_constraints_version_ordinal()
                 << "buffer_constraints_action_required: "
-                << shared_config->buffer_constraints_action_required();
-    if (shared_config->buffer_constraints_action_required()) {
-      last_required_output_config_ = shared_config;
+                << shared_constraints->buffer_constraints_action_required();
+    if (shared_constraints->buffer_constraints_action_required()) {
+      last_required_output_constraints_ = shared_constraints;
+      // A client is allowed to forget the output format on any action required
+      // buffer constraints, so forget here.
+      last_output_format_ = nullptr;
       FXL_VLOG(3) << "output_config_action_pending_ = true, because received a "
-                     "buffer_constraints_action_required config\n";
-      output_config_action_pending_ = true;
+                     "buffer_constraints_action_required constraints\n";
+      output_constraints_action_pending_ = true;
       if (!output_pending_) {
         output_pending_ = true;
         output_pending_notify_needed = true;
@@ -661,6 +868,42 @@
   }
 }
 
+void CodecClient::OnOutputFormat(
+    fuchsia::media::StreamOutputFormat output_format) {
+  // We don't need to notify output_pending_condition_ since in contrast to
+  // constraints we don't need to take action, and nobody cares about the format
+  // until the next packet arrives.
+  std::shared_ptr<fuchsia::media::StreamOutputFormat> shared_format =
+      std::make_shared<fuchsia::media::StreamOutputFormat>(
+          std::move(output_format));
+  std::unique_lock<std::mutex> lock(lock_);
+
+  if (!shared_format->has_stream_lifetime_ordinal()) {
+    FXL_LOG(FATAL) << "StreamOutputFormat missing stream_lifetime_ordinal";
+  }
+  uint64_t stream_lifetime_ordinal =
+      shared_format->stream_lifetime_ordinal();
+  TrackOutputStreamLifetimeOrdinal(stream_lifetime_ordinal);
+
+  if (is_format_since_last_packet_) {
+    // A server can easily elide unnecessary OnOutputFormat by not sending
+    // OnOutputFormat until immediately before OnOutputPacket.  The format is
+    // logically part of each output packet, with the optimization that we only
+    // send output format when it changes, in between packets, to avoid needing
+    // to send output format with every packet.
+    FXL_LOG(FATAL)
+        << "broken server sent two OnOutputFormat() in a row";
+  }
+  if (!shared_format->has_stream_lifetime_ordinal()) {
+    FXL_LOG(FATAL) << "OnOutputFormat !has_stream_lifetime_ordinal()";
+  }
+  if (!shared_format->has_format_details()) {
+    FXL_LOG(FATAL) << "OnOutputFormat !has_format_details()";
+  }
+  last_output_format_ = shared_format;
+  is_format_since_last_packet_ = true;
+}
+
 void CodecClient::OnOutputPacket(fuchsia::media::Packet output_packet,
                                  bool error_detected_before,
                                  bool error_detected_during) {
@@ -671,21 +914,28 @@
   std::unique_ptr<const fuchsia::media::Packet> local_packet =
       std::make_unique<fuchsia::media::Packet>(std::move(output_packet));
   uint32_t packet_index = local_packet->header().packet_index();
-  // This is safe outside the lock, because last_output_config_ is only updated
-  // by OnOutputConfig(), which can't happen simultaneously with
-  // OnOutputPacket().
-  uint64_t stream_lifetime_ordinal = local_packet->stream_lifetime_ordinal();
-  std::unique_ptr<CodecOutput> output = std::make_unique<CodecOutput>(
-      stream_lifetime_ordinal, last_output_config_, std::move(local_packet),
-      false);
+
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
-    if (output_config_action_pending_) {
+
+    if (!local_packet->has_stream_lifetime_ordinal()) {
+      FXL_LOG(FATAL) << "Packet missing stream_lifetime_ordinal";
+    }
+    uint64_t stream_lifetime_ordinal =
+        local_packet->stream_lifetime_ordinal();
+    TrackOutputStreamLifetimeOrdinal(stream_lifetime_ordinal);
+    if (!last_output_format_ || last_output_format_->stream_lifetime_ordinal() != stream_lifetime_ordinal) {
+      FXL_LOG(FATAL) << "OnOutputFormat required before OnOutputPacket, per-stream";
+    }
+
+    std::unique_ptr<CodecOutput> output = std::make_unique<CodecOutput>(
+        stream_lifetime_ordinal, last_output_constraints_, last_output_format_,
+        std::move(local_packet), false);
+    if (output_constraints_action_pending_) {
       // FWIW, we wouldn't be able to detect this if we were using the
       // async::Loop thread to perform output buffer re-configuration.
       FXL_LOG(FATAL) << "server incorrectly sent output packet while required "
-                        "config change "
-                        "pending";
+                        "constraints change pending";
     }
     if (!output_free_bits_[packet_index]) {
       // The packet was emitted twice by the server without it becoming free in
@@ -699,6 +949,7 @@
     // which case all the output packets start free with the server.
     output_free_bits_[packet_index] = false;
     emitted_output_.push_back(std::move(output));
+    is_format_since_last_packet_ = false;
     if (!output_pending_) {
       output_pending_ = true;
       output_pending_notify_needed = true;
@@ -713,15 +964,14 @@
                                       bool error_detected_before) {
   bool output_pending_notify_needed = false;
   std::unique_ptr<CodecOutput> output = std::make_unique<CodecOutput>(
-      stream_lifetime_ordinal, nullptr, nullptr, true);
+      stream_lifetime_ordinal, nullptr, nullptr, nullptr, true);
   {  // scope lock
     std::unique_lock<std::mutex> lock(lock_);
-    if (output_config_action_pending_) {
+    if (output_constraints_action_pending_) {
       // FWIW, we wouldn't be able to detect this if we were using the
       // async::Loop thread to perform output buffer re-configuration.
       FXL_LOG(FATAL) << "server incorrectly sent OnOutputEndOfStream() while "
-                        "required config "
-                        "change pending";
+                        "required constraints change pending";
     }
     emitted_output_.push_back(std::move(output));
     if (!output_pending_) {
diff --git a/garnet/lib/media/test/codec_output.cc b/garnet/lib/media/test/codec_output.cc
index 17d17cb..5d322fc 100644
--- a/garnet/lib/media/test/codec_output.cc
+++ b/garnet/lib/media/test/codec_output.cc
@@ -9,11 +9,13 @@
 
 CodecOutput::CodecOutput(
     uint64_t stream_lifetime_ordinal,
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> config,
+    std::shared_ptr<const fuchsia::media::StreamOutputConstraints> constraints,
+    std::shared_ptr<const fuchsia::media::StreamOutputFormat> format,
     std::unique_ptr<const fuchsia::media::Packet> packet,
     bool end_of_stream)
     : stream_lifetime_ordinal_(stream_lifetime_ordinal),
-      config_(config),
+      constraints_(constraints),
+      format_(format),
       packet_(std::move(packet)),
       end_of_stream_(end_of_stream) {
   // nothing else to do here
diff --git a/garnet/lib/media/test/frame_sink.cc b/garnet/lib/media/test/frame_sink.cc
index be457df..d281bce 100644
--- a/garnet/lib/media/test/frame_sink.cc
+++ b/garnet/lib/media/test/frame_sink.cc
@@ -43,7 +43,7 @@
 // Must be called on main_loop_'s thread.
 void FrameSink::PutFrame(
     uint32_t image_id, const zx::vmo& vmo, uint64_t vmo_offset,
-    std::shared_ptr<const fuchsia::media::StreamOutputConfig> output_config,
+    std::shared_ptr<const fuchsia::media::StreamOutputFormat> output_format,
     fit::closure on_done) {
   // This method fans out to the views_, and runs on_done async when all the
   // views_ are done with the frame.
@@ -67,13 +67,13 @@
   auto shared_done_runner =
       std::make_shared<decltype(done_runner)>(std::move(done_runner));
 
-  FXL_DCHECK(output_config->has_format_details());
-  FXL_DCHECK(output_config->format_details().has_domain());
-  FXL_DCHECK(output_config->format_details().domain().is_video());
+  FXL_DCHECK(output_format->has_format_details());
+  FXL_DCHECK(output_format->format_details().has_domain());
+  FXL_DCHECK(output_format->format_details().domain().is_video());
   FXL_DCHECK(
-      output_config->format_details().domain().video().is_uncompressed());
+      output_format->format_details().domain().video().is_uncompressed());
   const fuchsia::media::VideoUncompressedFormat& video_format =
-      output_config->format_details().domain().video().uncompressed();
+      output_format->format_details().domain().video().uncompressed();
 
   zx_time_t present_time;
   if (last_requested_present_time_ == ZX_TIME_INFINITE_PAST) {
diff --git a/garnet/lib/media/test/include/lib/media/test/codec_buffer.h b/garnet/lib/media/test/include/lib/media/test/codec_buffer.h
index 33d323b..a7b01df 100644
--- a/garnet/lib/media/test/include/lib/media/test/codec_buffer.h
+++ b/garnet/lib/media/test/include/lib/media/test/codec_buffer.h
@@ -26,6 +26,13 @@
       uint32_t buffer_index,
       const fuchsia::media::StreamBufferConstraints& constraints);
 
+  static std::unique_ptr<CodecBuffer> CreateFromVmo(
+      uint32_t buffer_index,
+      zx::vmo vmo,
+      uint32_t vmo_usable_start,
+      uint32_t vmo_usable_size,
+      bool need_write);
+
   // Each successful call to this method dups the VMO handle, with basic rights
   // + read + optional write depending on is_for_write.
   bool GetDupVmo(bool is_for_write, zx::vmo* out_vmo);
@@ -43,12 +50,15 @@
   explicit CodecBuffer(uint32_t buffer_index, size_t size_bytes);
   void SetPhysicallyContiguousRequired(
       const ::zx::handle& very_temp_kludge_bti_handle);
-  bool Init();
+  bool AllocateInternal();
+  bool CreateFromVmoInternal(zx::vmo vmo, uint32_t vmo_usable_start, uint32_t vmo_usable_size, bool need_write);
 
   uint32_t buffer_index_ = 0;
   size_t size_bytes_ = 0;
 
   bool is_physically_contiguous_required_ = false;
+
+  // TODO(dustingreen): Remove this:
   ::zx::bti very_temp_kludge_bti_handle_;
 
   zx::vmo vmo_;
diff --git a/garnet/lib/media/test/include/lib/media/test/codec_client.h b/garnet/lib/media/test/include/lib/media/test/codec_client.h
index 3f9682c..08e2adb 100644
--- a/garnet/lib/media/test/include/lib/media/test/codec_client.h
+++ b/garnet/lib/media/test/include/lib/media/test/codec_client.h
@@ -42,7 +42,7 @@
   // we want to be very sure that we'll be posting to the correct loop to send
   // messages using that loop's single thread, as ProxyController doesn't have
   // a lock_ in it.
-  CodecClient(async::Loop* loop);
+  CodecClient(async::Loop* loop, fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem);
   ~CodecClient();
 
   // Separate from Start() because we don't wan this class to handle the Codec
@@ -113,8 +113,30 @@
  private:
   friend class CodecStream;
 
+  void PostToFidlThread(fit::closure to_run);
+
   void CallSyncAndWaitForResponse();
 
+  void TrackOutputStreamLifetimeOrdinal(
+      uint64_t output_stream_lifetime_ordinal);
+
+  bool CreateAndSyncBufferCollection(
+      fuchsia::sysmem::BufferCollectionSyncPtr* out_buffer_collection,
+      fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>*
+          out_codec_sysmem_token);
+
+  bool WaitForSysmemBuffersAllocated(
+      fuchsia::sysmem::BufferCollectionSyncPtr* buffer_collection_param,
+      fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info);
+
+  bool ConfigurePortBufferCollection(
+      bool is_output, uint64_t new_buffer_lifetime_ordinal,
+      uint64_t buffer_constraints_version_ordinal,
+      uint32_t packet_count_for_server, uint32_t packet_count_for_client,
+      uint32_t* out_packet_count,
+      fuchsia::sysmem::BufferCollectionPtr* out_buffer_collection,
+      fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info);
+
   //
   // Events:
   //
@@ -137,7 +159,17 @@
   // more than one of this message per Codec instance (though not quite to the
   // degree needed to fully cover client handling of true mid-stream format
   // changes).
-  void OnOutputConfig(fuchsia::media::StreamOutputConfig output_config);
+  void OnOutputConstraints(
+      fuchsia::media::StreamOutputConstraints output_config);
+
+  // Ever output format is stream-specific with stream_lifetime_ordinal set, and
+  // the server is required to elide any unnecessary OnOutputFormat messages
+  // without any subsequent OnOutputPacket message to which the OnOutputFormat
+  // applies.  The format continues to apply to all subsequent output packets of
+  // the same stream until the stream ends or a new OnOutputFormat message is
+  // sent by the server.
+  void OnOutputFormat(
+      fuchsia::media::StreamOutputFormat output_format);
 
   // Every output packet is stream-specific with stream_lifetime_ordinal set.
   void OnOutputPacket(fuchsia::media::Packet output_packet,
@@ -154,6 +186,11 @@
   // requests give back a !is_valid() request.
   fidl::InterfaceRequest<fuchsia::media::StreamProcessor> temp_codec_request_;
 
+  fuchsia::sysmem::AllocatorPtr sysmem_;
+
+  fuchsia::sysmem::BufferCollectionPtr input_buffer_collection_;
+  fuchsia::sysmem::BufferCollectionPtr output_buffer_collection_;
+
   // We're use unique_ptr<> here only for it's optional-ness.
   std::unique_ptr<fuchsia::media::StreamBufferConstraints> input_constraints_;
   std::condition_variable input_constraints_exist_condition_;
@@ -202,42 +239,61 @@
   // how we notice.
   std::vector<bool> output_free_bits_;
 
+  // The server must order its output strictly by stream_lifetime_ordinal - once
+  // a new stream_lifetime_ordinal has started the server can't output anything
+  // with an older stream_lifetime_ordinal.
+  uint64_t output_stream_lifetime_ordinal_ = 0;
+
   // In contrast to free input packets, we care about the content of emitted
-  // output packets and their order.  In addition, OnOutputConfig() is ordered
+  // output packets and their order.  In addition, OnOutputConstraints() is ordered
   // with respect to output packets, so we just queue those along with the
   // output packets to avoid any ambiguity.
   //
   // A client that is immediately processing every output packet and just tracks
   // the most recent output config would work as long as it always associates
-  // an output packet with the closest prior StreamOutputConfig.
+  // an output packet with the closest prior StreamOutputConstraints.
   std::list<std::unique_ptr<CodecOutput>> emitted_output_;
 
   // For input, in this example we just know what the input format details are
   // and we send those to CodecFactory as part of CreateAudioDecoder_Params,
   // so we don't really need them as a member variable.
 
-  // For output, we have StreamOutputConfig here as a shared_ptr<> so we can
+  // For output, we have StreamOutputConstraints here as a shared_ptr<> so we can
   // explicitly associate each output packet with the config that applies to the
   // output packet.
   //
   // Note that stream_lifetime_ordinal is nearly entirely orthogonal from which
   // config applies.  The only interaction is that sometimes a new stream will
   // happen to have a different format so will cause format_details to update.
-  std::shared_ptr<const fuchsia::media::StreamOutputConfig> last_output_config_;
-  std::shared_ptr<const fuchsia::media::StreamOutputConfig>
-      last_required_output_config_;
-  // Becomes true when we get a new last_output_config_ with action required,
-  // and becomes false just before taking the needed action based on
-  // last_output_config_.
-  bool output_config_action_pending_ = false;
+  std::shared_ptr<const fuchsia::media::StreamOutputConstraints> last_output_constraints_;
+  std::shared_ptr<const fuchsia::media::StreamOutputConstraints>
+      last_required_output_constraints_;
+  // Most non-test clients will want to track this per-stream, since a
+  // StreamOutputFormat from an old stream_lifetime_ordinal() isn't relevant to
+  // the current stream_lifetime_ordinal.  A server is not allowed to send
+  // OnOutputFormat for an old stream_lifetime_ordinal.
+  std::shared_ptr<const fuchsia::media::StreamOutputFormat> last_output_format_;
+  // True if OnOutputFormat is more recent than OnOutputPacket.  This is
+  // intentionally tracked regardless of whether last_output_format_ has
+  // since been "forgotten" (set to nullptr) and regardless of whether the
+  // stream has since switched to a new stream, because we require servers to
+  // elide all unnecessary OnOutputFormat messages.  An OnOutputFormat without
+  // any subsequent OnOutputPacket of the same stream is by definition
+  // unnecessary.
+  bool is_format_since_last_packet_ = false;
+  // Becomes true when we get a new last_output_constraints_ with action
+  // required, and becomes false just before taking the needed action based on
+  // last_output_constraints_.
+  bool output_constraints_action_pending_ = false;
   // Only odd values are allowed for buffer_lifetime_ordinal.
   uint64_t next_output_buffer_lifetime_ordinal_ = 1;
+  uint64_t current_output_buffer_lifetime_ordinal_ = 0;
 
   // Invariant:
   // output_pending_ == (!emitted_output_.empty() ||
   // output_config_action_pending_)
   bool ComputeOutputPendingLocked() {
-    return !emitted_output_.empty() || output_config_action_pending_;
+    return !emitted_output_.empty() || output_constraints_action_pending_;
   }
   bool output_pending_ = false;
   std::condition_variable output_pending_condition_;
diff --git a/garnet/lib/media/test/include/lib/media/test/codec_output.h b/garnet/lib/media/test/include/lib/media/test/codec_output.h
index 6f2690a..10421d6 100644
--- a/garnet/lib/media/test/include/lib/media/test/codec_output.h
+++ b/garnet/lib/media/test/include/lib/media/test/codec_output.h
@@ -11,7 +11,7 @@
 #include <memory>
 
 // Each CodecOutput represents a Packet, and the correct associated
-// StreamOutputConfig for that packet.  Since the CodecClient takes care of
+// StreamOutputConstraints for that packet.  Since the CodecClient takes care of
 // buffer_constraints_action_required true internally, the consumer of
 // CodecOutput never has to deal with the situation where there's a new buffer
 // constraints that's action required before any more output packets will show
@@ -25,23 +25,30 @@
  public:
   CodecOutput(
       uint64_t stream_lifetime_ordinal,
-      std::shared_ptr<const fuchsia::media::StreamOutputConfig> config,
+      std::shared_ptr<const fuchsia::media::StreamOutputConstraints> constraints,
+      std::shared_ptr<const fuchsia::media::StreamOutputFormat> format,
       std::unique_ptr<const fuchsia::media::Packet> packet,
       bool end_of_stream);
 
   uint64_t stream_lifetime_ordinal() { return stream_lifetime_ordinal_; }
 
-  std::shared_ptr<const fuchsia::media::StreamOutputConfig> config() {
+  std::shared_ptr<const fuchsia::media::StreamOutputConstraints> constraints() {
     // Caller should only call this after checking end_of_stream() first.
-    assert(config_);
-    return config_;
+    ZX_ASSERT(constraints_);
+    return constraints_;
+  }
+
+  std::shared_ptr<const fuchsia::media::StreamOutputFormat> format() {
+    // Caller should only call this after checking end_of_stream() first.
+    ZX_ASSERT(format_);
+    return format_;
   }
 
   // The caller doesn't own the returned reference, and the caller must ensure
   // the returned reference isn't retained beyond the lifetime of CodecOutput.
   const fuchsia::media::Packet& packet() {
     // Caller should only call this after checking end_of_stream() first.
-    assert(packet_);
+    ZX_ASSERT(packet_);
     return *packet_;
   }
 
@@ -49,8 +56,10 @@
 
  private:
   uint64_t stream_lifetime_ordinal_ = 0;
-  // The shared_ptr<> is just to optimize away copying an immutable config.
-  std::shared_ptr<const fuchsia::media::StreamOutputConfig> config_;
+  // The shared_ptr<> is just to optimize away copying an immutable constraints.
+  std::shared_ptr<const fuchsia::media::StreamOutputConstraints> constraints_;
+  // The shared_ptr<> is just to optimize away copying an immutable format.
+  std::shared_ptr<const fuchsia::media::StreamOutputFormat> format_;
   std::unique_ptr<const fuchsia::media::Packet> packet_;
 
   bool end_of_stream_ = false;
diff --git a/garnet/lib/media/test/include/lib/media/test/frame_sink.h b/garnet/lib/media/test/include/lib/media/test/frame_sink.h
index cbf40a5..d04069b 100644
--- a/garnet/lib/media/test/include/lib/media/test/frame_sink.h
+++ b/garnet/lib/media/test/include/lib/media/test/frame_sink.h
@@ -47,8 +47,8 @@
   // The on_done will get called on main_loop_'s thread.  If the callee
   // wants/needs to, the callee can post to a different thread.
   void PutFrame(uint32_t image_id, const zx::vmo& vmo, uint64_t vmo_offset,
-                std::shared_ptr<const fuchsia::media::StreamOutputConfig>
-                    output_config,
+                std::shared_ptr<const fuchsia::media::StreamOutputFormat>
+                    output_format,
                 fit::closure on_done);
 
   // (Quickly) cause all frames to eventually be returned (assuming the rest of
diff --git a/garnet/lib/ui/gfx/displays/display_manager.cc b/garnet/lib/ui/gfx/displays/display_manager.cc
index e56034b..e56f7d6 100644
--- a/garnet/lib/ui/gfx/displays/display_manager.cc
+++ b/garnet/lib/ui/gfx/displays/display_manager.cc
@@ -230,7 +230,7 @@
   if (display_controller_->ImportBufferCollection(
           buffer_collection_id, std::move(token), &status) != ZX_OK ||
       status != ZX_OK) {
-    FXL_DLOG(ERROR) << "ImportBufferCollection failed";
+    FXL_DLOG(ERROR) << "ImportBufferCollection failed - status: " << status;
     return 0;
   }
 
diff --git a/garnet/packages/examples/BUILD.gn b/garnet/packages/examples/BUILD.gn
index d19ea43..1def270 100644
--- a/garnet/packages/examples/BUILD.gn
+++ b/garnet/packages/examples/BUILD.gn
@@ -163,7 +163,6 @@
     "//garnet/examples/media:simple_sine_sync",
     "//garnet/examples/media:tones",
     "//garnet/examples/media:tts",
-    "//garnet/examples/media:use_aac_decoder_test",
     "//garnet/examples/media:use_h264_decoder_test",
     "//garnet/examples/media:use_media_decoder",
   ]
diff --git a/garnet/packages/prod/BUILD.gn b/garnet/packages/prod/BUILD.gn
index 802cb9c..399ed02 100644
--- a/garnet/packages/prod/BUILD.gn
+++ b/garnet/packages/prod/BUILD.gn
@@ -579,7 +579,6 @@
   public_deps = [
     "//garnet/bin/media:codec_factory",
     "//garnet/bin/media:codec_runner_sw_ffmpeg",
-    "//garnet/bin/media:codec_runner_sw_omx",
   ]
 }
 
diff --git a/garnet/packages/prod/media_codec b/garnet/packages/prod/media_codec
new file mode 100644
index 0000000..0196e8c
--- /dev/null
+++ b/garnet/packages/prod/media_codec
@@ -0,0 +1,6 @@
+{
+    "packages": [
+        "//garnet/bin/media:codec_factory",
+        "//garnet/bin/media:codec_runner_sw_ffmpeg"
+    ]
+}
diff --git a/garnet/packages/tests/BUILD.gn b/garnet/packages/tests/BUILD.gn
index 03416f4..253e2ef 100644
--- a/garnet/packages/tests/BUILD.gn
+++ b/garnet/packages/tests/BUILD.gn
@@ -748,7 +748,7 @@
     "//garnet/bin/media/audio_core/test:audio_fidl_tests",
     "//garnet/bin/media/codecs/sw:mpsc_queue_tests",
     "//garnet/bin/media/codecs/test:raw_frames_test",
-    "//garnet/examples/media:use_aac_decoder_test",
+    "//garnet/examples/media:use_h264_decoder_test",
     "//garnet/packages/prod:media_audio",
     "//garnet/packages/testing:run_test_component",
     "//garnet/packages/tests:virtual_audio",
diff --git a/garnet/public/lib/fostr/fidl/fuchsia.media/BUILD.gn b/garnet/public/lib/fostr/fidl/fuchsia.media/BUILD.gn
index f823c01..42c6430 100644
--- a/garnet/public/lib/fostr/fidl/fuchsia.media/BUILD.gn
+++ b/garnet/public/lib/fostr/fidl/fuchsia.media/BUILD.gn
@@ -10,5 +10,6 @@
   deps = [
     "//garnet/public/lib/fostr/fidl/fuchsia.images",
     "//garnet/public/lib/fostr/fidl/fuchsia.media.audio",
+    "//garnet/public/lib/fostr/fidl/fuchsia.sysmem",
   ]
 }
diff --git a/garnet/public/lib/fostr/fidl/fuchsia.sysmem/BUILD.gn b/garnet/public/lib/fostr/fidl/fuchsia.sysmem/BUILD.gn
new file mode 100644
index 0000000..4986f2c
--- /dev/null
+++ b/garnet/public/lib/fostr/fidl/fuchsia.sysmem/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//garnet/public/build/fostr/fostr_fidl.gni")
+
+fostr_fidl("fuchsia.sysmem") {
+  fidl_target = "//zircon/public/fidl/fuchsia-sysmem"
+}
diff --git a/sdk/fidl/fuchsia.media/BUILD.gn b/sdk/fidl/fuchsia.media/BUILD.gn
index 8dd57c3..93468aa 100644
--- a/sdk/fidl/fuchsia.media/BUILD.gn
+++ b/sdk/fidl/fuchsia.media/BUILD.gn
@@ -24,5 +24,8 @@
   public_deps = [
     "//sdk/fidl/fuchsia.images",
     "//sdk/fidl/fuchsia.media.audio",
+    "//zircon/public/fidl/fuchsia-io",
+    "//zircon/public/fidl/fuchsia-mem",
+    "//zircon/public/fidl/fuchsia-sysmem",
   ]
 }
diff --git a/sdk/fidl/fuchsia.media/fuchsia.media.api b/sdk/fidl/fuchsia.media/fuchsia.media.api
index e5e9fc1..dc354e5 100644
--- a/sdk/fidl/fuchsia.media/fuchsia.media.api
+++ b/sdk/fidl/fuchsia.media/fuchsia.media.api
@@ -6,8 +6,8 @@
   "fidl/fuchsia.media/audio_renderer.fidl": "c36b624d329a6e80f3e9432794a038e9",
   "fidl/fuchsia.media/metadata.fidl": "fb11fd050505ea7a8fc661754b044012",
   "fidl/fuchsia.media/stream.fidl": "92dd4376d67914a882c7ae46874e6ca1",
-  "fidl/fuchsia.media/stream_common.fidl": "bdfa65aee3d20148233912c78101b4a8",
-  "fidl/fuchsia.media/stream_processor.fidl": "483e50cf3eb2859d47a0932308587e18",
+  "fidl/fuchsia.media/stream_common.fidl": "766af335abfd6779bd1924fa75e15326",
+  "fidl/fuchsia.media/stream_processor.fidl": "37d7c04a0dbca243a2c344a6b9a5508d",
   "fidl/fuchsia.media/stream_type.fidl": "84bed4307c847efb75e47bf49131d323",
   "fidl/fuchsia.media/timeline_function.fidl": "5ada023b93836a20fb45b278f3822ee4"
 }
\ No newline at end of file
diff --git a/sdk/fidl/fuchsia.media/stream_common.fidl b/sdk/fidl/fuchsia.media/stream_common.fidl
index 604eb4b..3ada02d 100644
--- a/sdk/fidl/fuchsia.media/stream_common.fidl
+++ b/sdk/fidl/fuchsia.media/stream_common.fidl
@@ -4,6 +4,8 @@
 
 library fuchsia.media;
 
+using fuchsia.sysmem;
+
 /// Value
 ///
 /// Generic "value" for use within generic "Parameter" struct.
@@ -98,7 +100,7 @@
 
 enum AudioBitrateMode {
     // Used mainly when a client is configuring an encoder's output format.  May
-    // also be present in an OnOutputConfig() message from an encoder, but
+    // also be present in an OnOutputConstraints() message from an encoder, but
     // should not be relied upon to be present by any consumer downstream of an
     // encoder.
     UNSPECIFIED = 0;
@@ -124,7 +126,7 @@
     // max bits per second and min bits per second, with the aim in VBR being
     // constant perceived quality.
     //
-    // In OnOutputConfig():
+    // In OnOutputConstraints():
     //
     // In CBR, the nominal bits per second.  In VBR, the nominal max bits per
     // second.
@@ -135,7 +137,7 @@
     // If UNSPECIFIED, up to the encoder.  If CBR or VBR, a hint to the encoder
     // to use that mode.
     //
-    // In OnOutputConfig():
+    // In OnOutputConstraints():
     //
     // Actual mode being used.  UNSPECIFIED means the source is not specifying
     // which mode.
@@ -257,12 +259,9 @@
 ///
 /// Compressed video format details.
 ///
-/// Mostly encoder settings will go under here.
-///
-// If a compressed video format is missing any fields here other than encoder
-// settings, it's because it's a good format and is already self-describing
-// given the mime_type + format-defined oob_bytes as appropriate +
-// in-band data.
+// If a compressed video format has no fields here, it's because it's a good
+// format and is already self-describing given the mime_type + format-defined
+// oob_bytes as appropriate + in-band data.
 union VideoCompressedFormat {
     // TODO(dustingreen): Any compressed video formats that aren't sufficiently
     // self-describing to select and create a Codec instance to decode it?
@@ -272,20 +271,6 @@
     uint32 temp_field_todo_remove;
 };
 
-/// VideoUncompressedFormatSpecificDetails
-///
-/// Extended format-specific uncompressed video format details.
-///
-// TODO(dustingreen): Switch to FIDL table instead.
-union VideoUncompressedFormatSpecificDetails {
-    // TODO(dustingreen): Which formats that we care about really require
-    // special format-specific details here?
-
-    // TODO(dustingreen): temp field to make the compiler happy until we have at
-    // least one real field.
-    uint32 temp_field_todo_remove;
-};
-
 enum VideoColorSpace {
     // TODO(dustingreen): add to this list
     INVALID = 0;
@@ -296,7 +281,13 @@
 /// Uncompressed video format details.
 ///
 // TODO(dustingreen): Integrate with a system-wide structure for this purpose.
+// In progress - see image_format field below which will take the place of this
+// struct/table.
 struct VideoUncompressedFormat {
+    // TODO(dustingreen): This will replace VideoUncompressedFormat (after
+    // struct to table change merges).
+    fuchsia.sysmem.ImageFormat_2 image_format;
+
     // fourcc
     //
     // A human-readable fourcc like RGBA should be 0x41424752 in the fourcc
@@ -383,23 +374,13 @@
     bool has_pixel_aspect_ratio = false;
     uint32 pixel_aspect_ratio_width = 1;
     uint32 pixel_aspect_ratio_height = 1;
-
-    // TODO(dustingreen): Currently this assumes 8 bits per channel, but we'll
-    // need fields to indicate more bits per pixel such as 10 or 12 bits per
-    // pixel.  Also, potentially a way to indicate different number of bits per
-    // channel for 565 16 bit RGB + packing details.  Also, potentially
-    // endian-ness.
-    //
-    // TODO(dustingreen): Also, an easy way to get a template
-    // VideoUncompressedFormat that's pre-populated with reasonably-plausible
-    // values, based on a fourcc or enum value + maybe primary resolution.
-
-    VideoUncompressedFormatSpecificDetails special_formats;
 };
 
 /// VideoFormat
 ///
-// Video (compress or uncompressed) format details.
+/// Video (compress or uncompressed) format details.  In this context,
+/// "uncompressed" can include block-based image compression formats that still
+/// permit fairly fast random access to image data.
 union VideoFormat {
     VideoCompressedFormat compressed;
     VideoUncompressedFormat uncompressed;
@@ -417,8 +398,8 @@
 
 /// FormatDetails
 ///
-/// This describes/details the format on input or output of a StreamProcessor (separate
-/// instances for input vs. output).
+/// This describes/details the format on input or output of a StreamProcessor
+/// (separate instances for input vs. output).
 table FormatDetails {
     // Particular instances of FormatDetails will set this field to make it
     // easier for a receiver to determine if any part of the format has changed
@@ -484,7 +465,7 @@
     // The encoder's compressed data output typically needs some configuration
     // (provided in this field) that's convenient to provide in a form that's
     // not oob_bytes, and the codec can convert that config to oob_bytes on
-    // encoder output via OnOutputConfig().  We retain these encoder settings
+    // encoder output via OnOutputConstraints().  We retain these encoder settings
     // in the output FormatDetails to allow for cases where a downstream
     // consumer knowing the encoder settings could be useful.
     //
diff --git a/sdk/fidl/fuchsia.media/stream_processor.fidl b/sdk/fidl/fuchsia.media/stream_processor.fidl
index da4262f..b256793 100644
--- a/sdk/fidl/fuchsia.media/stream_processor.fidl
+++ b/sdk/fidl/fuchsia.media/stream_processor.fidl
@@ -4,6 +4,8 @@
 
 library fuchsia.media;
 
+using fuchsia.sysmem;
+
 // See stream_processor.md for detailed interface documentation. The comments
 // here are a summary only.  Client implementers should see stream_processor.md
 // for more detail on any message that doesn't seem sufficiently-described
@@ -24,7 +26,7 @@
 //   * QueueInputPacket() + OnFreeInputPacket(), for as long as it takes,
 //     possibly working through all input packets repeatedly before...
 // 5. Get output constraints and format
-//   * OnOutputConfig() - may be delivered as early as before
+//   * OnOutputConstraints() - may be delivered as early as before
 //     OnInputConstraints() by some stream processors, but a client must
 //     tolerate as late as after substantial input data has been delivered
 //     including lots of input packet recycling via OnFreeInputPacket().
@@ -54,6 +56,10 @@
 
 // There are separate instances of this struct for stream input and stream
 // output.
+//
+// TODO(dustingreen): Some of the buffer-focused fields in this structure will
+// go away in favor of using sysmem for those aspects.  The packet count fields
+// will stay.  The single-buffer fields will stay.
 table StreamBufferConstraints {
     // This is a version number the server sets on the constraints to allow the
     // server to determine when the client has caught up with the latest
@@ -230,8 +236,11 @@
     12: bool single_buffer_mode_allowed;
 
     // If true, the buffers need to be physically contiguous pages, such as can
-    // be allocated using zx_vmo_create_contiguous().
+    // be allocated using zx_vmo_create_contiguous() (this syscall requires a
+    // bti handle which many clients won't have - the client will most likely
+    // want to use sysmem to allocate buffers).
     13: bool is_physically_contiguous_required;
+
     // VERY TEMPORARY HACK / KLUDGE - we want the BufferAllocator (or one of
     // the participant drivers that needs physically contiguous buffers) to
     // call zx_vmo_create_contiguous(), definitely not the StreamProcessor
@@ -242,10 +251,14 @@
     // allocates buffers in the StreamProcessor interface to avoid this hack
     // even before BufferAllocator, but the overall path seems shorter if we
     // jump directly from this to using BufferAllocator.
+    //
+    // TODO(dustingreen): remove once zero clients need this (zero clients that
+    // need to allocate physically contiguous buffers directly / all relevant
+    // clients using sysmem).
     14: handle very_temp_kludge_bti_handle;
 };
 
-// StreamOutputConfig
+// StreamOutputConstraints
 //
 // The stream-processor-controlled output configuration, including both
 // StreamBufferConstraints for the output and FormatDetails for the output.
@@ -256,17 +269,17 @@
 // It's different than output buffer settings, which the client does get to
 // control to some extent.  It's different than any configurable output
 // settings the client might specify for output of an encoder.
-table StreamOutputConfig {
+table StreamOutputConstraints {
     // A client which always immediately re-configures output buffers on
-    // receipt of OnOutputConfig() with buffer_constraints_action_required true
+    // receipt of OnOutputConstraints() with buffer_constraints_action_required true
     // can safely ignore this field.
 
-    // A client is permitted to ignore an OnOutputConfig() message even with
+    // A client is permitted to ignore an OnOutputConstraints() message even with
     // buffer_constraints_action_required true if the client knows the server
     // has already been told to discard the remainder of the stream with the
     // same stream_lifetime_ordinal or if this stream_lifetime_ordinal field is
     // set to 0.  The server is required to re-send needed output config via
-    // OnOutputConfig() with new stream_lifetime_ordinal and
+    // OnOutputConstraints() with new stream_lifetime_ordinal and
     // buffer_constraints_action_required true, if the most recent completed
     // server-side output config isn't what the server wants/needs yet for the
     // new stream.
@@ -306,6 +319,22 @@
     // See stream_processor.md for more on buffer_constraints_action_required.
     2: bool buffer_constraints_action_required;
     3: StreamBufferConstraints buffer_constraints;
+};
+
+table StreamOutputFormat {
+    // A client is permitted to ignore an OnOutputFormat() message even with
+    // buffer_constraints_action_required true if the client knows the server
+    // has already been told to discard the remainder of the stream with the
+    // same stream_lifetime_ordinal or if this stream_lifetime_ordinal field is
+    // set to 0.  The server is required to re-send needed output config via
+    // OnOutputConstraints() with new stream_lifetime_ordinal and
+    // buffer_constraints_action_required true, if the most recent completed
+    // server-side output config isn't what the server wants/needs yet for the
+    // new stream.
+    //
+    // The server is required to send an OnOutputFormat() before the first
+    // output packet of a stream.
+    1: uint64 stream_lifetime_ordinal;
 
     // format_details
     //
@@ -366,6 +395,15 @@
     // technically supports mid-stream format change, but the client happens to
     // know that none of the streams the client intends to process will ever
     // have a mid-stream format change.
+    2: FormatDetails format_details;
+};
+
+// DEPRECATED - this is splitting into StreamOutputConstraints and
+// StreamOutputFormat instead.  See those tables.
+table StreamOutputConfig {
+    1: uint64 stream_lifetime_ordinal;
+    2: bool buffer_constraints_action_required;
+    3: StreamBufferConstraints buffer_constraints;
     4: FormatDetails format_details;
 };
 
@@ -470,7 +508,7 @@
     // server's use. This must be >=
     // StreamBufferConstraints.packet_count_for_server_min.  If constraints
     // change such that this would no longer be true, the server will send an
-    // OnOutputConfig() event.
+    // OnOutputConstraints() event.
     //
     // The stream processor server is allowed to demand that all of
     // packet_count_for_server become free before making further progress, even
@@ -539,6 +577,102 @@
     6: bool single_buffer_mode;
 };
 
+// StreamBufferPartialSettings
+//
+// This struct is used instead of StreamBufferSettings when sysmem is used to
+// allocate buffers.  The settings in StreamBufferSettings that are missing from
+// StreamBufferPartialSettings can be conveyed from the client directly to
+// sysmem.
+//
+// TODO(dustingreen): Consider renaming this to StreamBufferSettings once that's
+// an option.
+table StreamBufferPartialSettings {
+    // buffer_lifetime_ordinal
+    //
+    // The containing message starts a new buffer_lifetime_ordinal.
+    //
+    // There is a separate buffer_lifetime_ordinal for input vs. output.
+    //
+    // Re-use of the same value is not allowed.  Values must be odd.  Values
+    // must only increase (increasing by more than 2 is permitted).
+    //
+    // A buffer_lifetime_ordinal lifetime starts at SetInputBufferSettings() or
+    // SetOutputBufferSettings(), and ends at the earlier of
+    // CloseCurrentStream() with release_input_buffers/release_output_buffers
+    // set or SetOutputBufferSettings() with new buffer_lifetime_ordinal in the
+    // case of mid-stream output config change.
+    //
+    // See stream_processor.md for more on buffer_lifetime_ordinal.
+    1: uint64 buffer_lifetime_ordinal;
+
+    // buffer_constraints_version_ordinal
+    //
+    // This value indicates which version of constraints the client is/was aware
+    // of so far.
+    //
+    // For input, this must always be 0 because constraints don't change for
+    // input (settings can change, but there's no settings vs current
+    // constraints synchronization issue on input).
+    //
+    // For output, this allows the server to know when the client is
+    // sufficiently caught up before the server will generate any more output.
+    //
+    // When there is no active stream, a client is permitted to re-configure
+    // buffers again using the same buffer_constraints_version_ordinal.
+    //
+    // See stream_processor.md for more on buffer_constraints_version_ordinal.
+    2: uint64 buffer_constraints_version_ordinal;
+
+    // If true, there is only one buffer, but still typically more than one
+    // packet.  If false, the # of packets == the number of buffers.
+    //
+    // While it's possible to set up single_buffer_mode false with each buffer
+    // referring to the same underlying VMO, single_buffer_mode true is more
+    // efficient for that case since only one mapping is created.
+    //
+    // This setting is specified by the client, and influences the constraints
+    // delivered from the StreamProcessor to sysmem (whether there's more than
+    // one buffer allocated overall or not).  For single_buffer_mode true, the
+    // StreamProcessor is the one to ask sysmem for a buffer - the client should
+    // refrain from doing so or the StreamProcessor will just fail when more
+    // than one buffer gets allocated by sysmem.
+    3: bool single_buffer_mode;
+
+    // When single_buffer_mode is false:
+    //
+    // The actual packet count will be
+    // max(packet_count_for_server + packet_count_for_client, sysmem_buffers).
+    // The sysmem_buffers is BufferCollectionInfo.buffer_count from sysmem if
+    // using sysmem, or 0 if not using sysmem.
+    //
+    // When single_buffer_mode is true:
+    //
+    // The actual packet count is packet_count_for_server +
+    // packet_count_for_client.
+    //
+    // If not using sysmem, or if using single_buffer_mode, these fields must be
+    // set and consistent with correpsonding fields in StreamBufferConstraints.
+    //
+    // If single_buffer_mode false and using sysmem, these fields can both be
+    // non-set, or can both be set and consistent with correpsonding fields in
+    // StreamBufferConstraints.  If not set, the value used for the fields in
+    // the "max" expression above is 0, so buffer_count.
+    //
+    // TODO(dustingreen): Try to change FIDL generated code to not require has
+    // if the field has a default specified in the FIDL file like these do.
+    4: uint32 packet_count_for_server = 0;
+    5: uint32 packet_count_for_client = 0;
+
+    // The client end of a BufferCollectionToken channel, which the
+    // StreamProcessor will use to deliver constraints to sysmem and learn of
+    // buffers allocated by sysmem.
+    //
+    // The client guarantees that the token is already known to sysmem (via
+    // BufferCollectionToken.Sync(), BufferCollection.Sync(), or
+    // BufferCollectionEvents.OnDuplicatedTokensKnownByServer()).
+    6: fuchsia.sysmem.BufferCollectionToken sysmem_token;
+};
+
 // StreamBuffer
 //
 // The StreamBuffer struct represents a pre-configured buffer.
@@ -591,25 +725,23 @@
     3: StreamBufferData data;
 };
 
-// StreamBufferData
-//
-// For the moment, a VMO per buffer is the only type of buffer.
-//
-
-// This is extremely likely to change significantly when adding gralloc stuff,
-// but the idea with this union is to have a struct per logical way of storing
-// the data.  Any multi-domain storage within a gralloc buffer will likely be
-// only indirectly represented here.
+/// StreamBufferData
+///
+/// For the moment, a VMO per buffer is the only type of buffer.
+///
+/// This is extremely likely to change significantly when adding gralloc stuff,
+/// but the idea with this union is to have a struct per logical way of storing
+/// the data.  Any multi-domain storage within a gralloc buffer will likely be
+/// only indirectly represented here.
 union StreamBufferData {
     StreamBufferDataVmo vmo;
 
     // TODO(dustingreen): add the gralloc way
 };
 
-// StreamBufferDataVmo
-//
-
-// Details for a buffer backed by a VMO.
+/// StreamBufferDataVmo
+///
+/// Details for a buffer backed by a VMO.
 table StreamBufferDataVmo {
     // The same VMO can be used by more than one StreamBuffer (only of the same
     // buffer_lifetime_ordinal), but each vmo_handle must be a separate handle.
@@ -627,14 +759,13 @@
     3: uint64 vmo_usable_size;
 };
 
-// PacketHeader
-//
-// When referring to a free packet, we use PacketHeader alone instead of
-// Packet, since while a packet is free it doesn't really have meaningful
-// offset or length etc.
-//
-
-// A populated Packet also has a PacketHeader.
+/// PacketHeader
+///
+/// When referring to a free packet, we use PacketHeader alone instead of
+/// Packet, since while a packet is free it doesn't really have meaningful
+/// offset or length etc.
+///
+/// A populated Packet also has a PacketHeader.
 table PacketHeader {
     // This is which buffer configuration lifetime this header is referring to.
     //
@@ -705,6 +836,10 @@
     // likely (will tend to complain in an obvious way if not filled out
     // instead of a non-obvious data corruption when decoding buffer 0
     // repeatedly instead of the correct buffers).
+    //
+    // TODO(dustingreen): Try to make FIDL table defaults have meaning, and not
+    // complain about !has when accessing the field.  For now the default
+    // specified here does nothing.
     2: uint32 buffer_index = 0x80000000;
 
     // stream_lifetime_ordinal
@@ -943,16 +1078,16 @@
     // This message is guaranteed to be sent unsolicited to the StreamProcessor
     // client during or shortly after StreamProcessor creation.  Clients should
     // not depend on this being the very first message to arrive at the client.
-    // OnOutputConfig() may be sent first by some stream processors that
+    // OnOutputConstraints() may be sent first by some stream processors that
     // already know their initial output config without any input data, to
     // encourage (but not strictly require) the client to configure output
     // buffers before feeding the first input, to avoid a wasteful
-    // OnOutputConfig() being generated for that first stream if the client has
+    // OnOutputConstraints() being generated for that first stream if the client has
     // started configuring output but isn't done configuring output before the
     // client sends the first input data for the first stream. A client is free
-    // to ignore OnOutputConfig() with a stale stream_lifetime_ordinal, but
-    // handling OnOutputConfig() with stream_lifetime_ordinal 0 (if any are
-    // sent) can help reduce latency to first output.  See OnOutputConfig() for
+    // to ignore OnOutputConstraints() with a stale stream_lifetime_ordinal, but
+    // handling OnOutputConstraints() with stream_lifetime_ordinal 0 (if any are
+    // sent) can help reduce latency to first output.  See OnOutputConstraints() for
     // more details.
     //
     // The "min" and "max" input constraints are guaranteed not to change for a
@@ -1008,31 +1143,175 @@
     // QueueInputPacket() after sending the last AddInputBuffer().
     AddInputBuffer(StreamBuffer buffer);
 
-    // OnOutputConfig()
+    // SetInputBufferPartialSettings()
     //
-    // This event informs the client of new output config.  The server will
-    // send at least one of these messages before the first output packet of a
-    // stream, but that message might not have
+    // When the client is using sysmem to allocate buffers, this message is
+    // used instead of SetInputBufferSettings()+AddInputBuffer().  Instead, a
+    // single SetInputBufferPartialSettings() provides the StreamProcessor with
+    // the client-specified input settings and a BufferCollectionToken which
+    // the StreamProcessor will use to convey constraints to sysmem.  Both the
+    // client and the StreamProcessor will be informed of the allocated buffers
+    // directly by sysmem via their BufferCollection channel (not via the
+    // StreamProcessor channel).
+    //
+    // The client must not QueueInput...() until after sysmem informs the client
+    // that buffer allocation has completed and was successful.
+    //
+    // The server should be prepared to see QueueInput...() before the server
+    // has necessarily heard from sysmem that the buffers are allocated - the
+    // server must tolerate either ordering, as the QueueInput...() and
+    // notification of sysmem allocation completion arrive on different
+    // channels, so the client having heard that allocation is complete doesn't
+    // mean the server knows that allocation is complete yet.  However, the
+    // server can expect that allocation is in fact complete and can expect to
+    // get the allocation information from sysmem immediately upon requesting
+    // the information from sysmem.
+    SetInputBufferPartialSettings(StreamBufferPartialSettings input_settings);
+
+    // OnOutputConstraints()
+    //
+    // This event informs the client of new output constraints.
+    //
+    // This message is ordered with respect to other output (such as output
+    // packets, output format, output end-of-stream).
+    //
+    // Before the first OnOutputPacket() of a stream, the server guarantees that
+    // at least one OnOutputConstraints() and exactly one OnOutputFormat() will
+    // be sent.  The server may not set buffer_constraints_action_required true
+    // in OnOutputConstraints() if the buffer config is already suitable for the
+    // stream (buffer_constraints_action_required false means the buffer config
+    // is already fine).  The client must tolerate multiple
+    // OnOutputConstraints() (and 1 OnOutputFormat() message) before the first
+    // output packet.  As long as the client hasn't moved to a new stream, the
+    // server won't send another OnOutputConstraints() until after the client
+    // has configured output buffers.
+    //
+    // This message can be sent mid-stream by a server.  If
+    // buffer_constraints_action_required false, the message is safe to
+    // ignore, but a client may choose to stash the new constraints for
+    // later use the next time the client wants to unilaterally re-configure
+    // buffers (when allowed).  If later the server needs the output config to
+    // change, the server may send a new OnOutputConstraints() with
     // buffer_constraints_action_required true.
     //
-    // If buffer_constraints_action_required is true and the
-    // stream_lifetime_ordinal matches the current stream, the client must react
-    // by configuring or re-configuring output buffers.
+    // On buffer_constraints_action_required true, a client that does not wish
+    // to fully handle mid-stream output buffer config changes should either
+    // give up completely on the processing, or at least re-config the output
+    // as specified before starting a new stream (and possibly re-delivering
+    // input data, if the client wants).  This avoids useless retry with a new
+    // stream starting from just before the output buffer config change which
+    // would hit the same mid-stream output config change again.
     //
-    // Some clients may prefer not to support mid-stream output config changes,
-    // but even those clients are required to process OnOutputConfig() messages
-    // up to the first output packet of each stream, as OnOutputConfig() is used
-    // for stream format detection as well as for potential mid-stream output
-    // config changes.
+    // Similarly, some servers may only partly support mid-stream format
+    // changes, or only support a mid-stream format change if the buffers are
+    // already large enough to handle both before and after the format change.
+    // Such servers should still indicate buffer_constraints_action_required
+    // true, but then send OnStreamFailed() after the client has re-configured
+    // output buffers (seamlessly dealing with the mid-stream output config
+    // change is even better of course, but is not always feasible depending on
+    // format).  When the client retries with a new stream starting from a
+    // nearby location in the client's logical overall media timeline, the
+    // output buffers will already be suitable for the larger size output, so
+    // the new stream will not need any mid-stream output buffer re-config,
+    // only a mid-stream OnOutputFormat().  This strategy avoids the problem
+    // that would otherwise occur if a client were to retry with a new stream
+    // starting just before the mid-stream output buffer config change (the
+    // retry wouldn't be effective since the same need for an output buffer
+    // config change would be hit again).  Servers are discouraged from sending
+    // OnStreamFailed() solely due to a mid-stream need for different output
+    // buffer config without first sending OnOutputConstraints() with
+    // buffer_constraints_action_required true and waiting for the client to
+    // re-configure output buffers (to avoid the useless client retry with a
+    // new stream from a logical location before the config change).
     //
-    // For more on OnOutputConfig(), see cocec.md.
+    // When buffer_constraints_action_required true, the server will not send
+    // any OnOutputPacket() for this stream until after the client has
+    // configured/re-configured output buffers.
+    //
+    // A client that gives up on processing on any mid-stream
+    // OnOutputConstraints() or mid-stream OnOuptutFormat() should completely
+    // ignore any OnOutputConstraints() with buffer_constraints_action_required
+    // false.  Otherwise the client may needlessly fail processing, or server
+    // implementations might not be able to use
+    // buffer_constraints_action_required false for fear of simpler clients
+    // just disconnecting.
+    //
+    // All clients, even those which don't want to support any mid-stream
+    // output buffer re-config or mid-stream OnOutputFormat() are required to
+    // deal with 1..multiple OnOutputConstraints() messages before the first
+    // output packet, and 1 OnOutputFormat() messages before the first output
+    // packet.
+    //
+    // This message is ordered with respect to output packets, and with respect
+    // to OnOutputFormat().
+    //
+    // For more on OnOutputConstraints(), see cocec.md.
+    -> OnOutputConstraints(StreamOutputConstraints output_config);
+
+    // OnOutputFormat()
+    //
+    // This message is sent by the server before the first output packet of any
+    // stream, and potentially mid-stream between output packets of the stream,
+    // ordered with respect to output packets, and ordered with respect to
+    // OnOutputConstraints().
+    //
+    // The server guarantees that the first packet of every stream will be
+    // preceeded by an OnOutputFormat().
+    //
+    // The server guarantees that there will be an OnOutputFormat() between an
+    // OnOutputConstraints() with buffer_constraints_action_required true and an
+    // OnOutputPacket().  In other words, the client is essentially allowed to
+    // forget what the output format is on any OnOutputConstraints() with
+    // buffer_constraints_action_required true, because the server promises a
+    // subsequent OnOutputFormat() before any OnOutputPacket().
+    //
+    // If the server sets buffer_constraints_action_required true in
+    // OnOutputConstraints(), the server won't send OnOutputFormat() (and therefore
+    // also won't send OnOutputPacket()) until the client has re-configured
+    // output buffers.
+    //
+    // The server is allowed to send an OnOutputFormat() mid-stream between two
+    // output packets.
+    //
+    // A server won't send two adjacent OnOutputFormat() messages without any
+    // output packet in between.  However an OnOutputFormat() message doesn't
+    // guarantee a subsequent packet, because for example the server could send
+    // OnOutputEndOfStream() or OnStreamFailed() instead.
+    //
+    // A client that does not wish to seamlessly handle mid-stream output format
+    // changes should either ensure that no stream processed by the client
+    // ever has any mid-stream format change, or the client should ensure that
+    // any retry of processing starts the new attempt at a point logically at or
+    // after the ponit where the old format has ended and the new format starts,
+    // else the client could just hit the same mid-stream format change again.
+    //
+    // An example of this message being sent mid-stream is mid-stream change
+    // of dimensions of video frames output from a video decoder.
+    //
+    // Not all servers will support seamless handling of format change.  Those
+    // that do support seamless handling of format change may require that the
+    // format change not also require output buffer re-config, in order for the
+    // handling to be seamless.  See the comment block for OnOutputConstraints() for
+    // more discussion of how servers and clients should behave - in particular
+    // when they don't seamlessly handle output constraint change and/or output
+    // format change.
+    //
+    // If this message isn't being sent by the server when expected at the
+    // start of a stream, the most common reason is that a previous
+    // OnOutputConstraints() with buffer_constraints_action_required true hasn't
+    // been processed by the client (by configuring output buffers using
+    // SetOutputBufferPartialSettings() etc).
+    -> OnOutputFormat(StreamOutputFormat output_format);
+
+    // DEPRECATED - this is temporarily still sent in addition to
+    // OnOutputConstraints and OnOutputConfig.  Soon this will be removed.
     -> OnOutputConfig(StreamOutputConfig output_config);
 
     // SetOutputBufferSettings() and AddOutputBuffer()
     //
-    // These are not permitted until after the first OnOutputConfig().
+    // These are not permitted until after the first OnOutputConstraints().
     //
-    // Roughly speaking, these messages are sent in response to OnOutputConfig()
+    // Roughly speaking, these messages are sent in response to OnOutputConstraints()
     // with buffer_constraints_action_required true.
     //
     // Configuring output buffers consists of calling SetOutputBufferSettings()
@@ -1041,7 +1320,7 @@
     // mode, this is the same as the number of packets.  In single-buffer mode,
     // this is 1.
     //
-    // Configuring output buffers is _required_ after OnOutputConfig() is
+    // Configuring output buffers is _required_ after OnOutputConstraints() is
     // received by the client with buffer_constraints_action_required true and
     // stream_lifetime_ordinal equal to the client's current
     // stream_lifetime_ordinal (even if there is an active stream), and is
@@ -1052,6 +1331,37 @@
     SetOutputBufferSettings(StreamBufferSettings output_settings);
     AddOutputBuffer(StreamBuffer buffer);
 
+    // SetOutputBufferPartialSettings()
+    //
+    // When the client is using sysmem to allocate buffers, this message is
+    // used instead of SetOutputBufferSettings()+AddOutputBuffer(). Instead, a
+    // single SetOutputBufferPartialSettings() provides the StreamProcessor
+    // with the client-specified output settings and a BufferCollectionToken
+    // which the StreamProcessor will use to convey constraints to sysmem.
+    // Both the client and the StreamProcessor will be informed of the
+    // allocated buffers directly by sysmem via their BufferCollection channel
+    // (not via the StreamProcessor channel).
+    //
+    // See also ClientReadyForOutput().
+    SetOutputBufferPartialSettings(StreamBufferPartialSettings output_settings);
+
+    // After SetOutputBufferPartialSettings(), the server won't send
+    // OnOutputConstraints(), OnOutputFormat(), OnOutputPacket(), or
+    // OnOutputEndOfStream() until after the client sends
+    // CompleteOutputBufferPartialSettings().
+    //
+    // Some clients may be able to send
+    // CompleteOutputBufferPartialSettings() immediately after
+    // SetOutputBufferPartialSettings() - in that case the client needs to be
+    // prepared to receive output without knowing the buffer count or packet
+    // count yet - such clients may internally delay processing the received
+    // output until the client has heard from sysmem (which is when the client
+    // will learn the buffer count and packet count).
+    //
+    // Other clients may first wait for sysmem to allocate, prepare to receive
+    // output, and then send CompleteOutputBufferPartialSettings().
+    CompleteOutputBufferPartialSettings(uint64 buffer_lifetime_ordinal);
+
     // FlushEndOfStreamAndCloseStream()
     //
     // This message is optional.
@@ -1090,7 +1400,7 @@
     //
     // Because the old stream is not done processing yet and the old stream's
     // data is not being discarded, the client must be prepared to continue to
-    // process OnOutputConfig() messages until the stream_lifetime_ordinal is
+    // process OnOutputConstraints() messages until the stream_lifetime_ordinal is
     // done.  The client will know the stream_lifetime_ordinal is done when
     // OnOutputEndOfStream(), OnStreamFailed(), or the StreamProcessor channel
     // closes.
@@ -1242,7 +1552,7 @@
     // the only ways that an OnOutputEndOfStream() won't happen after
     // QueueInputEndOfStream().
     //
-    // There will be no more OnOutputPacket() or OnOutputConfig() messages for
+    // There will be no more OnOutputPacket() or OnOutputConstraints() messages for
     // this stream_lifetime_ordinal after this message - if a server doesn't
     // follow this rule, a client should close the StreamProcessor channel.
     //
@@ -1258,6 +1568,9 @@
 
     // QueueInputFormatDetails()
     //
+    // TODO(dustingreen): Rename from QueueInputFormatDetails() to
+    // QueueInputFormat().
+    //
     // If the input format details are still the same as specified during
     // StreamProcessor creation, this message is unnecessary and does not need
     // to be sent.
@@ -1294,15 +1607,15 @@
     // If the stream doesn't exist yet, this message creates the new stream.
     //
     // The client is required to be willing to send QueueInputPacket() prior to
-    // the server's first OnOutputConfig(), and is permitted to start a new
+    // the server's first OnOutputConstraints(), and is permitted to start a new
     // stream without output buffers configured yet.
     //
     // The client must continue to deliver input data via this message even if
-    // the stream processor has not yet generated the first OnOutputConfig(),
+    // the stream processor has not yet generated the first OnOutputConstraints(),
     // and even if the StreamProcessor is generating OnFreeInputPacket() for
     // previously-queued input packets.  The input data must continue as long
     // as there are free packets to be assured that the server will ever
-    // generate the first OnOutputConfig().
+    // generate the first OnOutputConstraints().
     //
     // For more on QueueInputPacket(), see stream_processor.md.
     QueueInputPacket(Packet packet);
diff --git a/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api b/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
index 63bee83..0824158 100644
--- a/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
+++ b/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
@@ -2,11 +2,11 @@
   "fidl/fuchsia.sysmem/allocator.fidl": "6914234653a1699ba7d63a4e13a53d17",
   "fidl/fuchsia.sysmem/collection.fidl": "8f9c46318344c3b589266a97dc2786a4",
   "fidl/fuchsia.sysmem/collections_deprecated.fidl": "4953fa82beb051ff8e12be5df4e3c1a7",
-  "fidl/fuchsia.sysmem/constraints.fidl": "d1f97b74eca541caf5d585fc785ec321",
+  "fidl/fuchsia.sysmem/constraints.fidl": "92db2c9fbb47a53ab2af5a08167980ab",
   "fidl/fuchsia.sysmem/driver_connector.fidl": "d2818e5d7c2a0eae18fb006c8d802bf4",
   "fidl/fuchsia.sysmem/format_modifier.fidl": "72bd4509f27b9581544a9b3915240aac",
   "fidl/fuchsia.sysmem/formats_deprecated.fidl": "8c47d0b4664069b45b4af650e1089dde",
-  "fidl/fuchsia.sysmem/image_formats.fidl": "2e1325df30fa2a1f8e81f3e2d887a7c2",
+  "fidl/fuchsia.sysmem/image_formats.fidl": "802774efd1813df8fc72738e0c34199c",
   "fidl/fuchsia.sysmem/image_formats_deprecated.fidl": "0660023fbaed2b5abca9e5038d809df8",
-  "fidl/fuchsia.sysmem/usages.fidl": "3df34d5dc5e4e8d21df52f9eee8d1862"
+  "fidl/fuchsia.sysmem/usages.fidl": "64442fca34901fc3c7e45b5b640be209"
 }
\ No newline at end of file
diff --git a/src/media/playback/mediaplayer/fidl/fidl_decoder.cc b/src/media/playback/mediaplayer/fidl/fidl_decoder.cc
index dbfc9c7..2a9065f 100644
--- a/src/media/playback/mediaplayer/fidl/fidl_decoder.cc
+++ b/src/media/playback/mediaplayer/fidl/fidl_decoder.cc
@@ -129,8 +129,10 @@
       fit::bind_member(this, &FidlDecoder::OnStreamFailed);
   outboard_decoder_.events().OnInputConstraints =
       fit::bind_member(this, &FidlDecoder::OnInputConstraints);
-  outboard_decoder_.events().OnOutputConfig =
-      fit::bind_member(this, &FidlDecoder::OnOutputConfig);
+  outboard_decoder_.events().OnOutputConstraints =
+      fit::bind_member(this, &FidlDecoder::OnOutputConstraints);
+  outboard_decoder_.events().OnOutputFormat =
+      fit::bind_member(this, &FidlDecoder::OnOutputFormat);
   outboard_decoder_.events().OnOutputPacket =
       fit::bind_member(this, &FidlDecoder::OnOutputPacket);
   outboard_decoder_.events().OnOutputEndOfStream =
@@ -424,14 +426,6 @@
       return;
     }
 
-    if (!have_real_output_stream_type_) {
-      if (pre_stream_type_packet_requests_remaining_ != 0) {
-        --pre_stream_type_packet_requests_remaining_;
-      } else {
-        return;
-      }
-    }
-
     RequestInputPacket();
   }
 }
@@ -466,56 +460,24 @@
   InitSucceeded();
 }
 
-void FidlDecoder::OnOutputConfig(fuchsia::media::StreamOutputConfig config) {
+void FidlDecoder::OnOutputConstraints(
+    fuchsia::media::StreamOutputConstraints constraints) {
   FXL_DCHECK_CREATION_THREAD_IS_CURRENT(thread_checker_);
 
-  if (!config.has_format_details()) {
-    FXL_LOG(ERROR) << "Config has no format details.";
-    InitFailed();
-    return;
-  }
-
-  auto stream_type =
-      fidl::To<std::unique_ptr<StreamType>>(config.format_details());
-  if (!stream_type) {
-    FXL_LOG(ERROR) << "Can't comprehend format details.";
-    InitFailed();
-    return;
-  }
-
-  if (!config.format_details().has_format_details_version_ordinal()) {
-    FXL_LOG(ERROR) << "Format details do not have version ordinal.";
-    InitFailed();
-    return;
-  }
-
-  if (output_stream_type_) {
-    if (output_format_details_version_ordinal_ !=
-        config.format_details().format_details_version_ordinal()) {
-      HandlePossibleOutputStreamTypeChange(*output_stream_type_, *stream_type);
-    }
-  }
-
-  output_format_details_version_ordinal_ =
-      config.format_details().format_details_version_ordinal();
-
-  output_stream_type_ = std::move(stream_type);
-  have_real_output_stream_type_ = true;
-
-  if (config.has_buffer_constraints_action_required() &&
-      config.buffer_constraints_action_required() &&
-      !config.has_buffer_constraints()) {
-    FXL_LOG(ERROR) << "OnOutputConfig: constraints action required but "
+  if (constraints.has_buffer_constraints_action_required() &&
+      constraints.buffer_constraints_action_required() &&
+      !constraints.has_buffer_constraints()) {
+    FXL_LOG(ERROR) << "OnOutputConstraints: constraints action required but "
                       "constraints missing";
     InitFailed();
     return;
   }
 
-  if (!config.has_buffer_constraints_action_required() ||
-      !config.buffer_constraints_action_required()) {
+  if (!constraints.has_buffer_constraints_action_required() ||
+      !constraints.buffer_constraints_action_required()) {
     if (init_callback_) {
-      FXL_LOG(ERROR) << "OnOutputConfig: constraints action not required on "
-                        "initial config.";
+      FXL_LOG(ERROR) << "OnOutputConstraints: constraints action not required on "
+                        "initial constraints.";
       InitFailed();
       return;
     }
@@ -530,10 +492,10 @@
 
   // Use a single VMO for audio, VMO per buffer for video.
   const bool success = output_buffers_.ApplyConstraints(
-      config.buffer_constraints(),
+      constraints.buffer_constraints(),
       output_stream_type_->medium() == StreamType::Medium::kAudio);
   if (!success) {
-    FXL_LOG(ERROR) << "OnOutputConfig: Failed to apply constraints.";
+    FXL_LOG(ERROR) << "OnOutputConstraints: Failed to apply constraints.";
     InitFailed();
     return;
   }
@@ -544,9 +506,9 @@
   outboard_decoder_->SetOutputBufferSettings(
       fidl::Clone(current_set.settings()));
 
-  if (config.has_buffer_constraints() &&
-      (!config.buffer_constraints().has_per_packet_buffer_bytes_max() ||
-       config.buffer_constraints().per_packet_buffer_bytes_max() == 0)) {
+  if (constraints.has_buffer_constraints() &&
+      (!constraints.buffer_constraints().has_per_packet_buffer_bytes_max() ||
+       constraints.buffer_constraints().per_packet_buffer_bytes_max() == 0)) {
     FXL_LOG(ERROR) << "Buffer constraints are missing non-zero per packet "
                       "buffer bytes max";
     InitFailed();
@@ -555,9 +517,44 @@
 
   // Create the VMOs when we're ready, and add them to the outboard decoder.
   // Mutable so we can move the vmo handle out.
-  MaybeConfigureOutput(config.mutable_buffer_constraints());
+  MaybeConfigureOutput(constraints.mutable_buffer_constraints());
 };  // namespace media_player
 
+void FidlDecoder::OnOutputFormat(fuchsia::media::StreamOutputFormat format) {
+  if (!format.has_format_details()) {
+    FXL_LOG(ERROR) << "Config has no format details.";
+    InitFailed();
+    return;
+  }
+
+  auto stream_type =
+      fidl::To<std::unique_ptr<StreamType>>(format.format_details());
+  if (!stream_type) {
+    FXL_LOG(ERROR) << "Can't comprehend format details.";
+    InitFailed();
+    return;
+  }
+
+  if (!format.format_details().has_format_details_version_ordinal()) {
+    FXL_LOG(ERROR) << "Format details do not have version ordinal.";
+    InitFailed();
+    return;
+  }
+
+  if (output_stream_type_) {
+    if (output_format_details_version_ordinal_ !=
+        format.format_details().format_details_version_ordinal()) {
+      HandlePossibleOutputStreamTypeChange(*output_stream_type_, *stream_type);
+    }
+  }
+
+  output_format_details_version_ordinal_ =
+      format.format_details().format_details_version_ordinal();
+
+  output_stream_type_ = std::move(stream_type);
+  have_real_output_stream_type_ = true;
+}
+
 void FidlDecoder::OnOutputPacket(fuchsia::media::Packet packet,
                                  bool error_detected_before,
                                  bool error_detected_during) {
@@ -585,7 +582,12 @@
   }
 
   if (!output_buffers_.has_current_set()) {
-    FXL_LOG(FATAL) << "OnOutputPacket event without prior OnOutputConfig event";
+    FXL_LOG(FATAL) << "OnOutputPacket event without prior OnOutputConstraints event";
+    // TODO(dalesat): Report error rather than crashing.
+  }
+
+  if (!have_real_output_stream_type_) {
+    FXL_LOG(FATAL) << "OnOutputPacket event without prior OnOutputFormat event";
     // TODO(dalesat): Report error rather than crashing.
   }
 
diff --git a/src/media/playback/mediaplayer/fidl/fidl_decoder.h b/src/media/playback/mediaplayer/fidl/fidl_decoder.h
index 7d1f7e0..97c7fe97 100644
--- a/src/media/playback/mediaplayer/fidl/fidl_decoder.h
+++ b/src/media/playback/mediaplayer/fidl/fidl_decoder.h
@@ -76,7 +76,7 @@
   void AddInputBuffers();
 
   // Configures the output as appropriate. |ConfigureConnectors| calls this as
-  // does |OnOutputConfig|. If |constraints| is supplied, this method will
+  // does |OnOutputConstraints|. If |constraints| is supplied, this method will
   // either cache the value (if the node isn't ready) or use it to configure
   // the output (if the node is ready). If |constraints| is null, there are
   // cached constraints and the node is ready, this method will configure the
@@ -101,8 +101,11 @@
   // |ConfigureConnectors| is called.
   void OnInputConstraints(fuchsia::media::StreamBufferConstraints constraints);
 
-  // Handles the |OnOutputConfig| event from the outboard decoder.
-  void OnOutputConfig(fuchsia::media::StreamOutputConfig config);
+  // Handles the |OnOutputConstraints| event from the outboard decoder.
+  void OnOutputConstraints(fuchsia::media::StreamOutputConstraints config);
+
+  // Handles the |OnOutputFormat| event from the outboard decoder.
+  void OnOutputFormat(fuchsia::media::StreamOutputFormat format);
 
   // Handles the |OnOutputPacket| event from the outboard decoder.
   void OnOutputPacket(fuchsia::media::Packet output_packet,
@@ -127,7 +130,6 @@
   fuchsia::media::FormatDetails input_format_details_;
   fit::function<void(bool)> init_callback_;
   bool have_real_output_stream_type_ = false;
-  uint32_t pre_stream_type_packet_requests_remaining_ = 10;
   std::unique_ptr<StreamType> output_stream_type_;
   std::unique_ptr<StreamType> revised_output_stream_type_;
   bool add_input_buffers_pending_ = false;
diff --git a/zircon/system/banjo/ddk-protocol-sysmem/sysmem.banjo b/zircon/system/banjo/ddk-protocol-sysmem/sysmem.banjo
index 8db1f65..b2f441d 100644
--- a/zircon/system/banjo/ddk-protocol-sysmem/sysmem.banjo
+++ b/zircon/system/banjo/ddk-protocol-sysmem/sysmem.banjo
@@ -9,7 +9,7 @@
 [Layout = "ddk-protocol"]
 protocol Sysmem {
     /// Takes the server end of a FIDL connection that'll serve
-    /// fuchsia.sysmem.Allocator2.  If the connection fails, the channel will
+    /// fuchsia.sysmem.Allocator.  If the connection fails, the channel will
     /// close.
-    Connect(handle<channel> allocator2_request) -> (zx.status s);
+    Connect(handle<channel> allocator_request) -> (zx.status s);
 };
diff --git a/zircon/system/core/svchost/sysmem.cpp b/zircon/system/core/svchost/sysmem.cpp
index 36e8d5b..e1a45bc 100644
--- a/zircon/system/core/svchost/sysmem.cpp
+++ b/zircon/system/core/svchost/sysmem.cpp
@@ -20,11 +20,11 @@
 
 static zx_status_t sysmem2_connect(
     void* ctx, async_dispatcher_t* dispatcher, const char* service_name,
-    zx_handle_t allocator2_request_param) {
-    zx::channel allocator2_request(allocator2_request_param);
+    zx_handle_t allocator_request_param) {
+    zx::channel allocator_request(allocator_request_param);
     sysmem_connector_t* connector = static_cast<sysmem_connector_t*>(ctx);
     if (!strcmp(service_name, fuchsia_sysmem_Allocator_Name)) {
-        sysmem_connector_queue_connection_request(connector, allocator2_request.release());
+        sysmem_connector_queue_connection_request(connector, allocator_request.release());
     }
     return ZX_ERR_NOT_SUPPORTED;
 }
diff --git a/zircon/system/dev/bus/platform/platform-proxy-device.cpp b/zircon/system/dev/bus/platform/platform-proxy-device.cpp
index f9826c5..1b4fe72 100644
--- a/zircon/system/dev/bus/platform/platform-proxy-device.cpp
+++ b/zircon/system/dev/bus/platform/platform-proxy-device.cpp
@@ -251,12 +251,12 @@
     return proxy_->Rpc(device_id_, &req.header, sizeof(req), &resp, sizeof(resp));
 }
 
-zx_status_t ProxySysmem::SysmemConnect(zx::channel allocator2_request) {
+zx_status_t ProxySysmem::SysmemConnect(zx::channel allocator_request) {
     platform_proxy_req_t req = {};
     platform_proxy_rsp_t resp = {};
     req.proto_id = ZX_PROTOCOL_SYSMEM;
     req.op = SYSMEM_CONNECT;
-    zx_handle_t handle = allocator2_request.release();
+    zx_handle_t handle = allocator_request.release();
 
     return proxy_->Rpc(device_id_, &req, sizeof(req), &resp, sizeof(resp), &handle, 1, nullptr, 0,
                        nullptr);
diff --git a/zircon/system/dev/display/display/client.cpp b/zircon/system/dev/display/display/client.cpp
index 3b65ea7..47249f7 100644
--- a/zircon/system/dev/display/display/client.cpp
+++ b/zircon/system/dev/display/display/client.cpp
@@ -1789,6 +1789,8 @@
     if (status != ZX_OK) {
         // Not a fatal error, but BufferCollection functions won't work.
         // TODO(ZX-3355) TODO: Fail creation once all drivers implement this.
+        zxlogf(ERROR, "GetSysmemConnection failed (continuing) - status: %d\n",
+               status);
         sysmem_allocator_.reset();
     }
 
diff --git a/zircon/system/dev/display/vim-display/vim-display.cpp b/zircon/system/dev/display/vim-display/vim-display.cpp
index 67b4828..ea0ade2 100644
--- a/zircon/system/dev/display/vim-display/vim-display.cpp
+++ b/zircon/system/dev/display/vim-display/vim-display.cpp
@@ -475,7 +475,7 @@
 
     zx_status_t status = sysmem_connect(&display->sysmem, request_handle.release());
     if (status != ZX_OK) {
-        DISP_ERROR("Could not connect to sysmem\n");
+        DISP_ERROR("Could not connect to sysmem - status: %d\n", status);
         return status;
     }
 
diff --git a/zircon/system/dev/sysmem/sysmem/buffer_collection.cpp b/zircon/system/dev/sysmem/sysmem/buffer_collection.cpp
index 57cdb42..7190b28 100644
--- a/zircon/system/dev/sysmem/sysmem/buffer_collection.cpp
+++ b/zircon/system/dev/sysmem/sysmem/buffer_collection.cpp
@@ -59,7 +59,13 @@
 };
 
 BufferCollection::~BufferCollection() {
-    // nothing else to do here
+    // Close() the SimpleBinding<> before deleting the list of pending Txn(s),
+    // so that ~Txn doesn't complain about being deleted without being
+    // completed.
+    //
+    // Don't run the error handler; if any error handler remains it'll just get
+    // deleted here.
+    (void)binding_.Close();
 }
 
 zx_status_t BufferCollection::SetEventSink(
diff --git a/zircon/system/dev/sysmem/sysmem/device.cpp b/zircon/system/dev/sysmem/sysmem/device.cpp
index d5f494b..3dc4154 100644
--- a/zircon/system/dev/sysmem/sysmem/device.cpp
+++ b/zircon/system/dev/sysmem/sysmem/device.cpp
@@ -39,9 +39,9 @@
 }();
 
 zx_status_t in_proc_sysmem_Connect(void* ctx,
-                                   zx_handle_t allocator2_request_param) {
+                                   zx_handle_t allocator_request_param) {
     Device* self = static_cast<Device*>(ctx);
-    return self->Connect(allocator2_request_param);
+    return self->Connect(allocator_request_param);
 }
 
 // In-proc sysmem interface.  Essentially an in-proc version of
diff --git a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
index 034d482..d5e64cd 100644
--- a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
+++ b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
@@ -11,6 +11,7 @@
 
 #include <lib/image-format/image_format.h>
 #include <limits.h> // PAGE_SIZE
+#include <limits> // std::numeric_limits
 #include <zircon/assert.h>
 
 namespace {
@@ -36,11 +37,22 @@
 // TODO(dustingreen): Switch to FIDL C++ generated code (preferred) and remove
 // this, or fully implement something like this for all fields that need 0 to
 // imply a default value that isn't 0.
-template <typename T> T FieldDefault1(T value) {
-    if (value == 0) {
-        return 1;
+template <typename T> void FieldDefault1(T* value) {
+    if (*value == 0) {
+        *value = 1;
     }
-    return value;
+}
+
+template <typename T> void FieldDefaultMax(T* value) {
+    if (*value == 0) {
+        *value = std::numeric_limits<T>::max();
+    }
+}
+
+// This exists just to document the meaning for now, to make the conversion more
+// clear when we switch from FIDL struct to FIDL table.
+template <typename T> void FieldDefaultZero(T* value) {
+    // no-op
 }
 
 template <typename T> T AlignUp(T value, T divisor) {
@@ -488,7 +500,7 @@
         return false;
     }
 
-    if (!CheckBufferCollectionConstraints(iter->get())) {
+    if (!CheckSanitizeBufferCollectionConstraints(iter->get())) {
         return false;
     }
 
@@ -500,7 +512,7 @@
         if (!iter->get()) {
             continue;
         }
-        if (!CheckBufferCollectionConstraints(iter->get())) {
+        if (!CheckSanitizeBufferCollectionConstraints(iter->get())) {
             return false;
         }
         if (!AccumulateConstraintBufferCollection(result.get(),
@@ -511,14 +523,17 @@
         }
     }
 
-    SanitizeBufferCollectionConstraints(result.get());
+    if (!CheckSanitizeBufferCollectionConstraints(result.get())) {
+      return false;
+    }
 
     constraints_ = std::move(result);
     return true;
 }
 
-bool LogicalBufferCollection::CheckBufferCollectionConstraints(
-    const fuchsia_sysmem_BufferCollectionConstraints* constraints) {
+bool LogicalBufferCollection::CheckSanitizeBufferCollectionConstraints(
+    fuchsia_sysmem_BufferCollectionConstraints* constraints) {
+    FieldDefaultMax(&constraints->max_buffer_count);
     // At least one usage bit must be specified by any participant that
     // specifies constraints.
     if (constraints->usage.cpu == 0 && constraints->usage.vulkan == 0 &&
@@ -527,7 +542,7 @@
         return false;
     }
     for (uint32_t i = 0; i < constraints->image_format_constraints_count; ++i) {
-        if (!CheckImageFormatConstraints(
+        if (!CheckSanitizeImageFormatConstraints(
                 &constraints->image_format_constraints[i])) {
             return false;
         }
@@ -535,8 +550,8 @@
     return true;
 }
 
-bool LogicalBufferCollection::CheckImageFormatConstraints(
-    const fuchsia_sysmem_ImageFormatConstraints* constraints) {
+bool LogicalBufferCollection::CheckSanitizeImageFormatConstraints(
+    fuchsia_sysmem_ImageFormatConstraints* constraints) {
     if (constraints->pixel_format.type ==
         fuchsia_sysmem_PixelFormatType_INVALID) {
         LogError("PixelFormatType INVALID not allowed");
@@ -556,6 +571,13 @@
         return false;
     }
 
+    FieldDefault1(&constraints->coded_width_divisor);
+    FieldDefault1(&constraints->coded_height_divisor);
+    FieldDefault1(&constraints->bytes_per_row_divisor);
+    FieldDefault1(&constraints->start_offset_divisor);
+    FieldDefault1(&constraints->display_width_divisor);
+    FieldDefault1(&constraints->display_height_divisor);
+
     if (!IsNonZeroPowerOf2(constraints->coded_width_divisor)) {
         LogError("non-power-of-2 coded_width_divisor not supported");
         return false;
@@ -588,6 +610,21 @@
             return false;
         }
     }
+
+    FieldDefaultMax(&constraints->required_min_coded_width);
+    FieldDefaultZero(&constraints->required_max_coded_width);
+    FieldDefaultMax(&constraints->required_min_coded_height);
+    FieldDefaultZero(&constraints->required_max_coded_height);
+    FieldDefaultMax(&constraints->required_min_bytes_per_row);
+    FieldDefaultZero(&constraints->required_max_bytes_per_row);
+
+    uint32_t min_bytes_per_row_given_min_width =
+        ImageFormatStrideBytesPerWidthPixel(&constraints->pixel_format) *
+                                            constraints->min_coded_width;
+    constraints->min_bytes_per_row = std::max(
+        constraints->min_bytes_per_row,
+        min_bytes_per_row_given_min_width);
+
     // TODO(dustingreen): Check compatibility of color_space[] entries vs. the
     // pixel_format.  In particular, 2020 and 2100 don't have 8 bpp, only 10 or
     // 12 bpp, while a given PixelFormat.type is a specific bpp.  There's
@@ -597,22 +634,6 @@
     return true;
 }
 
-void LogicalBufferCollection::SanitizeBufferCollectionConstraints(
-    fuchsia_sysmem_BufferCollectionConstraints* constraints) {
-    for (uint32_t i = 0; i < constraints->image_format_constraints_count; ++i) {
-        // Ensure min_bytes_per_row is sufficient to fit min_coded_width.
-        fuchsia_sysmem_ImageFormatConstraints* image_format_constraints =
-            &constraints->image_format_constraints[i];
-        uint32_t min_bytes_per_row_given_min_width =
-            ImageFormatStrideBytesPerWidthPixel(
-                &image_format_constraints->pixel_format) *
-            image_format_constraints->min_coded_width;
-        image_format_constraints->min_bytes_per_row =
-            std::max(image_format_constraints->min_bytes_per_row,
-                     min_bytes_per_row_given_min_width);
-    }
-}
-
 LogicalBufferCollection::Constraints
 LogicalBufferCollection::BufferCollectionConstraintsClone(
     const fuchsia_sysmem_BufferCollectionConstraints* input) {
@@ -647,6 +668,13 @@
         std::max(acc->min_buffer_count_for_shared_slack,
                  c->min_buffer_count_for_shared_slack);
 
+    // 0 is replaced with 0xFFFFFFFF in
+    // CheckSanitizeBufferCollectionConstraints.
+    ZX_DEBUG_ASSERT(acc->max_buffer_count != 0);
+    ZX_DEBUG_ASSERT(c->max_buffer_count != 0);
+    acc->max_buffer_count = std::min(
+        acc->max_buffer_count, c->max_buffer_count);
+
     if (!acc->has_buffer_memory_constraints) {
         if (c->has_buffer_memory_constraints) {
             // struct copy
@@ -718,6 +746,13 @@
     fuchsia_sysmem_BufferMemoryConstraints* acc,
     const fuchsia_sysmem_BufferMemoryConstraints* c) {
     acc->min_size_bytes = std::max(acc->min_size_bytes, c->min_size_bytes);
+    // Don't permit 0 as the overall min_size_bytes; that would be nonsense.  No
+    // particular initiator should feel that it has to specify 1 in this field;
+    // that's just built into sysmem instead.  While a VMO will have a minimum
+    // actual size of page size, we do permit treating buffers as if they're 1
+    // byte, mainly for testing reasons, and to avoid any unnecessary dependence
+    // or assumptions re. page size.
+    acc->min_size_bytes = std::max(acc->min_size_bytes, 1u);
     acc->max_size_bytes = std::min(acc->max_size_bytes, c->max_size_bytes);
     if (acc->min_size_bytes > acc->max_size_bytes) {
         LogError("min_size_bytes > max_size_bytes");
@@ -882,6 +917,48 @@
     acc->display_height_divisor =
         std::max(acc->display_height_divisor, c->display_height_divisor);
 
+    // The required_ space is accumulated by taking the union, and must be fully
+    // within the non-required_ space, else fail.  For example, this allows a
+    // video decoder to indicate that it's capable of outputting a wide range of
+    // output dimensions, but that it has specific current dimensions that are
+    // presently required_ (min == max) for decode to proceed.
+    ZX_DEBUG_ASSERT(acc->required_min_coded_width != 0);
+    ZX_DEBUG_ASSERT(c->required_min_coded_width != 0);
+    acc->required_min_coded_width = std::min(acc->required_min_coded_width, c->required_min_coded_width);
+    if (acc->required_min_coded_width < acc->min_coded_width) {
+        LogError("required_min_coded_width < min_coded_width");
+        return false;
+    }
+    acc->required_max_coded_width = std::max(acc->required_max_coded_width, c->required_max_coded_width);
+    if (acc->required_max_coded_width > acc->max_coded_width) {
+        LogError("required_max_coded_width > max_coded_width");
+        return false;
+    }
+    ZX_DEBUG_ASSERT(acc->required_min_coded_height != 0);
+    ZX_DEBUG_ASSERT(c->required_min_coded_height != 0);
+    acc->required_min_coded_height = std::min(acc->required_min_coded_height, c->required_min_coded_height);
+    if (acc->required_min_coded_height < acc->min_coded_height) {
+        LogError("required_min_coded_height < min_coded_height");
+        return false;
+    }
+    acc->required_max_coded_height = std::max(acc->required_max_coded_height, c->required_max_coded_height);
+    if (acc->required_max_coded_height > acc->max_coded_height) {
+        LogError("required_max_coded_height > max_coded_height");
+        return false;
+    }
+    ZX_DEBUG_ASSERT(acc->required_min_bytes_per_row != 0);
+    ZX_DEBUG_ASSERT(c->required_min_bytes_per_row != 0);
+    acc->required_min_bytes_per_row = std::min(acc->required_min_bytes_per_row, c->required_min_bytes_per_row);
+    if (acc->required_min_bytes_per_row < acc->min_bytes_per_row) {
+        LogError("required_min_bytes_per_row < min_bytes_per_row");
+        return false;
+    }
+    acc->required_max_bytes_per_row = std::max(acc->required_max_bytes_per_row, c->required_max_bytes_per_row);
+    if (acc->required_max_bytes_per_row > acc->max_bytes_per_row) {
+        LogError("required_max_bytes_per_row > max_bytes_per_row");
+        return false;
+    }
+
     return true;
 }
 
@@ -952,9 +1029,21 @@
     BufferCollection::BufferCollectionInfo result(
         BufferCollection::BufferCollectionInfo::Default);
 
-    result->buffer_count = constraints_->min_buffer_count_for_camping +
-                           constraints_->min_buffer_count_for_dedicated_slack +
-                           constraints_->min_buffer_count_for_shared_slack;
+    uint32_t min_buffer_count =
+        constraints_->min_buffer_count_for_camping +
+        constraints_->min_buffer_count_for_dedicated_slack +
+        constraints_->min_buffer_count_for_shared_slack;
+    uint32_t max_buffer_count = constraints_->max_buffer_count;
+    if (min_buffer_count > max_buffer_count) {
+      LogError("aggregate min_buffer_count > aggregate max_buffer_count - "
+               "min: %u max: %u", min_buffer_count, max_buffer_count);
+      *allocation_result = ZX_ERR_NOT_SUPPORTED;
+      return BufferCollection::BufferCollectionInfo(
+          BufferCollection::BufferCollectionInfo::Null);
+    }
+    result->buffer_count = min_buffer_count;
+    ZX_DEBUG_ASSERT(result->buffer_count <= max_buffer_count);
+
     uint64_t min_size_bytes = 0;
     uint64_t max_size_bytes = std::numeric_limits<uint64_t>::max();
 
@@ -1033,7 +1122,7 @@
 
         min_image.coded_width =
             AlignUp(constraints->min_coded_width,
-                    FieldDefault1(constraints->coded_width_divisor));
+                    constraints->coded_width_divisor);
         if (min_image.coded_width > constraints->max_coded_width) {
             LogError(
                 "coded_width_divisor caused coded_width > max_coded_width");
@@ -1043,7 +1132,7 @@
         }
         min_image.coded_height =
             AlignUp(constraints->min_coded_height,
-                    FieldDefault1(constraints->coded_height_divisor));
+                    constraints->coded_height_divisor);
         if (min_image.coded_height > constraints->max_coded_height) {
             LogError(
                 "coded_height_divisor caused coded_height > max_coded_height");
@@ -1053,7 +1142,7 @@
         }
         min_image.bytes_per_row =
             AlignUp(constraints->min_bytes_per_row,
-                    FieldDefault1(constraints->bytes_per_row_divisor));
+                    constraints->bytes_per_row_divisor);
         if (min_image.bytes_per_row > constraints->max_bytes_per_row) {
             LogError("bytes_per_row_divisor caused bytes_per_row > "
                      "max_bytes_per_row");
@@ -1130,6 +1219,11 @@
 
     // Now that min_size_bytes accounts for any ImageFormatConstraints, we can
     // just allocate min_size_bytes buffers.
+    //
+    // If an initiator (or a participant) wants to force buffers to be larger
+    // than the size implied by minimum image dimensions, the initiator can use
+    // BufferMemorySettings.min_size_bytes to force allocated buffers to be
+    // large enough.
     buffer_settings->size_bytes = static_cast<uint32_t>(min_size_bytes);
 
     for (uint32_t i = 0; i < result->buffer_count; ++i) {
diff --git a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
index 237a71e..dcd9065 100644
--- a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
+++ b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
@@ -101,15 +101,12 @@
 
     bool CombineConstraints();
 
-    bool CheckBufferCollectionConstraints(
-        const fuchsia_sysmem_BufferCollectionConstraints* constraints);
-
-    bool CheckImageFormatConstraints(
-        const fuchsia_sysmem_ImageFormatConstraints* constraints);
-
-    void SanitizeBufferCollectionConstraints(
+    bool CheckSanitizeBufferCollectionConstraints(
         fuchsia_sysmem_BufferCollectionConstraints* constraints);
 
+    bool CheckSanitizeImageFormatConstraints(
+        fuchsia_sysmem_ImageFormatConstraints* constraints);
+
     Constraints BufferCollectionConstraintsClone(
         const fuchsia_sysmem_BufferCollectionConstraints* input);
 
diff --git a/zircon/system/fidl/fuchsia-sysmem/constraints.fidl b/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
index 9c1e8b8..c18b0f9 100644
--- a/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
+++ b/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
@@ -77,6 +77,9 @@
     /// accounted for in min_buffer_count_for_camping.
     uint32 min_buffer_count_for_shared_slack;
 
+    // 0 is treated as 0xFFFFFFFF.
+    uint32 max_buffer_count;
+
     /// Constraints on BufferCollectionSettings.buffer_settings.
     ///
     /// A participant that intends to specify image_format_constraints_count > 1
@@ -142,7 +145,7 @@
 
 struct SingleBufferInfo {
     SingleBufferSettings settings;
-    handle<vmo>? vmo;
+    VmoBuffer buffer;
 };
 
 // After the initial buffer allocation, it's allowed to close old buffers and
@@ -218,8 +221,16 @@
     uint32 color_spaces_count;
     array<ColorSpace>:32 color_space;
 
-    /// Minimum width in pixels.  For example a video decoder participant may
-    /// set this field to the coded_width specified by a stream.
+    /// Minimum permitted width in pixels.
+    ///
+    /// For example a video decoder participant may set this field to the
+    /// minimum coded_width that might potentially be specified by a stream.  In
+    /// contrast, required_min_coded_width would be set to the current
+    /// coded_width specified by the stream.  While min_coded_width aggregates
+    /// by taking the max, required_min_coded_width aggregates by taking the
+    /// min.
+    ///
+    /// See also required_min_coded_width.
     uint32 min_coded_width;
     /// Maximum width in pixels.  For example Scenic may set this field
     /// (directly or via sub-participants) to the maximum width that can be
@@ -265,6 +276,39 @@
 
     /// display_height % display_height_divisor must be 0.
     uint32 display_height_divisor = 1;
+
+    /// required_ dimension bounds.
+    ///
+    /// In contrast to the corresponding fields without "required_" at the
+    /// start, these fields (when set to non-zero values) express a requirement
+    /// that the resulting aggregated non-required_ fields specify a space that
+    /// fully contain the space expressed by each participant's required_
+    /// fields.
+    ///
+    /// For example, a producer video decoder is perfectly happy for the
+    /// consumer to be willing to accept anything, and the video decoder doesn't
+    /// really want to constrain the potential space of dimensions that might be
+    /// seen in a stream and may be acceptable to the consumer, but the video
+    /// decoder needs to ensure that the resulting dimension ranges contain
+    /// at least the current dimensions decoded from the stream.
+    ///
+    /// Similarly, an initiator with a particular dynamic-dimension scenario in
+    /// mind may wish to require up front that participants agree to handle at
+    /// least the range of dimensions expected by the initiator in that
+    /// scenario (else fail earlier rather than later, maybe trying again with
+    /// smaller required_ space).
+    ///
+    /// It's much more common for a producer or initiator to set these fields
+    /// than for a consumer to set these fields.
+    ///
+    /// While the non-required_ fields aggregate by taking the intersection, the
+    /// required_ fields aggregate by taking the union.
+    uint32 required_min_coded_width;
+    uint32 required_max_coded_width;
+    uint32 required_min_coded_height;
+    uint32 required_max_coded_height;
+    uint32 required_min_bytes_per_row;
+    uint32 required_max_bytes_per_row;
 };
 
 /// Describes how an image is represented.
@@ -297,4 +341,23 @@
 
     /// Color space.
     ColorSpace color_space;
+
+    // The pixel_aspect_ratio_width : pixel_aspect_ratio_height is the
+    // pixel aspect ratio (AKA sample aspect ratio aka SAR) for the luma
+    // (AKA Y) samples. A pixel_aspect_ratio of 1:1 mean square pixels. A
+    // pixel_aspect_ratio of 2:1 would mean pixels that are displayed twice
+    // as wide as they are tall. Codec implementation should ensure these
+    // two values are relatively prime by reducing the fraction (dividing
+    // both by GCF) if necessary.
+    //
+    // When has_pixel_aspect_ratio == false, pixel_aspect_ratio_width and
+    // pixel_aspect_ratio_height will both be 1, but in that case the
+    // pixel_aspect_ratio_width : pixel_aspect_ratio_height of 1:1 is just
+    // a very weak suggestion re. reasonable-ish handling, not in any way
+    // authoritative. In this case (or in any case really) the receiver of
+    // this message may have other OOB means to determine the actual
+    // pixel_aspect_ratio.
+    bool has_pixel_aspect_ratio = false;
+    uint32 pixel_aspect_ratio_width = 1;
+    uint32 pixel_aspect_ratio_height = 1;
 };
diff --git a/zircon/system/fidl/fuchsia-sysmem/image_formats.fidl b/zircon/system/fidl/fuchsia-sysmem/image_formats.fidl
index 84cff51..83414aa 100644
--- a/zircon/system/fidl/fuchsia-sysmem/image_formats.fidl
+++ b/zircon/system/fidl/fuchsia-sysmem/image_formats.fidl
@@ -50,6 +50,9 @@
     // for this is (including any variants that are specified / permitted /
     // indicated in-band / prohibited).
     MJPEG = 106; // For UVC compliance.
+
+    // YUV only, 8 bits per Y sample
+    YV12 = 107;
 };
 
 // Describes how the pixels within an image are meant to be presented.
diff --git a/zircon/system/fidl/fuchsia-sysmem/usages.fidl b/zircon/system/fidl/fuchsia-sysmem/usages.fidl
index ef5fdf19d..e23a1cd 100644
--- a/zircon/system/fidl/fuchsia-sysmem/usages.fidl
+++ b/zircon/system/fidl/fuchsia-sysmem/usages.fidl
@@ -39,4 +39,6 @@
 // TODO(ZX-2259): Add more specific HwDecoder flags if needed.
 const uint32 videoUsageHwDecoder = 1;
 const uint32 videoUsageHwEncoder = 2;
+// TODO(dustingreen): This bit is likely redundant with secure_required and
+// secure_permitted bool(s); this bit might be able to be removed.
 const uint32 videoUsageHwProtected = 4;
diff --git a/zircon/system/ulib/image-format/image_format.cpp b/zircon/system/ulib/image-format/image_format.cpp
index 2659efa..edea916 100644
--- a/zircon/system/ulib/image-format/image_format.cpp
+++ b/zircon/system/ulib/image-format/image_format.cpp
@@ -53,6 +53,7 @@
     // 8 bits RGB when uncompressed - in this context, MJPEG is essentially
     // pretending to be uncompressed.
     {fuchsia_sysmem_PixelFormatType_MJPEG, {{8}, kColorType_RGB}},
+    {fuchsia_sysmem_PixelFormatType_YV12, {{8}, kColorType_YUV}},
 };
 
 class ImageFormatSet {
@@ -182,6 +183,7 @@
         case fuchsia_sysmem_PixelFormatType_M420:
         case fuchsia_sysmem_PixelFormatType_NV12:
         case fuchsia_sysmem_PixelFormatType_YUY2:
+        case fuchsia_sysmem_PixelFormatType_YV12:
             return true;
         }
         return false;
@@ -203,6 +205,8 @@
             return coded_height * bytes_per_row * 3 / 2;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return coded_height * bytes_per_row;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return coded_height * bytes_per_row * 3 / 2;
         default:
             return 0u;
         }
@@ -287,6 +291,8 @@
             return 12u;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return 2u * 8u;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return 12u;
     }
     ZX_PANIC("Unknown Pixel Format: %d", static_cast<int>(pixel_format->type));
     return 0u;
@@ -313,6 +319,8 @@
             return 1u;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return 2u;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return 1u;
     }
     ZX_PANIC("Unknown Pixel Format: %d", static_cast<int>(pixel_format->type));
     return 0u;
@@ -348,6 +356,8 @@
             return 2u;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return 2u;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return 2u;
     }
     ZX_PANIC("Unknown Pixel Format: %d", static_cast<int>(pixel_format->type));
     return 0u;
@@ -374,6 +384,8 @@
             return 2u;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return 2u;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return 2u;
     }
     ZX_PANIC("Unknown Pixel Format: %d", static_cast<int>(pixel_format->type));
     return 0u;
@@ -400,6 +412,8 @@
             return 2u;
         case fuchsia_sysmem_PixelFormatType_YUY2:
             return 2u;
+        case fuchsia_sysmem_PixelFormatType_YV12:
+            return 2u;
     }
     ZX_PANIC("Unknown Pixel Format: %d", static_cast<int>(pixel_format->type));
     return 0u;