[camera] refactor manual streaming test

This change makes a number of improvements to the camera streaming test
in preparation for adding other stream sources to the test (e.g. HAL).
Specifically, it moves everything to the latest camera2 fidl interface,
and moves the creation of streams to its own class.

Test: manually ran the test on sherlock and verified it behaves the
      same as before
Change-Id: I3c6721d481121288d295607f0bcaa03168c09eb7
diff --git a/src/camera/examples/demo/BUILD.gn b/src/camera/examples/demo/BUILD.gn
index e97db51..fffcfe8 100644
--- a/src/camera/examples/demo/BUILD.gn
+++ b/src/camera/examples/demo/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//build/package.gni")
-import("//src/graphics/lib/vulkan/layers.gni")
 
 demo_name = "camera_demo"
 
@@ -15,17 +14,17 @@
   ]
 
   deps = [
+    "./stream_provider",
     "//garnet/public/lib/fsl",
     "//garnet/public/lib/ui/base_view/cpp",
     "//garnet/public/lib/ui/scenic/cpp",
+    "//sdk/fidl/fuchsia.camera2",
     "//sdk/fidl/fuchsia.ui.gfx",
     "//sdk/lib/fidl/cpp",
     "//src/lib/fxl",
     "//src/lib/ui/scenic/cpp",
     "//src/ui/lib/glm_workaround",
     "//zircon/public/lib/async-loop-cpp",
-    "//zircon/system/fidl/fuchsia-camera-common",
-    "//zircon/system/fidl/fuchsia-camera-test:fuchsia-camera-test_c",
   ]
 }
 
diff --git a/src/camera/examples/demo/main.cc b/src/camera/examples/demo/main.cc
index fab0fcc..69c090f 100644
--- a/src/camera/examples/demo/main.cc
+++ b/src/camera/examples/demo/main.cc
@@ -8,8 +8,6 @@
 
 #include <dirent.h>
 #include <fcntl.h>
-#include <fuchsia/camera/common/cpp/fidl.h>
-#include <fuchsia/camera/test/c/fidl.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/component/cpp/startup_context.h>
 #include <lib/fdio/fdio.h>
@@ -18,6 +16,7 @@
 #include <lib/ui/scenic/cpp/commands.h>
 #include <lib/ui/scenic/cpp/resources.h>
 #include <lib/zx/time.h>
+#include <stream_provider.h>
 #include <sys/types.h>
 
 #include <queue>
@@ -34,23 +33,21 @@
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtx/quaternion.hpp>
 
-static constexpr const char* kDevicePath = "/dev/class/isp-device-test/000";
-
 static const uint32_t kChaosMaxSleepMsec = 2000;
 static const uint32_t kChaosMeanSleepMsec = 50;
 
-static fuchsia::images::PixelFormat convertFormat(fuchsia_sysmem_PixelFormatType type) {
+static fuchsia::images::PixelFormat convertFormat(fuchsia::sysmem::PixelFormatType type) {
   switch (type) {
-    case fuchsia_sysmem_PixelFormatType_BGRA32:
+    case fuchsia::sysmem::PixelFormatType::BGRA32:
       return fuchsia::images::PixelFormat::BGRA_8;
-    case fuchsia_sysmem_PixelFormatType_YUY2:
+    case fuchsia::sysmem::PixelFormatType::YUY2:
       return fuchsia::images::PixelFormat::YUY2;
-    case fuchsia_sysmem_PixelFormatType_NV12:
+    case fuchsia::sysmem::PixelFormatType::NV12:
       return fuchsia::images::PixelFormat::NV12;
-    case fuchsia_sysmem_PixelFormatType_YV12:
+    case fuchsia::sysmem::PixelFormatType::YV12:
       return fuchsia::images::PixelFormat::YV12;
     default:
-      FXL_LOG(ERROR) << "sysmem pixel format (" << type
+      FXL_LOG(ERROR) << "sysmem pixel format (" << static_cast<uint32_t>(type)
                      << ") has no corresponding fuchsia::images::PixelFormat";
       return static_cast<fuchsia::images::PixelFormat>(-1);
   }
@@ -58,7 +55,7 @@
 
 // Draws a scenic scene containing a single rectangle with an image pipe material,
 // constructed with buffers populated by a stream provider (in this case, the ArmIspDevice).
-class DemoView : public scenic::BaseView {
+class DemoView : public scenic::BaseView, public fuchsia::camera2::Stream_EventSender {
  public:
   explicit DemoView(scenic::ViewContext context, async::Loop* loop, bool chaos)
       : BaseView(std::move(context), "Camera Demo"),
@@ -81,27 +78,15 @@
                                           bool chaos) {
     auto view = std::make_unique<DemoView>(std::move(context), loop, chaos);
 
-    int result = open(kDevicePath, O_RDONLY);
-    if (result < 0) {
-      FXL_LOG(ERROR) << "Error opening " << kDevicePath;
-      return nullptr;
-    }
-    fbl::unique_fd isp_fd(result);
-
-    zx::handle isp_handle;
-    zx_status_t status = fdio_get_service_handle(isp_fd.get(), isp_handle.reset_and_get_address());
-    if (status != ZX_OK) {
-      FXL_PLOG(ERROR, status) << "Failed to get service handle";
+    auto stream_provider = StreamProvider::Create(StreamProvider::Source::ISP);
+    if (!stream_provider) {
+      FXL_LOG(ERROR) << "Failed to get ISP stream provider";
       return nullptr;
     }
 
-    fuchsia_sysmem_BufferCollectionInfo buffers;
-    status = fuchsia_camera_test_IspTesterCreateStream(
-        isp_handle.get(), view->stream_.NewRequest().TakeChannel().release(), &buffers);
-    if (status != ZX_OK) {
-      FXL_PLOG(ERROR, status) << "Failed to call CreateStream";
-      return nullptr;
-    }
+    fuchsia::sysmem::ImageFormat_2 format;
+    fuchsia::sysmem::BufferCollectionInfo_2 buffers;
+    view->stream_ = stream_provider->ConnectToStream(view.get(), &format, &buffers);
 
     uint32_t image_pipe_id = view->session()->AllocResourceId();
     view->session()->Enqueue(
@@ -109,28 +94,25 @@
     scenic::Material material(view->session());
     material.SetTexture(image_pipe_id);
     view->session()->ReleaseResource(image_pipe_id);
-    scenic::Rectangle shape(view->session(), buffers.format.image.width,
-                            buffers.format.image.height);
-    view->shape_width_ = buffers.format.image.width;
+    scenic::Rectangle shape(view->session(), format.display_width, format.display_height);
+    view->shape_width_ = format.display_width;
     view->node_.SetShape(shape);
     view->node_.SetMaterial(material);
     view->root_node().AddChild(view->node_);
 
     fuchsia::images::ImageInfo image_info{};
-    image_info.width = buffers.format.image.width;
-    image_info.height = buffers.format.image.height;
-    image_info.stride = buffers.format.image.planes[0].bytes_per_row;
-    image_info.pixel_format = convertFormat(buffers.format.image.pixel_format.type);
+    image_info.width = format.display_width;
+    image_info.height = format.display_height;
+    image_info.stride = format.bytes_per_row;
+    image_info.pixel_format = convertFormat(format.pixel_format.type);
     for (uint32_t i = 0; i < buffers.buffer_count; ++i) {
       uint32_t image_id = i + 1;  // Scenic doesn't allow clients to use image ID 0.
-      zx::vmo vmo(buffers.vmos[i]);
-      view->image_pipe_->AddImage(image_id, image_info, std::move(vmo), 0, buffers.vmo_size,
+      view->image_pipe_->AddImage(image_id, image_info, std::move(buffers.buffers[i].vmo), 0,
+                                  buffers.settings.buffer_settings.size_bytes,
                                   fuchsia::images::MemoryType::HOST_MEMORY);
       view->image_ids_[i] = image_id;
     }
 
-    view->stream_.events().OnFrameAvailable =
-        fit::bind_member(view.get(), &DemoView::OnFrameAvailable);
     view->stream_->Start();
 
     view->InvalidateScene();
@@ -159,13 +141,14 @@
   // |scenic::SessionListener|
   void OnScenicError(std::string error) override { FXL_LOG(ERROR) << "Scenic Error " << error; }
 
-  void OnFrameAvailable(fuchsia::camera::common::FrameAvailableEvent event) {
+  // |fuchsia::camera2::Stream_EventSender|
+  void OnFrameAvailable(fuchsia::camera2::FrameAvailableInfo info) override {
     SleepIfChaos();
     if (!has_logical_size()) {
-      stream_->ReleaseFrame(event.buffer_id);
+      stream_->ReleaseFrame(info.buffer_id);
       return;
     }
-    if (event.frame_status != fuchsia::camera::common::FrameStatus::OK) {
+    if (info.frame_status != fuchsia::camera2::FrameStatus::OK) {
       FXL_LOG(ERROR) << "Received OnFrameAvailable with error event";
       return;
     }
@@ -188,20 +171,20 @@
 
     uint64_t now_ns = zx_clock_get_monotonic();
     image_pipe_->PresentImage(
-        image_ids_[event.buffer_id], now_ns, std::move(acquire_fences), std::move(release_fences),
+        image_ids_[info.buffer_id], now_ns, std::move(acquire_fences), std::move(release_fences),
         [this](fuchsia::images::PresentationInfo presentation_info) { SleepIfChaos(); });
 
     auto waiter = std::make_unique<async::Wait>(
         release_fence.get(), ZX_EVENT_SIGNALED, 0,
-        [this, event](async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
-                      const zx_packet_signal_t* signal) {
+        [this, buffer_id = info.buffer_id](async_dispatcher_t* dispatcher, async::Wait* wait,
+                                           zx_status_t status, const zx_packet_signal_t* signal) {
           if (status != ZX_OK) {
             FXL_PLOG(ERROR, status) << "Wait failed";
             loop_->Quit();
             return;
           }
           SleepIfChaos();
-          stream_->ReleaseFrame(event.buffer_id);
+          stream_->ReleaseFrame(buffer_id);
         });
     waiters_.emplace(std::move(waiter), std::move(release_fence));
     status = waiters_.back().first->Begin(loop_->dispatcher());
@@ -232,7 +215,7 @@
   bool chaos_;
   std::mt19937 chaos_gen_;
   std::binomial_distribution<uint32_t> chaos_dist_;
-  fuchsia::camera::common::StreamPtr stream_;
+  std::unique_ptr<fuchsia::camera2::Stream> stream_;
   scenic::ShapeNode node_;
   fuchsia::images::ImagePipePtr image_pipe_;
   std::map<uint32_t, uint32_t> image_ids_;
diff --git a/src/camera/examples/demo/meta/demo.cmx b/src/camera/examples/demo/meta/demo.cmx
index 5094350..057da52 100644
--- a/src/camera/examples/demo/meta/demo.cmx
+++ b/src/camera/examples/demo/meta/demo.cmx
@@ -1,18 +1,17 @@
 {
-    "program": {
-        "binary": "bin/app"
-    },
-    "sandbox": {
-        "dev": [
-            "class/isp-device-test"
-        ],
-        "services": [
-            "fuchsia.sys.Environment",
-            "fuchsia.sys.Launcher",
-            "fuchsia.tracing.provider.Registry",
-            "fuchsia.ui.policy.Presenter",
-            "fuchsia.ui.scenic.Scenic",
-            "fuchsia.camera.test.IspTester"
-        ]
-    }
-}
+  "program": {
+    "binary": "bin/app"
+  },
+  "sandbox": {
+    "dev": [
+      "class/isp-device-test"
+    ],
+    "services": [
+      "fuchsia.sys.Environment",
+      "fuchsia.sys.Launcher",
+      "fuchsia.tracing.provider.Registry",
+      "fuchsia.ui.policy.Presenter",
+      "fuchsia.ui.scenic.Scenic"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/src/camera/examples/demo/stream_provider/BUILD.gn b/src/camera/examples/demo/stream_provider/BUILD.gn
new file mode 100644
index 0000000..ea58016
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/BUILD.gn
@@ -0,0 +1,53 @@
+# 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.
+
+group("stream_provider") {
+  public_deps = [
+    ":stream_provider_lib",
+  ]
+  public_configs = [
+    ":stream_provider_config",
+  ]
+}
+
+source_set("stream_provider_lib") {
+  visibility = [ ":*" ]
+
+  sources = [
+    "stream_provider.cc",
+    "isp_stream_provider.cc",
+  ]
+
+  deps = [
+    "//garnet/public/lib/fsl",
+    "//garnet/public/lib/ui/base_view/cpp",
+    "//garnet/public/lib/ui/scenic/cpp",
+    "//sdk/fidl/fuchsia.camera2.hal",
+    "//sdk/fidl/fuchsia.ui.gfx",
+    "//sdk/lib/fidl/cpp",
+    "//src/lib/fxl",
+    "//src/lib/ui/scenic/cpp",
+    "//src/ui/lib/glm_workaround",
+    "//zircon/public/lib/async-loop-cpp",
+    "//zircon/system/fidl/fuchsia-camera-common",
+    "//zircon/system/fidl/fuchsia-camera-test",
+  ]
+
+  configs += [
+    ":stream_provider_config",
+  ]
+
+  public_deps = [
+    "//sdk/fidl/fuchsia.camera2",
+    "//zircon/system/fidl/fuchsia-sysmem",
+  ]
+}
+
+config("stream_provider_config") {
+  visibility = [ ":*" ]
+
+  include_dirs = [
+    "//src/camera/examples/demo/stream_provider/include",
+  ]
+}
diff --git a/src/camera/examples/demo/stream_provider/camera_common_stream_shim.h b/src/camera/examples/demo/stream_provider/camera_common_stream_shim.h
new file mode 100644
index 0000000..c2bee8da8
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/camera_common_stream_shim.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef SRC_CAMERA_EXAMPLES_DEMO_CAMERA_COMMON_STREAM_SHIM_H_
+#define SRC_CAMERA_EXAMPLES_DEMO_CAMERA_COMMON_STREAM_SHIM_H_
+
+#include <fuchsia/camera/common/cpp/fidl.h>
+#include <fuchsia/camera2/cpp/fidl.h>
+
+#include <memory>
+
+namespace camera {
+
+// This class provides a shim to serve a fuchsia::camera2::Stream interface to a caller using a
+// fuchsia::camera::common::Stream client connection.
+class CameraCommonStreamShim : public fuchsia::camera2::Stream {
+ public:
+  CameraCommonStreamShim(fuchsia::camera::common::StreamPtr stream,
+                         fuchsia::camera2::Stream_EventSender* event_handler)
+      : stream_(std::move(stream)), event_handler_(event_handler) {
+    stream_.events().OnFrameAvailable = [this](fuchsia::camera::common::FrameAvailableEvent event) {
+      event_handler_->OnFrameAvailable(Convert(event));
+    };
+  }
+  virtual void Start() override { stream_->Start(); }
+  virtual void Stop() override { stream_->Stop(); }
+  virtual void ReleaseFrame(uint32_t buffer_id) override { stream_->ReleaseFrame(buffer_id); }
+  virtual void AcknowledgeFrameError() override {}
+  virtual void SetRegionOfInterest(float x_min, float y_min, float x_max, float y_max,
+                                   SetRegionOfInterestCallback callback) override {
+    callback(ZX_ERR_NOT_SUPPORTED);
+  }
+  virtual void SetImageFormat(uint32_t image_format_index,
+                              SetImageFormatCallback callback) override {
+    callback(ZX_ERR_NOT_SUPPORTED);
+  }
+  virtual void GetImageFormats(GetImageFormatsCallback callback) override {
+    callback(std::vector<fuchsia::sysmem::ImageFormat_2>());
+  }
+  static fuchsia::camera2::FrameAvailableInfo Convert(
+      fuchsia::camera::common::FrameAvailableEvent x) {
+    fuchsia::camera2::FrameAvailableInfo ret;
+    ret.frame_status = Convert(x.frame_status);
+    ret.buffer_id = x.buffer_id;
+    ret.metadata = Convert(x.metadata);
+    return ret;
+  }
+  static fuchsia::camera2::FrameStatus Convert(fuchsia::camera::common::FrameStatus frame_status) {
+    switch (frame_status) {
+      case fuchsia::camera::common::FrameStatus::OK:
+        return fuchsia::camera2::FrameStatus::OK;
+      case fuchsia::camera::common::FrameStatus::ERROR_FRAME:
+        return fuchsia::camera2::FrameStatus::ERROR_FRAME;
+      case fuchsia::camera::common::FrameStatus::ERROR_BUFFER_FULL:
+        return fuchsia::camera2::FrameStatus::ERROR_BUFFER_FULL;
+      default:
+        FXL_LOG(ERROR) << "Inconvertible Value " << static_cast<uint32_t>(frame_status);
+        return fuchsia::camera2::FrameStatus::OK;
+    }
+  }
+  static fuchsia::camera2::FrameMetadata Convert(fuchsia::camera::common::Metadata metadata) {
+    fuchsia::camera2::FrameMetadata ret;
+    ret.set_timestamp(metadata.timestamp);
+    return ret;
+  }
+
+ private:
+  fuchsia::camera::common::StreamPtr stream_;
+  fuchsia::camera2::Stream_EventSender* event_handler_;
+};
+
+}  // namespace camera
+
+#endif  // SRC_CAMERA_EXAMPLES_DEMO_CAMERA_COMMON_STREAM_SHIM_H_
diff --git a/src/camera/examples/demo/stream_provider/include/stream_provider.h b/src/camera/examples/demo/stream_provider/include/stream_provider.h
new file mode 100644
index 0000000..e266d3a
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/include/stream_provider.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef SRC_CAMERA_EXAMPLES_DEMO_STREAM_PROVIDER_STREAM_PROVIDER_H_
+#define SRC_CAMERA_EXAMPLES_DEMO_STREAM_PROVIDER_STREAM_PROVIDER_H_
+
+#include <fuchsia/camera2/cpp/fidl.h>
+#include <fuchsia/sysmem/cpp/fidl.h>
+#include <zircon/types.h>
+
+#include <memory>
+#include <string>
+
+// The StreamProvider class allows the caller to connect to a fuchsia::camera2::Stream client
+// endpoint from a variety of sources.
+class StreamProvider {
+ public:
+  enum class Source {
+    ISP,
+    NUM_SOURCES,
+  };
+  virtual ~StreamProvider() = default;
+
+  // Creates a provider from the given source
+  // Args:
+  //   |source|: the desired source of the stream
+  // Returns:
+  //   A StreamProvider instance, or nullptr on error
+  static std::unique_ptr<StreamProvider> Create(Source source);
+
+  // Gets the friendly name of stream source specified during creation of the provider
+  // Returns:
+  //   The friendly name of the source.
+  virtual std::string GetName() = 0;
+
+  // Creates a Stream instance, routing events to the given event_handler.
+  // This method reflects the corresponding API in CameraManager, however here the caller is not
+  // able to participate in buffer format negotiation. The caller must ensure that event_handler
+  // remain valid for the lifetime of the returned Stream.
+  // Args:
+  //   |event_handler|: a caller-provided implementation of Stream event handlers
+  //   |format_out|: output parameter that describes the format of the created stream
+  //   |buffers_out|: output parameter that describes the buffers backing the created stream
+  // Returns:
+  //   A Stream instance, or nullptr on error.
+  virtual std::unique_ptr<fuchsia::camera2::Stream> ConnectToStream(
+      fuchsia::camera2::Stream_EventSender* event_handler,
+      fuchsia::sysmem::ImageFormat_2* format_out,
+      fuchsia::sysmem::BufferCollectionInfo_2* buffers_out) = 0;
+};
+
+#endif  // SRC_CAMERA_EXAMPLES_DEMO_STREAM_PROVIDER_STREAM_PROVIDER_H_
diff --git a/src/camera/examples/demo/stream_provider/isp_stream_provider.cc b/src/camera/examples/demo/stream_provider/isp_stream_provider.cc
new file mode 100644
index 0000000..436bba1
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/isp_stream_provider.cc
@@ -0,0 +1,73 @@
+// 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.
+
+#include "isp_stream_provider.h"
+
+#include <fcntl.h>
+#include <fuchsia/camera/common/cpp/fidl.h>
+#include <fuchsia/camera/test/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/fdio.h>
+
+#include <src/lib/fxl/logging.h>
+
+#include "camera_common_stream_shim.h"
+
+static constexpr const char* kDevicePath = "/dev/class/isp-device-test/000";
+
+// Create a provider using the known IspDeviceTest device.
+std::unique_ptr<StreamProvider> IspStreamProvider::Create() {
+  auto provider = std::make_unique<IspStreamProvider>();
+
+  int result = open(kDevicePath, O_RDONLY);
+  if (result < 0) {
+    FXL_LOG(ERROR) << "Error opening " << kDevicePath;
+    return nullptr;
+  }
+  provider->isp_fd_.reset(result);
+
+  return std::move(provider);
+}
+
+// Offer a stream as served through the tester interface.
+std::unique_ptr<fuchsia::camera2::Stream> IspStreamProvider::ConnectToStream(
+    fuchsia::camera2::Stream_EventSender* event_handler, fuchsia::sysmem::ImageFormat_2* format_out,
+    fuchsia::sysmem::BufferCollectionInfo_2* buffers_out) {
+  if (!format_out || !buffers_out) {
+    return nullptr;
+  }
+
+  // Get a channel to the tester device.
+  zx::channel channel;
+  zx_status_t status = fdio_get_service_handle(isp_fd_.get(), channel.reset_and_get_address());
+  if (status != ZX_OK) {
+    FXL_PLOG(ERROR, status) << "Failed to get service handle";
+    return nullptr;
+  }
+
+  // Bind the tester interface and create a stream.
+  fuchsia::camera::test::IspTesterSyncPtr tester;
+  tester.Bind(std::move(channel));
+  fuchsia::camera::common::StreamPtr stream;
+  fuchsia::sysmem::BufferCollectionInfo buffers;
+  status = tester->CreateStream(stream.NewRequest(), &buffers);
+  if (status != ZX_OK) {
+    FXL_PLOG(ERROR, status) << "Failed to create stream";
+    return nullptr;
+  }
+
+  // Populate output parameters with ISP-provided values and known parameters.
+  format_out->pixel_format.type = buffers.format.image().pixel_format.type;
+  format_out->display_width = buffers.format.image().width;
+  format_out->display_height = buffers.format.image().height;
+  format_out->bytes_per_row = buffers.format.image().planes[0].bytes_per_row;
+  buffers_out->buffer_count = buffers.buffer_count;
+  buffers_out->settings.buffer_settings.size_bytes = buffers.vmo_size;
+  for (uint32_t i = 0; i < buffers.buffer_count; ++i) {
+    buffers_out->buffers[i].vmo = std::move(buffers.vmos[i]);
+    buffers_out->buffers[i].vmo_usable_start = 0;
+  }
+
+  return std::make_unique<camera::CameraCommonStreamShim>(std::move(stream), event_handler);
+}
diff --git a/src/camera/examples/demo/stream_provider/isp_stream_provider.h b/src/camera/examples/demo/stream_provider/isp_stream_provider.h
new file mode 100644
index 0000000..0290828
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/isp_stream_provider.h
@@ -0,0 +1,25 @@
+// 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.
+
+#ifndef SRC_CAMERA_EXAMPLES_DEMO_ISP_STREAM_PROVIDER_H_
+#define SRC_CAMERA_EXAMPLES_DEMO_ISP_STREAM_PROVIDER_H_
+
+#include <fbl/unique_fd.h>
+
+#include "stream_provider.h"
+
+class IspStreamProvider : public StreamProvider {
+ public:
+  static std::unique_ptr<StreamProvider> Create();
+  virtual std::unique_ptr<fuchsia::camera2::Stream> ConnectToStream(
+      fuchsia::camera2::Stream_EventSender* event_handler,
+      fuchsia::sysmem::ImageFormat_2* format_out,
+      fuchsia::sysmem::BufferCollectionInfo_2* buffers_out) override;
+  virtual std::string GetName() override { return "Image Signal Processor (ISP)"; }
+
+ private:
+  fbl::unique_fd isp_fd_;
+};
+
+#endif  // SRC_CAMERA_EXAMPLES_DEMO_ISP_STREAM_PROVIDER_H_
diff --git a/src/camera/examples/demo/stream_provider/stream_provider.cc b/src/camera/examples/demo/stream_provider/stream_provider.cc
new file mode 100644
index 0000000..c89940a
--- /dev/null
+++ b/src/camera/examples/demo/stream_provider/stream_provider.cc
@@ -0,0 +1,16 @@
+// 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.
+
+#include "stream_provider.h"
+
+#include "isp_stream_provider.h"
+
+std::unique_ptr<StreamProvider> StreamProvider::Create(Source source) {
+  switch (source) {
+    case Source::ISP:
+      return IspStreamProvider::Create();
+    default:
+      return nullptr;
+  }
+}